Skip to content

Commit 79582b3

Browse files
committed
updates
1 parent e18c22a commit 79582b3

3 files changed

Lines changed: 119 additions & 69 deletions

File tree

src/support/delta_debugging.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ namespace wasm {
3737
// `partition` items.
3838
template<typename T, typename F>
3939
std::vector<T> deltaDebugging(std::vector<T> items, F&& tryPartition) {
40+
if (items.empty()) {
41+
return items;
42+
}
43+
// First try removing everything.
44+
if (tryPartition(0, 1, {})) {
45+
return {};
46+
}
4047
size_t numPartitions = 2;
4148
while (numPartitions <= items.size()) {
4249
// Partition the items.
@@ -98,12 +105,13 @@ std::vector<T> deltaDebugging(std::vector<T> items, F&& tryPartition) {
98105
}
99106
}
100107

101-
// Otherwise, make the partitions finer grained.
102-
if (numPartitions < items.size()) {
103-
numPartitions = std::min(items.size(), 2 * numPartitions);
104-
} else {
108+
if (numPartitions == items.size()) {
109+
// Cannot further refine the partitions. We're done.
105110
break;
106111
}
112+
113+
// Otherwise, make the partitions finer grained.
114+
numPartitions = std::min(items.size(), 2 * numPartitions);
107115
}
108116
return items;
109117
}

src/tools/wasm-reduce/wasm-reduce.cpp

Lines changed: 100 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -894,43 +894,103 @@ struct Reducer
894894
}
895895
}
896896

897+
bool isEmptyBody(Expression* body) {
898+
if (body->is<Nop>() || body->is<Unreachable>()) {
899+
return true;
900+
}
901+
if (auto* block = body->dynCast<Block>()) {
902+
return block->list.empty();
903+
}
904+
return false;
905+
}
906+
897907
void reduceFunctionBodies() {
898908
std::cerr << "| try to remove function bodies\n";
899909
// Use function indices to speed up finding the complement of the kept
900910
// partition.
901-
std::vector<Index> funcs;
902-
funcs.reserve(module->functions.size());
911+
std::vector<Index> nontrivialFuncIndices;
912+
nontrivialFuncIndices.reserve(module->functions.size());
903913
for (Index i = 0; i < module->functions.size(); ++i) {
904-
funcs.push_back(i);
905-
}
906-
deltaDebugging(
907-
std::move(funcs),
908-
[&](Index partitionIndex,
909-
Index numPartitions,
910-
const std::vector<Index>& partition) {
911-
std::cerr << "| try partition " << partitionIndex + 1 << " / "
912-
<< numPartitions << " (size " << partition.size() << ")\n";
913-
std::vector<Name> removed;
914-
removed.reserve(module->functions.size() - partition.size());
915-
Index i = 0;
916-
for (Index j : partition) {
917-
while (i < j) {
918-
removed.push_back(module->functions[i++]->name);
914+
auto& func = module->functions[i];
915+
// Skip functions that already have trivial bodies.
916+
if (func->imported() || isEmptyBody(func->body)) {
917+
continue;
918+
}
919+
nontrivialFuncIndices.push_back(i);
920+
}
921+
// TODO: Use something other than an exception to implement early return.
922+
struct EarlyReturn {};
923+
try {
924+
deltaDebugging(
925+
nontrivialFuncIndices,
926+
[&](Index partitionIndex,
927+
Index numPartitions,
928+
const std::vector<Index>& partition) {
929+
// Stop early if the partition size is less than the square root of
930+
// the remaining set. We don't want to waste time on very fine-grained
931+
// partitions when we could switch to another reduction strategy
932+
// instead.
933+
if (partition.size() > 0 &&
934+
partition.size() < std::sqrt(nontrivialFuncIndices.size())) {
935+
throw EarlyReturn{};
919936
}
920-
++i;
921-
}
922-
while (i < module->functions.size()) {
923-
removed.push_back(module->functions[i++]->name);
924-
}
925-
if (tryToEmptyFunctions(removed)) {
926-
// TODO: Consider doing this just once after the delta debugging since
927-
// we never need to restore from the working copy while removing
928-
// function bodies.
929-
noteReduction(removed.size());
937+
938+
std::cerr << "| try partition " << partitionIndex + 1 << " / "
939+
<< numPartitions << " (size " << partition.size() << ")\n";
940+
Index removedSize = nontrivialFuncIndices.size() - partition.size();
941+
std::vector<Expression*> oldBodies(removedSize);
942+
943+
// We first need to remove each non-kept function body, and later we
944+
// might need to restore the same function bodies. Abstract the logic
945+
// for iterating over these function bodies. `f` takes a Function* and
946+
// Expression*& for the stashed body.
947+
auto forEachRemovedFuncBody = [&](auto f) {
948+
Index bodyIndex = 0;
949+
Index nontrivialIndex = 0;
950+
Index partitionIndex = 0;
951+
while (nontrivialIndex < nontrivialFuncIndices.size()) {
952+
if (partitionIndex < partition.size() &&
953+
nontrivialFuncIndices[nontrivialIndex] ==
954+
partition[partitionIndex]) {
955+
// Kept, skip it.
956+
nontrivialIndex++;
957+
partitionIndex++;
958+
} else {
959+
// Removed, process it
960+
Index funcIndex = nontrivialFuncIndices[nontrivialIndex++];
961+
f(module->functions[funcIndex].get(), oldBodies[bodyIndex++]);
962+
}
963+
}
964+
assert(bodyIndex == removedSize);
965+
assert(partitionIndex == partition.size());
966+
};
967+
968+
// Stash the bodies.
969+
forEachRemovedFuncBody([&](Function* func, Expression*& oldBody) {
970+
oldBody = func->body;
971+
Builder builder(*module);
972+
if (func->getResults() == Type::none) {
973+
func->body = builder.makeNop();
974+
} else {
975+
func->body = builder.makeUnreachable();
976+
}
977+
});
978+
979+
if (!writeAndTestReduction()) {
980+
// Failure. Restore the bodies.
981+
forEachRemovedFuncBody([](Function* func, Expression*& oldBody) {
982+
func->body = oldBody;
983+
});
984+
return false;
985+
}
986+
987+
// Success!
988+
noteReduction(removedSize);
989+
nontrivialFuncIndices = partition;
930990
return true;
931-
}
932-
return false;
933-
});
991+
});
992+
} catch (EarlyReturn) {
993+
}
934994
}
935995

936996
bool reduceFunctions() {
@@ -1085,41 +1145,6 @@ struct Reducer
10851145
}
10861146
}
10871147

1088-
// Try to empty out the bodies of some functions.
1089-
bool tryToEmptyFunctions(std::vector<Name> names) {
1090-
std::vector<Expression*> oldBodies;
1091-
size_t actuallyEmptied = 0;
1092-
for (auto name : names) {
1093-
auto* func = module->getFunction(name);
1094-
auto* oldBody = func->body;
1095-
oldBodies.push_back(oldBody);
1096-
// Nothing to do for imported functions (body is nullptr) or for bodies
1097-
// that have already been as reduced as we can make them.
1098-
if (func->imported() || oldBody->is<Unreachable>() ||
1099-
oldBody->is<Nop>()) {
1100-
continue;
1101-
}
1102-
actuallyEmptied++;
1103-
bool useUnreachable = func->getResults() != Type::none;
1104-
if (useUnreachable) {
1105-
func->body = builder->makeUnreachable();
1106-
} else {
1107-
func->body = builder->makeNop();
1108-
}
1109-
}
1110-
if (actuallyEmptied > 0 && writeAndTestReduction()) {
1111-
std::cerr << "| emptied " << actuallyEmptied << " / "
1112-
<< names.size() << " functions\n";
1113-
return true;
1114-
} else {
1115-
// Restore the bodies.
1116-
for (size_t i = 0; i < names.size(); i++) {
1117-
module->getFunction(names[i])->body = oldBodies[i];
1118-
}
1119-
return false;
1120-
}
1121-
}
1122-
11231148
// Try to actually remove functions. If they are somehow referred to, we will
11241149
// get a validation error and undo it.
11251150
bool tryToRemoveFunctions(std::vector<Name> names) {
@@ -1542,10 +1567,20 @@ More documentation can be found at
15421567

15431568
bool stopping = false;
15441569

1570+
bool first = true;
15451571
while (1) {
15461572
Reducer reducer(
15471573
command, test, working, binary, deNan, verbose, debugInfo, options);
15481574

1575+
// For extremely large modules with slow reproduction commands, reducing
1576+
// function bodies first can be more effective than running passes. TODO:
1577+
// clean this up and reconsider the order of reducers.
1578+
if (first) {
1579+
reducer.loadWorking();
1580+
reducer.reduceFunctionBodies();
1581+
first = false;
1582+
}
1583+
15491584
// run binaryen optimization passes to reduce. passes are fast to run
15501585
// and can often reduce large amounts of code efficiently, as opposed
15511586
// to detructive reduction (i.e., that doesn't preserve correctness as

test/gtest/delta_debugging.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,10 @@ TEST(DeltaDebuggingTest, DifferentTypes) {
8080
std::vector<std::string> expected = {"banana", "date"};
8181
EXPECT_EQ(result, expected);
8282
}
83+
84+
TEST(DeltaDebuggingTest, UnconditionallyTrue) {
85+
std::vector<int> items = {0, 1, 2, 3};
86+
auto result = deltaDebugging(
87+
items, [](size_t, size_t, const std::vector<int>&) { return true; });
88+
EXPECT_TRUE(result.empty());
89+
}

0 commit comments

Comments
 (0)