diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index a763006ab66..0aa64c5f418 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -100,6 +100,7 @@ set(passes_SOURCES StringLowering.cpp Strip.cpp StripTargetFeatures.cpp + StripToolchainAnnotations.cpp TraceCalls.cpp RandomizeBranchHints.cpp RedundantSetElimination.cpp diff --git a/src/passes/StripToolchainAnnotations.cpp b/src/passes/StripToolchainAnnotations.cpp new file mode 100644 index 00000000000..6118998eabf --- /dev/null +++ b/src/passes/StripToolchainAnnotations.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2026 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. + */ + +// +// Removes toolchain-specific annotations from the IR. Typically this should be +// done after toolchain optimizations no longer need the annotations, and before +// shipping the final wasm (VMs do not need these toolchain annotations). +// + +#include "pass.h" +#include "wasm-binary.h" +#include "wasm.h" + +namespace wasm { + +struct StripToolchainAnnotations + : public WalkerPass> { + bool isFunctionParallel() override { return true; } + + bool requiresNonNullableLocalFixups() override { return false; } + + std::unique_ptr create() override { + return std::make_unique(); + } + + void doWalkFunction(Function* func) { + auto& annotations = func->codeAnnotations; + auto iter = annotations.begin(); + while (iter != annotations.end()) { + // Remove the toolchain-specific annotations. + auto& annotation = iter->second; + annotation.removableIfUnused = false; + + // If nothing remains, remove the entire annotation. + if (annotation == CodeAnnotation()) { + iter = annotations.erase(iter); + } else { + ++iter; + } + } + } +}; + +Pass* createStripToolchainAnnotationsPass() { + return new StripToolchainAnnotations(); +} + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 0f83013b8ab..a09f383a23b 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -548,6 +548,9 @@ void PassRegistry::registerPasses() { registerPass("strip-target-features", "strip the wasm target features section", createStripTargetFeaturesPass); + registerPass("strip-toolchain-annotations", + "strip all toolchain-specific code annotations", + createStripToolchainAnnotationsPass); registerPass("translate-to-new-eh", "deprecated; same as translate-to-exnref", createTranslateToExnrefPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 6481e062c6d..00bbdc23754 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -163,6 +163,9 @@ Pass* createSimplifyLocalsNoNestingPass(); Pass* createSimplifyLocalsNoTeePass(); Pass* createSimplifyLocalsNoStructurePass(); Pass* createSimplifyLocalsNoTeeNoStructurePass(); +Pass* createSouperifyPass(); +Pass* createSouperifySingleUsePass(); +Pass* createSpillPointersPass(); Pass* createStackCheckPass(); Pass* createStringGatheringPass(); Pass* createStringLiftingPass(); @@ -171,12 +174,10 @@ Pass* createStringLoweringMagicImportPass(); Pass* createStringLoweringMagicImportAssertPass(); Pass* createStripDebugPass(); Pass* createStripDWARFPass(); +Pass* createStripEHPass(); Pass* createStripProducersPass(); Pass* createStripTargetFeaturesPass(); -Pass* createSouperifyPass(); -Pass* createSouperifySingleUsePass(); -Pass* createSpillPointersPass(); -Pass* createStripEHPass(); +Pass* createStripToolchainAnnotationsPass(); Pass* createStubUnsupportedJSOpsPass(); Pass* createSSAifyPass(); Pass* createSSAifyNoMergePass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 98785fda862..53a8c729fda 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -519,6 +519,9 @@ ;; CHECK-NEXT: --strip-target-features strip the wasm target features ;; CHECK-NEXT: section ;; CHECK-NEXT: +;; CHECK-NEXT: --strip-toolchain-annotations strip all toolchain-specific +;; CHECK-NEXT: code annotations +;; CHECK-NEXT: ;; CHECK-NEXT: --stub-unsupported-js stub out unsupported JS ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 4c0f4f67047..6ffd6950662 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -551,6 +551,9 @@ ;; CHECK-NEXT: --strip-target-features strip the wasm target features ;; CHECK-NEXT: section ;; CHECK-NEXT: +;; CHECK-NEXT: --strip-toolchain-annotations strip all toolchain-specific +;; CHECK-NEXT: code annotations +;; CHECK-NEXT: ;; CHECK-NEXT: --stub-unsupported-js stub out unsupported JS ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 3c201f79ac4..c75f9b7bdc3 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -483,6 +483,9 @@ ;; CHECK-NEXT: --strip-target-features strip the wasm target features ;; CHECK-NEXT: section ;; CHECK-NEXT: +;; CHECK-NEXT: --strip-toolchain-annotations strip all toolchain-specific +;; CHECK-NEXT: code annotations +;; CHECK-NEXT: ;; CHECK-NEXT: --stub-unsupported-js stub out unsupported JS ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/lit.cfg.py b/test/lit/lit.cfg.py index be80ea4fc98..148e89e9031 100644 --- a/test/lit/lit.cfg.py +++ b/test/lit/lit.cfg.py @@ -25,7 +25,10 @@ for tool in ('not', 'foreach'): tool_file = config.binaryen_src_root + '/scripts/' + tool + '.py' python = sys.executable.replace('\\', '/') - config.substitutions.append((tool, python + ' ' + tool_file)) + # Add spaces to avoid replacing every 'not' in the command (like in + # "--strip-anNOTations"; capitalization for emphasis). + config.substitutions.append((' ' + tool + ' ', + ' ' + python + ' ' + tool_file + ' ')) if 'linux' in sys.platform: config.available_features.add('linux') diff --git a/test/lit/passes/strip-toolchain-annotations-func.wast b/test/lit/passes/strip-toolchain-annotations-func.wast new file mode 100644 index 00000000000..b42de3b63ef --- /dev/null +++ b/test/lit/passes/strip-toolchain-annotations-func.wast @@ -0,0 +1,41 @@ +;; RUN: wasm-opt -all --strip-toolchain-annotations %s -S -o - | filecheck %s + +(module + (@metadata.code.inline "\00") + (func $test-func-a + ;; This VM annotation is kept. + ) + + (@binaryen.removable.if.unused) + (func $test-func-b + ;; Toolchain one is removed. + ) + + (@metadata.code.inline "\00") + (@binaryen.removable.if.unused) + (func $test-func-c + ;; Toolchain one is removed, VM one is kept. + ) + + (@binaryen.removable.if.unused) + (@metadata.code.inline "\00") + (func $test-func-d + ;; Reverse order of above. + ) +) + +;; CHECK: (module +;; CHECK-NEXT: (type $0 (func)) +;; CHECK-NEXT: (@metadata.code.inline "\00") +;; CHECK-NEXT: (func $test-func-a (type $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (func $test-func-b (type $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (@metadata.code.inline "\00") +;; CHECK-NEXT: (func $test-func-c (type $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (@metadata.code.inline "\00") +;; CHECK-NEXT: (func $test-func-d (type $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + diff --git a/test/lit/passes/strip-toolchain-annotations.wast b/test/lit/passes/strip-toolchain-annotations.wast new file mode 100644 index 00000000000..8e51cc993be --- /dev/null +++ b/test/lit/passes/strip-toolchain-annotations.wast @@ -0,0 +1,41 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --strip-toolchain-annotations -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 i32) + ;; CHECK-NEXT: (@metadata.code.inline "\00") + ;; CHECK-NEXT: (call $test + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $test + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.inline "\00") + ;; CHECK-NEXT: (call $test + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.inline "\00") + ;; CHECK-NEXT: (call $test + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param i32) + ;; Inlining hints are not removed, as they are for the VM too. + (@metadata.code.inline "\00") + (call $test (i32.const 0)) + + ;; Toolchain hints are removed. + (@binaryen.removable.if.unused) + (call $test (i32.const 1)) + + ;; When both are present, remove the toolchain one, in either order. + (@binaryen.removable.if.unused) + (@metadata.code.inline "\00") + (call $test (i32.const 2)) + + (@metadata.code.inline "\00") + (@binaryen.removable.if.unused) + (call $test (i32.const 3)) + ) +)