| /* |
| * 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. |
| */ |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| #include "ir/effects.h" |
| #include "matchers/effects.h" |
| #include "parser/wat-parser.h" |
| #include "wasm.h" |
| |
| using namespace wasm; |
| using namespace testing; |
| |
| namespace { |
| |
| class EffectAnalyzerTest : public Test { |
| protected: |
| Module wasm; |
| PassOptions options; |
| |
| void SetUp() override { wasm.features = FeatureSet::All; } |
| }; |
| |
| TEST_F(EffectAnalyzerTest, Suspend) { |
| auto moduleText = R"wasm( |
| (module |
| (tag $tag) |
| (func $test |
| (suspend $tag) |
| ) |
| ) |
| )wasm"; |
| |
| auto parseResult = WATParser::parseModule(wasm, moduleText); |
| ASSERT_FALSE(parseResult.getErr()); |
| |
| auto* func = wasm.getFunction("test"); |
| ASSERT_NE(func, nullptr); |
| |
| EffectAnalyzer effects(options, wasm, func->body); |
| |
| // Suspension detection |
| EXPECT_TRUE(effects.suspends); |
| EXPECT_THAT(&effects, Suspends()); |
| EXPECT_TRUE(effects.getSideEffects() & EffectAnalyzer::SideEffects::Suspends); |
| |
| // Suspending executes arbitrary other code in the handler before resuming, |
| // modeled as a call. |
| EXPECT_TRUE(effects.calls); |
| EXPECT_THAT(&effects, Calls()); |
| |
| // Accesses all global mutable state via calls |
| EXPECT_TRUE(effects.accessesMemory()); |
| EXPECT_TRUE(effects.accessesSharedMemory()); |
| EXPECT_TRUE(effects.accessesTable()); |
| EXPECT_TRUE(effects.accessesMutableStruct()); |
| EXPECT_TRUE(effects.accessesSharedMutableStruct()); |
| EXPECT_TRUE(effects.accessesArray()); |
| EXPECT_TRUE(effects.accessesSharedArray()); |
| EXPECT_TRUE(effects.writesGlobalState()); |
| EXPECT_TRUE(effects.readsMutableGlobalState()); |
| EXPECT_TRUE(effects.accessesSharedGlobalState()); |
| |
| // Control flow & side effect queries |
| EXPECT_TRUE(effects.transfersControlFlow()); |
| EXPECT_TRUE(effects.hasNonTrapSideEffects()); |
| EXPECT_TRUE(effects.hasSideEffects()); |
| EXPECT_TRUE(effects.hasUnremovableSideEffects()); |
| } |
| |
| TEST_F(EffectAnalyzerTest, UnknownCall) { |
| auto moduleText = R"wasm( |
| (module |
| (func $callee) |
| (func $caller |
| (call $callee) |
| ) |
| ) |
| )wasm"; |
| |
| auto parseResult = WATParser::parseModule(wasm, moduleText); |
| ASSERT_FALSE(parseResult.getErr()); |
| |
| auto* caller = wasm.getFunction("caller"); |
| ASSERT_NE(caller, nullptr); |
| |
| // With stack switching enabled, unknown calls conservatively assume |
| // suspension. |
| wasm.features.setStackSwitching(true); |
| EffectAnalyzer effectsWithStackSwitch(options, wasm, caller->body); |
| EXPECT_TRUE(effectsWithStackSwitch.suspends); |
| |
| // With stack switching disabled, calls do not suspend. |
| wasm.features.setStackSwitching(false); |
| EffectAnalyzer effectsWithoutStackSwitch(options, wasm, caller->body); |
| EXPECT_FALSE(effectsWithoutStackSwitch.suspends); |
| } |
| |
| } // anonymous namespace |