blob: f464b5fe0f1aac797bf41674f7b70857bee8c7bd [file] [log] [blame] [edit]
/*
* 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 <variant>
#include "passes/stringify-walker.h"
#include "wasm-type.h"
#include "wasm.h"
#include "gtest/gtest.h"
using namespace wasm;
struct FuncStart {
Function* func;
bool operator==(const FuncStart& other) const { return func == other.func; }
};
struct BlockStart {
Block* block;
bool operator==(const BlockStart& other) const {
return block == other.block;
}
};
struct IfStart {
If* iff;
bool operator==(const IfStart& other) const { return iff == other.iff; }
};
struct ElseStart {
bool operator==(const ElseStart&) const { return true; }
};
struct LoopStart {
Loop* loop;
bool operator==(const LoopStart& other) const { return loop == other.loop; }
};
struct TryStart {
Try* tryy;
bool operator==(const TryStart& other) const { return tryy == other.tryy; }
};
struct CatchStart {
Name tag;
bool operator==(const CatchStart& other) const { return tag == other.tag; }
};
struct CatchAllStart {
bool operator==(const CatchAllStart&) const { return true; }
};
struct TryTableStart {
TryTable* tryt;
bool operator==(const TryTableStart& other) const {
return tryt == other.tryt;
}
};
struct End {
Expression* curr;
bool operator==(const End& other) const { return curr == other.curr; }
};
using Delimiter = std::variant<FuncStart,
BlockStart,
IfStart,
ElseStart,
LoopStart,
TryStart,
CatchStart,
CatchAllStart,
TryTableStart,
End>;
using Visited = std::variant<Expression*, Delimiter>;
std::ostream& operator<<(std::ostream& o, const FuncStart& v) {
return o << "FuncStart(" << v.func->name << ")";
}
std::ostream& operator<<(std::ostream& o, const BlockStart& v) {
return o << "BlockStart(" << v.block->name << ")";
}
std::ostream& operator<<(std::ostream& o, const IfStart& v) {
return o << "IfStart(" << v.iff << ")";
}
std::ostream& operator<<(std::ostream& o, const ElseStart& v) {
return o << "ElseStart";
}
std::ostream& operator<<(std::ostream& o, const LoopStart& v) {
return o << "LoopStart(" << v.loop->name << ")";
}
std::ostream& operator<<(std::ostream& o, const TryStart& v) {
return o << "TryStart(" << v.tryy->name << ")";
}
std::ostream& operator<<(std::ostream& o, const CatchStart& v) {
return o << "CatchStart(" << v.tag << ")";
}
std::ostream& operator<<(std::ostream& o, const CatchAllStart& v) {
return o << "CatchAllStart";
}
std::ostream& operator<<(std::ostream& o, const TryTableStart& v) {
return o << "TryTableStart(" << v.tryt << ")";
}
std::ostream& operator<<(std::ostream& o, const End& v) {
return o << "End(" << v.curr << ")";
}
std::ostream& operator<<(std::ostream& o, const Visited& v) {
std::visit([&](auto&& arg) { o << arg; }, v);
return o;
}
struct TestStringifyWalker : StringifyWalker<TestStringifyWalker> {
using SeparatorReason = StringifyWalker<TestStringifyWalker>::SeparatorReason;
std::vector<Visited> visited;
void visitExpression(Expression* curr) { visited.push_back(curr); }
void addUniqueSymbol(SeparatorReason curr) {
if (auto* func = curr.getFuncStart()) {
visited.push_back(FuncStart{func->func});
} else if (auto* block = curr.getBlockStart()) {
visited.push_back(BlockStart{block->block});
} else if (auto* iff = curr.getIfStart()) {
visited.push_back(IfStart{iff->iff});
} else if (curr.getElseStart()) {
visited.push_back(ElseStart{});
} else if (auto* loop = curr.getLoopStart()) {
visited.push_back(LoopStart{loop->loop});
} else if (auto* tryy = curr.getTryStart()) {
visited.push_back(TryStart{tryy->tryy});
} else if (auto* tag = curr.getCatchStart()) {
visited.push_back(CatchStart{tag->tag});
} else if (curr.getCatchAllStart()) {
visited.push_back(CatchAllStart{});
} else if (auto* tryt = curr.getTryTableStart()) {
visited.push_back(TryTableStart{tryt->tryt});
} else if (auto* end = curr.getEnd()) {
visited.push_back(End{end->curr});
}
}
};
TEST(StringifyWalkerTest, Nop) {
Module wasm;
Builder builder(wasm);
// (func $func nop)
auto* nop = builder.makeNop();
auto f = builder.makeFunction("func", Signature(), {}, nop);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{
Visited(FuncStart{func}), Visited(nop), Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, Add) {
Module wasm;
Builder builder(wasm);
// (func $func
// (i32.add
// (i32.const 0)
// (i32.const 1)
// )
// )
auto* a = builder.makeConst(uint32_t(0));
auto* b = builder.makeConst(uint32_t(1));
auto* add = builder.makeBinary(AddInt32, a, b);
auto f = builder.makeFunction("func", Signature(), {}, add);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(a),
Visited(b),
Visited(add),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, EmptyBlock) {
Module wasm;
Builder builder(wasm);
// (func $func
// (block)
// )
auto* block = builder.makeBlock();
auto f = builder.makeFunction("func", Signature(), {}, block);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(block),
Visited(End{nullptr}),
Visited(BlockStart{block}),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, SingletonBlock) {
Module wasm;
Builder builder(wasm);
// (func $func
// (block
// (nop)
// )
// )
auto* nop = builder.makeNop();
auto* block = builder.makeBlock({nop});
auto f = builder.makeFunction("func", Signature(), {}, block);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(block),
Visited(End{nullptr}),
Visited(BlockStart{block}),
Visited(nop),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, Block) {
Module wasm;
Builder builder(wasm);
// (func $func
// (block
// (nop)
// (nop)
// (nop)
// )
// )
auto* nop1 = builder.makeNop();
auto* nop2 = builder.makeNop();
auto* nop3 = builder.makeNop();
auto* block = builder.makeBlock({nop1, nop2, nop3});
auto f = builder.makeFunction("func", Signature(), {}, block);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(block),
Visited(End{nullptr}),
Visited(BlockStart{block}),
Visited(nop1),
Visited(nop2),
Visited(nop3),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, NestedBlocks) {
Module wasm;
Builder builder(wasm);
// (func $func
// (block $a
// (block $b
// (block $c
// (nop)
// )
// )
// )
// )
auto* nop = builder.makeNop();
auto* c = builder.makeBlock({nop});
auto* b = builder.makeBlock({c});
auto* a = builder.makeBlock({b});
auto f = builder.makeFunction("func", Signature(), {}, a);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(a),
Visited(End{nullptr}),
Visited(BlockStart{a}),
Visited(b),
Visited(End{nullptr}),
Visited(BlockStart{b}),
Visited(c),
Visited(End{nullptr}),
Visited(BlockStart{c}),
Visited(nop),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, MixedBlock) {
Module wasm;
Builder builder(wasm);
// (func $func
// (block $a
// (block $b)
// (nop)
// (block $c)
// )
// )
auto* nop = builder.makeNop();
auto* b = builder.makeBlock();
auto* c = builder.makeBlock();
auto* a = builder.makeBlock({b, nop, c});
auto f = builder.makeFunction("func", Signature(), {}, a);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited{FuncStart{func}},
Visited(a),
Visited(End{nullptr}),
Visited(BlockStart{a}),
Visited(b),
Visited(nop),
Visited(c),
Visited(End{nullptr}),
Visited(BlockStart{b}),
Visited(End{nullptr}),
Visited(BlockStart{c}),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, AddBlocks) {
Module wasm;
Builder builder(wasm);
// (func $func
// (i32.add
// (block (i32.const 0))
// (block (i32.const 1))
// )
// )
auto* c0 = builder.makeConst(uint32_t(0));
auto* c1 = builder.makeConst(uint32_t(1));
auto* lhs = builder.makeBlock({c0});
auto* rhs = builder.makeBlock({c1});
auto* add = builder.makeBinary(AddInt32, lhs, rhs);
auto f = builder.makeFunction("func", Signature(), {}, add);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(lhs),
Visited(rhs),
Visited(add),
Visited(End{nullptr}),
Visited(BlockStart{lhs}),
Visited(c0),
Visited(End{nullptr}),
Visited(BlockStart{rhs}),
Visited(c1),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, IfElse) {
Module wasm;
Builder builder(wasm);
// (func $func
// (if
// (i32.const 1)
// (then (nop))
// (else (nop))
// )
// )
auto* cond = builder.makeConst(uint32_t(1));
auto* ifTrue = builder.makeNop();
auto* ifFalse = builder.makeNop();
auto* iff = builder.makeIf(cond, ifTrue, ifFalse);
auto f = builder.makeFunction("func", Signature(), {}, iff);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(cond),
Visited(iff),
Visited(End{nullptr}),
Visited(IfStart{iff}),
Visited(ifTrue),
Visited(ElseStart{}),
Visited(ifFalse),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, If) {
Module wasm;
Builder builder(wasm);
// (func $func
// (if
// (i32.const 1)
// (then (nop))
// )
// )
auto* cond = builder.makeConst(uint32_t(1));
auto* ifTrue = builder.makeNop();
auto* iff = builder.makeIf(cond, ifTrue);
auto f = builder.makeFunction("func", Signature(), {}, iff);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(cond),
Visited(iff),
Visited(End{nullptr}),
Visited(IfStart{iff}),
Visited(ifTrue),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, IfNestedCondition) {
Module wasm;
Builder builder(wasm);
// (func $func
// (if
// (if
// (i32.const 1)
// (then (i32.const 2))
// (else (i32.const 3))
// )
// (then (nop))
// (else (nop))
// )
// )
auto* c1 = builder.makeConst(uint32_t(1));
auto* c2 = builder.makeConst(uint32_t(2));
auto* c3 = builder.makeConst(uint32_t(3));
auto* cond = builder.makeIf(c1, c2, c3);
auto* ifTrue = builder.makeNop();
auto* ifFalse = builder.makeNop();
auto* iff = builder.makeIf(cond, ifTrue, ifFalse);
auto f = builder.makeFunction("func", Signature(), {}, iff);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(c1),
Visited(cond),
Visited(iff),
Visited(End{nullptr}),
Visited(IfStart{cond}),
Visited(c2),
Visited(ElseStart{}),
Visited(c3),
Visited(End{nullptr}),
Visited(IfStart{iff}),
Visited(ifTrue),
Visited(ElseStart{}),
Visited(ifFalse),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, Loop) {
Module wasm;
Builder builder(wasm);
// (func $func (loop (nop)))
auto* nop = builder.makeNop();
auto* loop = builder.makeLoop("loop", nop);
auto f = builder.makeFunction("func", Signature(), {}, loop);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(loop),
Visited(End{nullptr}),
Visited(LoopStart{loop}),
Visited(nop),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, Try) {
Module wasm;
Builder builder(wasm);
// (func $func
// (try
// (do (nop))
// )
// )
auto* nop = builder.makeNop();
std::vector<Name> tags;
std::vector<Expression*> bodies;
auto* tryy = builder.makeTry(nop, tags, bodies);
auto f = builder.makeFunction("func", Signature(), {}, tryy);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(tryy),
Visited(End{nullptr}),
Visited(TryStart{tryy}),
Visited(nop),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, TryCatch) {
Module wasm;
Builder builder(wasm);
// (func $func
// (try
// (do (nop))
// (catch $e (nop))
// )
// )
auto* nop = builder.makeNop();
auto* body = builder.makeNop();
std::vector<Name> tags{"e"};
std::vector<Expression*> bodies{body};
auto* tryy = builder.makeTry(nop, tags, bodies);
auto f = builder.makeFunction("func", Signature(), {}, tryy);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(tryy),
Visited(End{nullptr}),
Visited(TryStart{tryy}),
Visited(nop),
Visited(CatchStart{"e"}),
Visited(body),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, TryCatchAll) {
Module wasm;
Builder builder(wasm);
// (func $func
// (try
// (do (nop))
// (catch_all (nop))
// )
// )
auto* nop = builder.makeNop();
auto* body = builder.makeNop();
std::vector<Name> tags;
std::vector<Expression*> bodies{body};
auto* tryy = builder.makeTry(nop, tags, bodies);
auto f = builder.makeFunction("func", Signature(), {}, tryy);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(tryy),
Visited(End{nullptr}),
Visited(TryStart{tryy}),
Visited(nop),
Visited(CatchAllStart{}),
Visited(body),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
TEST(StringifyWalkerTest, TryMultipleCatch) {
Module wasm;
Builder builder(wasm);
// (func $func
// (try
// (do (nop))
// (catch $e1 (nop))
// (catch $e2 (nop))
// (catch_all (nop))
// )
// )
auto* nop = builder.makeNop();
auto* body1 = builder.makeNop();
auto* body2 = builder.makeNop();
auto* body3 = builder.makeNop();
std::vector<Name> tags{"e1", "e2"};
std::vector<Expression*> bodies{body1, body2, body3};
auto* tryy = builder.makeTry(nop, tags, bodies);
auto f = builder.makeFunction("func", Signature(), {}, tryy);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(tryy),
Visited(End{nullptr}),
Visited(TryStart{tryy}),
Visited(nop),
Visited(CatchStart{"e1"}),
Visited(body1),
Visited(CatchStart{"e2"}),
Visited(body2),
Visited(CatchAllStart{}),
Visited(body3),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}
// TEST(StringifyWalkerTest, TryDelegate) {
// Module wasm;
// Builder builder(wasm);
// // (func $func
// // (try
// // (do (nop))
// // (delegate 0)
// // )
// // )
// auto* nop = builder.makeNop();
// auto* tryy = builder.makeTry(nop, DELEGATE_CALLER_TARGET);
// auto f = builder.makeFunction("func", Signature(), {}, tryy);
// auto* func = f.get();
// wasm.addFunction(std::move(f));
// TestStringifyWalker walker;
// walker.walkModule(&wasm);
// std::vector<Visited> expected{Visited(FuncStart{func}),
// Visited(tryy),
// Visited(End{nullptr}),
// Visited(TryStart{tryy}),
// Visited(nop),
// Visited(Delegate{})};
// EXPECT_EQ(walker.visited, expected);
// }
TEST(StringifyWalkerTest, TryTable) {
Module wasm;
Builder builder(wasm);
// (func $func
// (try_table
// (nop)
// )
// )
auto* nop = builder.makeNop();
std::vector<Name> tags;
std::vector<Name> labels;
std::vector<bool> refs;
auto* tryy = builder.makeTryTable(nop, tags, labels, refs);
auto f = builder.makeFunction("func", Signature(), {}, tryy);
auto* func = f.get();
wasm.addFunction(std::move(f));
TestStringifyWalker walker;
walker.walkModule(&wasm);
std::vector<Visited> expected{Visited(FuncStart{func}),
Visited(tryy),
Visited(End{nullptr}),
Visited(TryTableStart{tryy}),
Visited(nop),
Visited(End{nullptr})};
EXPECT_EQ(walker.visited, expected);
}