blob: 7cdafc325544a6b3091fca88cbd4ec714b3dc74f [file] [edit]
/*
* 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.
*/
#ifndef wasm_support_delta_debugging_h
#define wasm_support_delta_debugging_h
#include <algorithm>
#include <cassert>
#include <vector>
#include "support/index.h"
namespace wasm {
// Use the delta debugging algorithm (Zeller 2002,
// https://dl.acm.org/doi/10.1109/32.988498) to find the minimal set of
// items necessary to preserve some property. `working` is the minimal set of
// items found so far and `test` is the smaller set of items that should be
// tested next. After testing, call `accept()`, `reject()`, or `resolve(bool
// accepted)` to update the working and test sets appropriately.
template<typename T> struct DeltaDebugger {
std::vector<T> working;
std::vector<T> test;
private:
Index numPartitions = 1;
Index currentPartition = 0;
bool testingComplements = false;
bool triedEmpty = false;
bool isFinished = false;
std::vector<std::vector<T>> partitions;
public:
DeltaDebugger(std::vector<T> items) : working(std::move(items)) {}
bool finished() const {
return isFinished || (triedEmpty && working.size() <= 1);
}
Index partitionCount() { return numPartitions; }
Index partitionIndex() { return currentPartition; }
void accept() {
if (finished()) {
return;
}
if (test.empty()) {
triedEmpty = true;
}
working = std::move(test);
// We might be finished now even if we weren't before.
if (finished()) {
return;
}
if (!testingComplements) {
numPartitions = 2;
} else {
numPartitions = std::max(numPartitions - 1, Index(2));
}
testingComplements = false;
currentPartition = 0;
updateTest();
}
void reject() {
if (test.empty()) {
triedEmpty = true;
numPartitions = 2;
updateTest();
return;
}
if (finished()) {
return;
}
++currentPartition;
if (currentPartition >= partitions.size()) {
// No need to test complements if there are only two partitions, since
// that is no different.
if (!testingComplements && numPartitions > 2) {
testingComplements = true;
currentPartition = 0;
} else {
if (numPartitions >= working.size()) {
isFinished = true;
return;
}
// Refine the partitions.
numPartitions = std::min(Index(working.size()), 2 * numPartitions);
testingComplements = false;
currentPartition = 0;
}
}
updateTest();
}
// Convenience wrapper for when there is already a bool determining whether to
// accept or reject the current test sequence.
void resolve(bool success) {
if (success) {
accept();
} else {
reject();
}
}
private:
void updateTest() {
if (finished()) {
test.clear();
return;
}
if (currentPartition == 0 && !testingComplements) {
generatePartitions();
}
if (!testingComplements) {
test = partitions[currentPartition];
} else {
test.clear();
test.reserve(working.size() - partitions[currentPartition].size());
for (size_t i = 0; i < partitions.size(); ++i) {
if (i != currentPartition) {
test.insert(test.end(), partitions[i].begin(), partitions[i].end());
}
}
}
}
void generatePartitions() {
partitions.clear();
size_t size = working.size();
assert(numPartitions != 0 && numPartitions <= size);
size_t basePartitionSize = size / numPartitions;
size_t rem = size % numPartitions;
size_t idx = 0;
for (size_t i = 0; i < numPartitions; ++i) {
size_t partitionSize = basePartitionSize + (i < rem ? 1 : 0);
if (partitionSize > 0) {
std::vector<T> partition;
partition.reserve(partitionSize);
for (size_t j = 0; j < partitionSize; ++j) {
partition.push_back(working[idx++]);
}
partitions.emplace_back(std::move(partition));
}
}
}
};
} // namespace wasm
#endif // wasm_support_delta_debugging_h