#include <optional>
#include <random>
#include <string>
#include <variant>
#include "support/command-line.h"
#include "tools/fuzzing/heap-types.h"
#include "tools/fuzzing/random.h"
#include "wasm-type-printing.h"
namespace wasm {
using RandEngine = std::mt19937_64;
uint64_t getSeed() {
std::random_device rand;
return std::uniform_int_distribution<uint64_t>{}(rand);
}
struct Fuzzer {
bool verbose;
std::vector<HeapType> types;
std::vector<std::vector<Index>> subtypeIndices;
Random rand;
Fuzzer(bool verbose) : verbose(verbose), rand({}) {}
void run(uint64_t seed);
static void printTypes(const std::vector<HeapType>&);
void checkSubtypes() const;
void checkLUBs() const;
void checkCanonicalization();
void checkInhabitable();
};
void Fuzzer::run(uint64_t seed) {
RandEngine getRand(seed);
std::cout << "Running with seed " << seed << "\n";
std::vector<char> bytes(4096);
for (size_t i = 0; i < bytes.size(); i += sizeof(uint64_t)) {
*(uint64_t*)(bytes.data() + i) = getRand();
}
rand = Random(std::move(bytes));
HeapTypeGenerator generator =
HeapTypeGenerator::create(rand, FeatureSet::All, 20);
auto result = generator.builder.build();
if (auto* err = result.getError()) {
Fatal() << "Failed to build types: " << err->reason << " at index "
<< err->index;
}
types = std::move(*result);
subtypeIndices = std::move(generator.subtypeIndices);
if (verbose) {
printTypes(types);
}
checkSubtypes();
checkLUBs();
checkCanonicalization();
checkInhabitable();
}
void Fuzzer::printTypes(const std::vector<HeapType>& types) {
std::cout << "Built " << types.size() << " types:\n";
struct FatalTypeNameGenerator
: TypeNameGeneratorBase<FatalTypeNameGenerator> {
TypeNames getNames(HeapType type) {
Fatal() << "trying to print unknown heap type";
}
} fatalGenerator;
IndexedTypeNameGenerator<FatalTypeNameGenerator> print(types, fatalGenerator);
std::unordered_map<HeapType, size_t> seen;
std::optional<RecGroup> currRecGroup;
auto inRecGroup = [&]() { return currRecGroup && currRecGroup->size() > 1; };
for (size_t i = 0; i < types.size(); ++i) {
auto type = types[i];
if (type.getRecGroup() != currRecGroup) {
if (inRecGroup()) {
std::cout << ")\n";
}
currRecGroup = type.getRecGroup();
if (inRecGroup()) {
std::cout << "(rec\n";
}
}
if (inRecGroup()) {
std::cout << ' ';
}
auto [it, inserted] = seen.insert({type, i});
if (inserted) {
std::cout << print(type);
} else {
std::cout << "(type $" << i << " identical to $" << it->second << ")";
}
std::cout << "\n";
}
if (inRecGroup()) {
std::cout << ")\n";
}
}
void Fuzzer::checkSubtypes() const {
for (size_t super = 0; super < types.size(); ++super) {
for (auto sub : subtypeIndices[super]) {
if (!HeapType::isSubType(types[sub], types[super])) {
Fatal() << "HeapType " << sub << " should be a subtype of HeapType "
<< super << " but is not!\n"
<< sub << ": " << types[sub] << "\n"
<< super << ": " << types[super] << "\n";
}
}
}
}
void Fuzzer::checkLUBs() const {
for (size_t i = 0; i < types.size(); ++i) {
for (size_t j = i; j < types.size(); ++j) {
HeapType a = types[i], b = types[j];
auto lub = HeapType::getLeastUpperBound(a, b);
if (lub) {
if (lub != HeapType::getLeastUpperBound(b, a) ||
lub != HeapType::getLeastUpperBound(a, b)) {
Fatal() << "Could not calculate a stable LUB of HeapTypes " << i
<< " and " << j << "!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n";
}
if (!HeapType::isSubType(a, *lub)) {
Fatal() << "HeapType " << i
<< " is not a subtype of its LUB with HeapType " << j << "!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << *lub << "\n";
}
if (!HeapType::isSubType(b, *lub)) {
Fatal() << "HeapType " << j
<< " is not a subtype of its LUB with HeapType " << i << "!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << *lub << "\n";
}
if (lub != HeapType::getLeastUpperBound(a, *lub)) {
Fatal() << "The LUB of HeapType " << i << " and HeapType " << j
<< " should be the LUB of itself and HeapType " << i
<< ", but it is not!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << *lub << "\n";
}
if (lub != HeapType::getLeastUpperBound(*lub, b)) {
Fatal() << "The LUB of HeapType " << i << " and HeapType " << j
<< " should be the LUB of itself and HeapType " << j
<< ", but it is not!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << *lub << "\n";
}
} else {
if (auto lub2 = HeapType::getLeastUpperBound(b, a)) {
Fatal() << "There is no LUB of HeapType " << i << " and HeapType "
<< j << ", but there is a LUB in the reverse direction!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << *lub2 << "\n";
}
if (HeapType::isSubType(a, b)) {
Fatal() << "There is no LUB of HeapType " << i << " and HeapType "
<< j << ", but HeapType " << i << " is a subtype of HeapType "
<< j << "!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n";
}
if (HeapType::isSubType(b, a)) {
Fatal() << "There is no LUB of HeapType " << i << " and HeapType "
<< j << ", but HeapType " << j << " is a subtype of HeapType "
<< i << "!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n";
}
}
}
}
}
void Fuzzer::checkCanonicalization() {
TypeBuilder builder(types.size());
struct Copier {
Random& rand;
const std::vector<HeapType>& types;
TypeBuilder& builder;
std::unordered_map<HeapType, std::vector<Index>> typeIndices;
std::vector<Index> recGroupEnds;
Index index = 0;
Copier(Fuzzer& parent, TypeBuilder& builder)
: rand(parent.rand), types(parent.types), builder(builder) {
for (size_t i = 0; i < types.size(); ++i) {
typeIndices[types[i]].push_back(i);
}
const auto& subtypeIndices = parent.subtypeIndices;
for (size_t super = 0; super < subtypeIndices.size(); ++super) {
for (auto sub : subtypeIndices[super]) {
if (sub != super) {
builder[sub].subTypeOf(builder[super]);
}
}
}
for (size_t i = 0; i < types.size(); ++i) {
builder[i].setOpen(types[i].isOpen());
}
recGroupEnds.reserve(builder.size());
std::optional<RecGroup> currGroup;
size_t currGroupStart = 0;
auto finishGroup = [&](Index end) {
builder.createRecGroup(currGroupStart, end - currGroupStart);
for (Index i = currGroupStart; i < end; ++i) {
recGroupEnds.push_back(end);
}
currGroupStart = end;
};
for (Index i = 0; i < types.size(); ++i) {
auto newGroup = types[i].getRecGroup();
if (!currGroup || newGroup != currGroup ||
types[i] == types[currGroupStart]) {
finishGroup(i);
currGroup = newGroup;
}
}
finishGroup(builder.size());
for (; index < types.size(); ++index) {
auto type = types[index];
if (type.isSignature()) {
builder[index] = getSignature(type.getSignature());
} else if (type.isStruct()) {
builder[index] = getStruct(type.getStruct());
} else if (type.isArray()) {
builder[index] = getArray(type.getArray());
} else {
WASM_UNREACHABLE("unexpected type kind");
}
}
}
struct NewHeapType : HeapType {};
struct OldHeapType : HeapType {};
struct CopiedHeapType {
std::variant<NewHeapType, OldHeapType> type;
NewHeapType* getNew() { return std::get_if<NewHeapType>(&type); }
OldHeapType* getOld() { return std::get_if<OldHeapType>(&type); }
HeapType get() {
return getNew() ? HeapType(*getNew()) : HeapType(*getOld());
}
};
struct NewType : Type {};
struct OldType : Type {};
struct CopiedType {
std::variant<NewType, OldType> type;
NewType* getNew() { return std::get_if<NewType>(&type); }
OldType* getOld() { return std::get_if<OldType>(&type); }
Type get() { return getNew() ? Type(*getNew()) : Type(*getOld()); }
};
CopiedHeapType getChildHeapType(HeapType old) {
auto it = typeIndices.find(old);
if (it == typeIndices.end()) {
assert(old.isBasic());
return {OldHeapType{old}};
}
assert(!old.isBasic());
auto group = old.getRecGroup();
if (group == types[index].getRecGroup()) {
std::optional<Index> i;
for (auto candidate : it->second) {
if (candidate >= recGroupEnds[index]) {
break;
}
i = candidate;
}
return {NewHeapType{builder[*i]}};
}
if (rand.oneIn(2)) {
std::vector<Index> candidateIndices;
for (auto i : it->second) {
if (i < recGroupEnds[index]) {
candidateIndices.push_back(i);
}
}
assert(!candidateIndices.empty());
Index i = rand.pick(candidateIndices);
return {NewHeapType{builder[i]}};
} else {
Index i = rand.pick(it->second);
return {OldHeapType{types[i]}};
}
}
CopiedType getTuple(Type old) {
TypeList types;
types.reserve(old.size());
bool hasTempChild = false;
for (auto type : old) {
auto copied = getType(type);
if (copied.getNew()) {
hasTempChild = true;
}
types.push_back(copied.get());
}
if (hasTempChild || rand.oneIn(2)) {
return {NewType{builder.getTempTupleType(types)}};
} else {
return {OldType{Tuple(std::move(types))}};
}
}
CopiedType getRef(Type old) {
auto copied = getChildHeapType(old.getHeapType());
auto type = copied.get();
auto nullability = old.getNullability();
if (copied.getNew()) {
return {NewType{builder.getTempRefType(type, nullability)}};
} else {
if (rand.oneIn(2)) {
return {NewType{builder.getTempRefType(type, nullability)}};
} else {
return {OldType{Type(type, nullability)}};
}
}
}
CopiedType getType(Type old) {
if (old.isTuple()) {
return getTuple(old);
} else if (old.isRef()) {
return getRef(old);
} else {
assert(old.isBasic());
return {OldType{old}};
}
}
Field getField(Field old) {
old.type = getType(old.type).get();
return old;
}
Signature getSignature(Signature old) {
return {getType(old.params).get(), getType(old.results).get()};
}
Struct getStruct(const Struct& old) {
FieldList fields;
fields.reserve(old.fields.size());
for (const auto& field : old.fields) {
fields.push_back(getField(field));
}
return {std::move(fields)};
}
Array getArray(Array old) {
old.element = getField(old.element);
return old;
}
};
Copier{*this, builder};
auto result = builder.build();
if (auto* error = result.getError()) {
IndexedTypeNameGenerator print(types);
Fatal() << "Failed to build copies of the types: " << error->reason
<< " at index " << error->index;
}
auto newTypes = *result;
assert(types.size() == newTypes.size());
for (size_t i = 0; i < types.size(); ++i) {
if (types[i] != newTypes[i]) {
IndexedTypeNameGenerator print(types);
std::cerr << "\n";
for (size_t j = 0; j < newTypes.size(); ++j) {
std::cerr << j << ": " << print(newTypes[j]) << "\n";
}
Fatal() << "Copy of type at index " << i << " is distinct:\n"
<< "original: " << print(types[i]) << '\n'
<< "copy: " << print(newTypes[i]);
}
}
}
void Fuzzer::checkInhabitable() {
std::vector<HeapType> inhabitable = HeapTypeGenerator::makeInhabitable(types);
if (verbose) {
std::cout << "\nInhabitable types:\n\n";
printTypes(inhabitable);
}
bool haveUninhabitable =
HeapTypeGenerator::getInhabitable(types).size() != types.size();
if (haveUninhabitable) {
auto verifiedInhabitable = HeapTypeGenerator::getInhabitable(inhabitable);
if (verifiedInhabitable.size() != inhabitable.size()) {
IndexedTypeNameGenerator print(inhabitable);
for (size_t i = 0; i < inhabitable.size(); ++i) {
if (i > verifiedInhabitable.size() ||
inhabitable[i] != verifiedInhabitable[i]) {
Fatal() << "Found uninhabitable type: " << print(inhabitable[i]);
}
}
}
} else {
if (types.size() != inhabitable.size()) {
Fatal() << "Number of inhabitable types does not match number of "
"original types";
}
for (size_t i = 0; i < types.size(); ++i) {
if (types[i] != inhabitable[i]) {
IndexedTypeNameGenerator print(types);
Fatal() << "makeInhabitable incorrectly changed type "
<< print(types[i]);
}
}
}
}
}
int main(int argc, const char* argv[]) {
using namespace wasm;
const std::string WasmFuzzTypesOption = "wasm-fuzz-types options";
Options options("wasm-fuzz-types",
"Fuzz type construction, canonicalization, and operations");
std::optional<uint64_t> seed;
options.add("--seed",
"",
"Run a single workload generated by the given seed",
WasmFuzzTypesOption,
Options::Arguments::One,
[&](Options*, const std::string& arg) {
seed = uint64_t(std::stoull(arg));
});
bool verbose = false;
options.add("--verbose",
"-v",
"Print extra information",
WasmFuzzTypesOption,
Options::Arguments::Zero,
[&](Options*, const std::string& arg) { verbose = true; });
options.parse(argc, argv);
Fuzzer fuzzer{verbose};
if (seed) {
fuzzer.run(*seed);
} else {
size_t i = 0;
RandEngine nextSeed(getSeed());
while (true) {
std::cout << "Iteration " << ++i << "\n";
fuzzer.run(nextSeed());
}
}
return 0;
}