#include "tools/fuzzing.h"
#include "ir/gc-type-utils.h"
#include "ir/local-structural-dominance.h"
#include "ir/module-utils.h"
#include "ir/subtypes.h"
#include "ir/type-updating.h"
#include "tools/fuzzing/heap-types.h"
#include "tools/fuzzing/parameters.h"
namespace wasm {
namespace {
}
TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm,
std::vector<char>&& input)
: wasm(wasm), builder(wasm), random(std::move(input), wasm.features) {
allowAddingUnreachableCode = oneIn(2);
loggableTypes = {Type::i32, Type::i64, Type::f32, Type::f64};
if (wasm.features.hasSIMD()) {
loggableTypes.push_back(Type::v128);
}
}
TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm,
std::string& filename)
: TranslateToFuzzReader(
wasm, read_file<std::vector<char>>(filename, Flags::Binary)) {}
void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) {
while (options.passes.size() < 20 && !random.finished() && !oneIn(3)) {
switch (upTo(32)) {
case 0:
case 1:
case 2:
case 3:
case 4: {
options.passes.push_back("O");
options.passOptions.optimizeLevel = upTo(4);
options.passOptions.shrinkLevel = upTo(4);
break;
}
case 5:
options.passes.push_back("coalesce-locals");
break;
case 6:
options.passes.push_back("code-pushing");
break;
case 7:
options.passes.push_back("code-folding");
break;
case 8:
options.passes.push_back("dce");
break;
case 9:
options.passes.push_back("duplicate-function-elimination");
break;
case 10:
options.passes.push_back("flatten");
break;
case 11:
options.passes.push_back("inlining");
break;
case 12:
options.passes.push_back("inlining-optimizing");
break;
case 13:
options.passes.push_back("local-cse");
break;
case 14:
options.passes.push_back("memory-packing");
break;
case 15:
options.passes.push_back("merge-blocks");
break;
case 16:
options.passes.push_back("optimize-instructions");
break;
case 17:
options.passes.push_back("pick-load-signs");
break;
case 18:
options.passes.push_back("precompute");
break;
case 19:
options.passes.push_back("precompute-propagate");
break;
case 20:
options.passes.push_back("remove-unused-brs");
break;
case 21:
options.passes.push_back("remove-unused-module-elements");
break;
case 22:
options.passes.push_back("remove-unused-names");
break;
case 23:
options.passes.push_back("reorder-functions");
break;
case 24:
options.passes.push_back("reorder-locals");
break;
case 25: {
options.passes.push_back("flatten");
options.passes.push_back("rereloop");
break;
}
case 26:
options.passes.push_back("simplify-locals");
break;
case 27:
options.passes.push_back("simplify-locals-notee");
break;
case 28:
options.passes.push_back("simplify-locals-nostructure");
break;
case 29:
options.passes.push_back("simplify-locals-notee-nostructure");
break;
case 30:
options.passes.push_back("ssa");
break;
case 31:
options.passes.push_back("vacuum");
break;
default:
WASM_UNREACHABLE("unexpected value");
}
}
if (oneIn(2)) {
options.passOptions.optimizeLevel = upTo(4);
}
if (oneIn(2)) {
options.passOptions.shrinkLevel = upTo(4);
}
std::cout << "opt level: " << options.passOptions.optimizeLevel << '\n';
std::cout << "shrink level: " << options.passOptions.shrinkLevel << '\n';
}
void TranslateToFuzzReader::build() {
if (HANG_LIMIT > 0) {
prepareHangLimitSupport();
}
if (allowMemory) {
setupMemory();
}
setupHeapTypes();
setupTables();
setupGlobals();
if (wasm.features.hasExceptionHandling()) {
setupTags();
}
modifyInitialFunctions();
addImportLoggingSupport();
while (!random.finished()) {
auto* func = addFunction();
addInvocations(func);
}
if (HANG_LIMIT > 0) {
addHangLimitSupport();
}
if (allowMemory) {
finalizeMemory();
addHashMemorySupport();
}
finalizeTable();
}
void TranslateToFuzzReader::setupMemory() {
MemoryUtils::ensureExists(&wasm);
auto& memory = wasm.memories[0];
if (wasm.features.hasBulkMemory()) {
size_t memCovered = 0;
size_t numSegments = upTo(8) + 1;
for (size_t i = 0; i < numSegments; i++) {
auto segment = builder.makeDataSegment();
segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(i)),
false);
segment->isPassive = bool(upTo(2));
size_t segSize = upTo(USABLE_MEMORY * 2);
segment->data.resize(segSize);
for (size_t j = 0; j < segSize; j++) {
segment->data[j] = upTo(512);
}
if (!segment->isPassive) {
segment->offset = builder.makeConst(int32_t(memCovered));
memCovered += segSize;
segment->memory = memory->name;
}
wasm.addDataSegment(std::move(segment));
}
} else {
auto segment = builder.makeDataSegment();
segment->memory = memory->name;
segment->offset = builder.makeConst(int32_t(0));
segment->setName(Name::fromInt(0), false);
wasm.dataSegments.push_back(std::move(segment));
auto num = upTo(USABLE_MEMORY * 2);
for (size_t i = 0; i < num; i++) {
auto value = upTo(512);
wasm.dataSegments[0]->data.push_back(value >= 256 ? 0 : (value & 0xff));
}
}
}
void TranslateToFuzzReader::setupHeapTypes() {
auto possibleHeapTypes = ModuleUtils::collectHeapTypes(wasm);
interestingHeapTypes = HeapTypeGenerator::getInhabitable(possibleHeapTypes);
if (wasm.features.hasGC()) {
auto generator =
HeapTypeGenerator::create(random, wasm.features, upTo(MAX_NEW_GC_TYPES));
auto result = generator.builder.build();
if (auto* err = result.getError()) {
Fatal() << "Failed to build heap types: " << err->reason << " at index "
<< err->index;
}
auto inhabitable = HeapTypeGenerator::makeInhabitable(*result);
for (auto type : inhabitable) {
if (!type.isBottom() && !type.isBasic()) {
interestingHeapTypes.push_back(type);
if (oneIn(2)) {
wasm.typeNames[type].name =
"generated_type$" + std::to_string(interestingHeapTypes.size());
}
}
}
}
SubTypes subTypes(interestingHeapTypes);
for (auto type : interestingHeapTypes) {
for (auto subType : subTypes.getImmediateSubTypes(type)) {
interestingHeapSubTypes[type].push_back(subType);
}
if (type.isStruct()) {
interestingHeapSubTypes[HeapType::struct_].push_back(type);
interestingHeapSubTypes[HeapType::eq].push_back(type);
interestingHeapSubTypes[HeapType::any].push_back(type);
auto& fields = type.getStruct().fields;
for (Index i = 0; i < fields.size(); i++) {
if (fields[i].mutable_) {
mutableStructFields.push_back(StructField{type, i});
}
}
} else if (type.isArray()) {
interestingHeapSubTypes[HeapType::array].push_back(type);
interestingHeapSubTypes[HeapType::eq].push_back(type);
interestingHeapSubTypes[HeapType::any].push_back(type);
if (type.getArray().element.mutable_) {
mutableArrays.push_back(type);
}
} else if (type.isSignature()) {
interestingHeapSubTypes[HeapType::func].push_back(type);
}
}
for (auto type : interestingHeapTypes) {
if (type.isStruct()) {
auto& fields = type.getStruct().fields;
for (Index i = 0; i < fields.size(); i++) {
typeStructFields[fields[i].type].push_back(StructField{type, i});
}
} else if (type.isArray()) {
typeArrays[type.getArray().element.type].push_back(type);
}
}
}
void TranslateToFuzzReader::setupTables() {
Table* table = nullptr;
Type funcref = Type(HeapType::func, Nullable);
auto iter = std::find_if(wasm.tables.begin(),
wasm.tables.end(),
[&](auto& table) { return table->type == funcref; });
if (iter != wasm.tables.end()) {
table = iter->get();
} else {
auto tablePtr = builder.makeTable(
Names::getValidTableName(wasm, "fuzzing_table"), funcref, 0, 0);
tablePtr->hasExplicitName = true;
table = wasm.addTable(std::move(tablePtr));
}
funcrefTableName = table->name;
bool hasFuncrefElemSegment =
std::any_of(wasm.elementSegments.begin(),
wasm.elementSegments.end(),
[&](auto& segment) {
return segment->table.is() && segment->type == funcref;
});
if (!hasFuncrefElemSegment) {
auto segment = std::make_unique<ElementSegment>(
table->name, builder.makeConst(int32_t(0)));
segment->setName(Names::getValidElementSegmentName(wasm, "elem$"), false);
wasm.addElementSegment(std::move(segment));
}
}
void TranslateToFuzzReader::setupGlobals() {
for (auto& global : wasm.globals) {
if (global->imported()) {
global->module = global->base = Name();
global->init = makeConst(global->type);
} else {
if (!wasm.features.hasGC() &&
!FindAll<GlobalGet>(global->init).list.empty()) {
global->init = makeConst(global->type);
}
}
}
auto numInitialGlobals = wasm.globals.size();
unsigned percentIgnoredInitialGlobals = 0;
if (numInitialGlobals) {
percentIgnoredInitialGlobals = upTo(100);
}
for (size_t index = upTo(MAX_GLOBALS); index > 0; --index) {
auto type = getConcreteType();
auto* init = makeConst(type);
if (!FindAll<RefAs>(init).list.empty()) {
type = getMVPType();
init = makeConst(type);
}
auto mutability = oneIn(2) ? Builder::Mutable : Builder::Immutable;
auto global = builder.makeGlobal(
Names::getValidGlobalName(wasm, "global$"), type, init, mutability);
wasm.addGlobal(std::move(global));
}
for (Index i = 0; i < wasm.globals.size(); i++) {
auto& global = wasm.globals[i];
if (i < numInitialGlobals && upTo(100) < percentIgnoredInitialGlobals) {
continue;
}
globalsByType[global->type].push_back(global->name);
if (global->mutable_) {
mutableGlobalsByType[global->type].push_back(global->name);
}
}
}
void TranslateToFuzzReader::setupTags() {
for (auto& tag : wasm.tags) {
if (tag->imported()) {
tag->module = tag->base = Name();
}
}
Index num = upTo(3);
for (size_t i = 0; i < num; i++) {
addTag();
}
}
void TranslateToFuzzReader::addTag() {
auto tag = builder.makeTag(Names::getValidTagName(wasm, "tag$"),
Signature(getControlFlowType(), Type::none));
wasm.addTag(std::move(tag));
}
void TranslateToFuzzReader::finalizeMemory() {
auto& memory = wasm.memories[0];
for (auto& segment : wasm.dataSegments) {
Address maxOffset = segment->data.size();
if (!segment->isPassive) {
if (!wasm.features.hasGC()) {
for (auto* get : FindAll<GlobalGet>(segment->offset).list) {
assert(!wasm.getGlobal(get->name)->imported());
segment->offset =
builder.makeConst(Literal::makeFromInt32(0, Type::i32));
}
}
if (auto* offset = segment->offset->dynCast<Const>()) {
maxOffset = maxOffset + offset->value.getInteger();
}
}
memory->initial = std::max(
memory->initial,
Address((maxOffset + Memory::kPageSize - 1) / Memory::kPageSize));
}
memory->initial = std::max(memory->initial, USABLE_MEMORY);
if (memory->max == Memory::kUnlimitedSize) {
memory->max = memory->initial;
}
if (memory->max <= memory->initial) {
memory->max =
std::min(Address(memory->initial + 1), Address(Memory::kMaxSize32));
}
for (auto& memory : wasm.memories) {
memory->module = memory->base = Name();
}
}
void TranslateToFuzzReader::finalizeTable() {
for (auto& table : wasm.tables) {
ModuleUtils::iterTableSegments(
wasm, table->name, [&](ElementSegment* segment) {
if (!wasm.features.hasGC()) {
for (auto* get : FindAll<GlobalGet>(segment->offset).list) {
assert(!wasm.getGlobal(get->name)->imported());
segment->offset =
builder.makeConst(Literal::makeFromInt32(0, Type::i32));
}
}
Address maxOffset = segment->data.size();
if (auto* offset = segment->offset->dynCast<Const>()) {
maxOffset = maxOffset + offset->value.getInteger();
}
table->initial = std::max(table->initial, maxOffset);
});
const Address ReasonableMaxTableSize = 10000;
table->initial = std::min(table->initial, ReasonableMaxTableSize);
assert(ReasonableMaxTableSize <= Table::kMaxSize);
table->max = oneIn(2) ? Address(Table::kUnlimitedSize) : table->initial;
table->module = table->base = Name();
}
}
void TranslateToFuzzReader::prepareHangLimitSupport() {
HANG_LIMIT_GLOBAL = Names::getValidGlobalName(wasm, "hangLimit");
}
void TranslateToFuzzReader::addHangLimitSupport() {
auto glob = builder.makeGlobal(HANG_LIMIT_GLOBAL,
Type::i32,
builder.makeConst(int32_t(HANG_LIMIT)),
Builder::Mutable);
wasm.addGlobal(std::move(glob));
}
void TranslateToFuzzReader::addImportLoggingSupport() {
for (auto type : loggableTypes) {
auto* func = new Function;
Name name = std::string("log-") + type.toString();
func->name = name;
func->module = "fuzzing-support";
func->base = name;
func->type = Signature(type, Type::none);
wasm.addFunction(func);
}
}
void TranslateToFuzzReader::addHashMemorySupport() {
std::vector<Expression*> contents;
contents.push_back(
builder.makeLocalSet(0, builder.makeConst(uint32_t(5381))));
auto zero = Literal::makeFromInt32(0, wasm.memories[0]->indexType);
for (Index i = 0; i < USABLE_MEMORY; i++) {
contents.push_back(builder.makeLocalSet(
0,
builder.makeBinary(
XorInt32,
builder.makeBinary(
AddInt32,
builder.makeBinary(ShlInt32,
builder.makeLocalGet(0, Type::i32),
builder.makeConst(uint32_t(5))),
builder.makeLocalGet(0, Type::i32)),
builder.makeLoad(1,
false,
i,
1,
builder.makeConst(zero),
Type::i32,
wasm.memories[0]->name))));
}
contents.push_back(builder.makeLocalGet(0, Type::i32));
auto* body = builder.makeBlock(contents);
auto* hasher = wasm.addFunction(builder.makeFunction(
"hashMemory", Signature(Type::none, Type::i32), {Type::i32}, body));
wasm.addExport(
builder.makeExport(hasher->name, hasher->name, ExternalKind::Function));
if (!wasm.getExportOrNull("memory")) {
wasm.addExport(builder.makeExport(
"memory", wasm.memories[0]->name, ExternalKind::Memory));
}
}
TranslateToFuzzReader::FunctionCreationContext::~FunctionCreationContext() {
LocalStructuralDominance info(
func, parent.wasm, LocalStructuralDominance::NonNullableOnly);
for (auto index : info.nonDominatingIndices) {
if (!parent.oneIn(5)) {
auto* value = parent.makeTrivial(func->getLocalType(index));
func->body = parent.builder.makeSequence(
parent.builder.makeLocalSet(index, value), func->body);
}
}
TypeUpdating::handleNonDefaultableLocals(func, parent.wasm);
if (HANG_LIMIT > 0) {
parent.addHangLimitChecks(func);
}
assert(breakableStack.empty());
assert(hangStack.empty());
parent.funcContext = nullptr;
}
Expression* TranslateToFuzzReader::makeHangLimitCheck() {
return builder.makeSequence(
builder.makeIf(
builder.makeUnary(UnaryOp::EqZInt32,
builder.makeGlobalGet(HANG_LIMIT_GLOBAL, Type::i32)),
builder.makeSequence(
builder.makeGlobalSet(HANG_LIMIT_GLOBAL,
builder.makeConst(int32_t(HANG_LIMIT))),
builder.makeUnreachable())),
builder.makeGlobalSet(
HANG_LIMIT_GLOBAL,
builder.makeBinary(BinaryOp::SubInt32,
builder.makeGlobalGet(HANG_LIMIT_GLOBAL, Type::i32),
builder.makeConst(int32_t(1)))));
}
Expression* TranslateToFuzzReader::makeLogging() {
auto type = getLoggableType();
return builder.makeCall(
std::string("log-") + type.toString(), {make(type)}, Type::none);
}
Expression* TranslateToFuzzReader::makeMemoryHashLogging() {
auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32);
return builder.makeCall(std::string("log-i32"), {hash}, Type::none);
}
Function* TranslateToFuzzReader::addFunction() {
LOGGING_PERCENT = upToSquared(100);
auto* func = new Function;
func->name = Names::getValidFunctionName(wasm, "func");
FunctionCreationContext context(*this, func);
assert(funcContext->typeLocals.empty());
Index numParams = upToSquared(MAX_PARAMS);
std::vector<Type> params;
params.reserve(numParams);
for (Index i = 0; i < numParams; i++) {
auto type = getSingleConcreteType();
funcContext->typeLocals[type].push_back(params.size());
params.push_back(type);
}
auto paramType = Type(params);
auto resultType = getControlFlowType();
func->type = Signature(paramType, resultType);
Index numVars = upToSquared(MAX_VARS);
for (Index i = 0; i < numVars; i++) {
auto type = getConcreteType();
funcContext->typeLocals[type].push_back(params.size() + func->vars.size());
func->vars.push_back(type);
}
auto bodyType = func->getResults();
if (oneIn(10)) {
bodyType = Type::unreachable;
}
if (oneIn(2)) {
func->body = makeBlock(bodyType);
} else {
func->body = make(bodyType);
}
if (allowOOB) {
recombine(func);
mutate(func);
fixAfterChanges(func);
}
wasm.addFunction(func);
auto validExportType = [](Type t) {
if (!t.isRef()) {
return true;
}
auto heapType = t.getHeapType();
return heapType == HeapType::ext || heapType == HeapType::func ||
heapType == HeapType::string;
};
bool validExportParams =
std::all_of(paramType.begin(), paramType.end(), [&](Type t) {
return validExportType(t) && t.isDefaultable();
});
bool validExportResults =
std::all_of(resultType.begin(), resultType.end(), validExportType);
if (validExportParams && validExportResults &&
(numAddedFunctions == 0 || oneIn(2)) &&
!wasm.getExportOrNull(func->name)) {
auto* export_ = new Export;
export_->name = func->name;
export_->value = func->name;
export_->kind = ExternalKind::Function;
wasm.addExport(export_);
}
while (oneIn(3) && !random.finished()) {
auto type = Type(func->type, NonNullable);
std::vector<ElementSegment*> compatibleSegments;
ModuleUtils::iterActiveElementSegments(wasm, [&](ElementSegment* segment) {
if (Type::isSubType(type, segment->type)) {
compatibleSegments.push_back(segment);
}
});
auto& randomElem = compatibleSegments[upTo(compatibleSegments.size())];
randomElem->data.push_back(builder.makeRefFunc(func->name, func->type));
}
numAddedFunctions++;
return func;
}
void TranslateToFuzzReader::addHangLimitChecks(Function* func) {
for (auto* loop : FindAll<Loop>(func->body).list) {
loop->body =
builder.makeSequence(makeHangLimitCheck(), loop->body, loop->type);
}
func->body =
builder.makeSequence(makeHangLimitCheck(), func->body, func->getResults());
for (auto* arrayNew : FindAll<ArrayNew>(func->body).list) {
if (!oneIn(100)) {
arrayNew->size = builder.makeBinary(
AndInt32, arrayNew->size, builder.makeConst(int32_t(1024 - 1)));
}
}
}
void TranslateToFuzzReader::recombine(Function* func) {
if (oneIn(2)) {
return;
}
struct Scanner
: public PostWalker<Scanner, UnifiedExpressionVisitor<Scanner>> {
TranslateToFuzzReader& parent;
InsertOrderedMap<Type, std::vector<Expression*>> exprsByType;
Scanner(TranslateToFuzzReader& parent) : parent(parent) {}
void visitExpression(Expression* curr) {
if (parent.canBeArbitrarilyReplaced(curr)) {
for (auto type : getRelevantTypes(curr->type)) {
exprsByType[type].push_back(curr);
}
}
}
std::vector<Type> getRelevantTypes(Type type) {
if (!type.isRef()) {
return {type};
}
std::vector<Type> ret;
auto heapType = type.getHeapType();
auto nullability = type.getNullability();
if (nullability == NonNullable) {
ret = getRelevantTypes(Type(heapType, Nullable));
}
while (1) {
ret.push_back(Type(heapType, nullability));
auto super = heapType.getSuperType();
if (!super) {
break;
}
heapType = *super;
}
return ret;
}
};
Scanner scanner(*this);
scanner.walk(func->body);
for (auto& pair : scanner.exprsByType) {
if (oneIn(2)) {
continue;
}
auto& list = pair.second;
std::vector<Expression*> trimmed;
size_t num = upToSquared(list.size());
for (size_t i = 0; i < num; i++) {
trimmed.push_back(pick(list));
}
if (trimmed.empty()) {
trimmed.push_back(pick(list));
}
list.swap(trimmed);
}
for (auto& pair : scanner.exprsByType) {
for (auto*& item : pair.second) {
item = ExpressionManipulator::copy(item, wasm);
}
}
struct Modder : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> {
Module& wasm;
Scanner& scanner;
TranslateToFuzzReader& parent;
Modder(Module& wasm, Scanner& scanner, TranslateToFuzzReader& parent)
: wasm(wasm), scanner(scanner), parent(parent) {}
void visitExpression(Expression* curr) {
if (parent.oneIn(10) && parent.canBeArbitrarilyReplaced(curr)) {
auto& candidates = scanner.exprsByType[curr->type];
assert(!candidates.empty()); auto* rep = parent.pick(candidates);
replaceCurrent(ExpressionManipulator::copy(rep, wasm));
}
}
};
Modder modder(wasm, scanner, *this);
modder.walk(func->body);
}
void TranslateToFuzzReader::mutate(Function* func) {
auto r = upTo(200);
if (r > 100) {
return;
}
double t = r;
t = t / 100;
t = pow(t, 9);
Index percentChance = t * 100;
percentChance = std::max(percentChance, Index(3));
struct Modder : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> {
Module& wasm;
TranslateToFuzzReader& parent;
Index percentChance;
bool allowUnreachable;
Modder(Module& wasm, TranslateToFuzzReader& parent, Index percentChance)
: wasm(wasm), parent(parent), percentChance(percentChance) {
allowUnreachable = parent.allowAddingUnreachableCode && parent.oneIn(2);
}
void visitExpression(Expression* curr) {
if (parent.upTo(100) < percentChance &&
parent.canBeArbitrarilyReplaced(curr)) {
if (allowUnreachable && parent.oneIn(20)) {
replaceCurrent(parent.make(Type::unreachable));
return;
}
if (auto* c = curr->dynCast<Const>()) {
if (parent.oneIn(2)) {
c->value = parent.tweak(c->value);
return;
}
}
replaceCurrent(parent.make(curr->type));
}
}
};
Modder modder(wasm, *this, percentChance);
modder.walk(func->body);
}
void TranslateToFuzzReader::fixAfterChanges(Function* func) {
struct Fixer
: public ControlFlowWalker<Fixer, UnifiedExpressionVisitor<Fixer>> {
Module& wasm;
TranslateToFuzzReader& parent;
Fixer(Module& wasm, TranslateToFuzzReader& parent)
: wasm(wasm), parent(parent) {}
std::set<Name> seen;
void visitExpression(Expression* curr) {
BranchUtils::operateOnScopeNameDefs(curr, [&](Name& name) {
if (name.is()) {
if (seen.count(name)) {
replace();
} else {
seen.insert(name);
}
}
});
BranchUtils::operateOnScopeNameUses(curr, [&](Name& name) {
if (name.is()) {
replaceIfInvalid(name);
}
});
}
void replaceIfInvalid(Name target) {
if (!hasBreakTarget(target)) {
replace();
}
}
void replace() { replaceCurrent(parent.makeTrivial(getCurrent()->type)); }
bool hasBreakTarget(Name name) {
if (controlFlowStack.empty()) {
return false;
}
Index i = controlFlowStack.size() - 1;
while (1) {
auto* curr = controlFlowStack[i];
bool has = false;
BranchUtils::operateOnScopeNameDefs(curr, [&](Name& def) {
if (def == name) {
has = true;
}
});
if (has) {
return true;
}
if (i == 0) {
return false;
}
i--;
}
}
};
Fixer fixer(wasm, *this);
fixer.walk(func->body);
ReFinalize().walkFunctionInModule(func, &wasm);
}
void TranslateToFuzzReader::modifyInitialFunctions() {
if (wasm.functions.empty()) {
return;
}
const int RESOLUTION = 10;
auto chance = upTo(RESOLUTION + 1);
for (Index i = 0; i < wasm.functions.size(); i++) {
auto* func = wasm.functions[i].get();
FunctionCreationContext context(*this, func);
if (func->imported()) {
func->module = func->base = Name();
func->body = make(func->getResults());
}
if (upTo(RESOLUTION) >= chance) {
dropToLog(func);
recombine(func);
mutate(func);
fixAfterChanges(func);
}
}
wasm.start = Name();
}
void TranslateToFuzzReader::dropToLog(Function* func) {
if (oneIn(2)) {
return;
}
struct Modder : public PostWalker<Modder> {
Module& wasm;
TranslateToFuzzReader& parent;
Modder(Module& wasm, TranslateToFuzzReader& parent)
: wasm(wasm), parent(parent) {}
void visitDrop(Drop* curr) {
if (parent.isLoggableType(curr->value->type) && parent.oneIn(2)) {
replaceCurrent(parent.builder.makeCall(std::string("log-") +
curr->value->type.toString(),
{curr->value},
Type::none));
}
}
};
Modder modder(wasm, *this);
modder.walk(func->body);
}
void TranslateToFuzzReader::addInvocations(Function* func) {
Name name = func->name.toString() + std::string("_invoker");
if (wasm.getFunctionOrNull(name) || wasm.getExportOrNull(name)) {
return;
}
auto invoker = builder.makeFunction(name, Signature(), {});
Block* body = builder.makeBlock();
invoker->body = body;
FunctionCreationContext context(*this, invoker.get());
std::vector<Expression*> invocations;
while (oneIn(2) && !random.finished()) {
std::vector<Expression*> args;
for (const auto& type : func->getParams()) {
args.push_back(makeConst(type));
}
Expression* invoke = builder.makeCall(func->name, args, func->getResults());
if (func->getResults().isConcrete()) {
invoke = builder.makeDrop(invoke);
}
invocations.push_back(invoke);
if (oneIn(2)) {
invocations.push_back(makeMemoryHashLogging());
}
}
if (invocations.empty()) {
return;
}
body->list.set(invocations);
wasm.addFunction(std::move(invoker));
wasm.addExport(builder.makeExport(name, name, ExternalKind::Function));
}
Expression* TranslateToFuzzReader::make(Type type) {
type = getSubType(type);
if (trivialNesting) {
return makeTrivial(type);
}
if (random.finished() || nesting >= 5 * NESTING_LIMIT || (nesting >= NESTING_LIMIT && !oneIn(3))) {
if (type.isConcrete()) {
if (oneIn(2)) {
return makeConst(type);
} else {
return makeLocalGet(type);
}
} else if (type == Type::none) {
if (oneIn(2)) {
return makeNop(type);
} else {
return makeLocalSet(type);
}
}
assert(type == Type::unreachable);
return makeTrivial(type);
}
nesting++;
Expression* ret = nullptr;
if (type.isConcrete()) {
ret = _makeConcrete(type);
} else if (type == Type::none) {
ret = _makenone();
} else {
assert(type == Type::unreachable);
ret = _makeunreachable();
}
assert(Type::isSubType(ret->type, type));
nesting--;
return ret;
}
Expression* TranslateToFuzzReader::_makeConcrete(Type type) {
bool canMakeControlFlow = !type.isTuple() || wasm.features.hasMultivalue();
using Self = TranslateToFuzzReader;
FeatureOptions<Expression* (Self::*)(Type)> options;
using WeightedOption = decltype(options)::WeightedOption;
options.add(FeatureSet::MVP,
WeightedOption{&Self::makeLocalGet, VeryImportant},
WeightedOption{&Self::makeLocalSet, VeryImportant},
WeightedOption{&Self::makeGlobalGet, Important},
WeightedOption{&Self::makeConst, Important});
if (canMakeControlFlow) {
options
.add(FeatureSet::MVP,
WeightedOption{&Self::makeBlock, Important},
WeightedOption{&Self::makeIf, Important},
WeightedOption{&Self::makeLoop, Important},
WeightedOption{&Self::makeBreak, Important},
&Self::makeCall,
&Self::makeCallIndirect)
.add(FeatureSet::ExceptionHandling, &Self::makeTry)
.add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef);
}
if (type.isSingle()) {
options
.add(FeatureSet::MVP,
WeightedOption{&Self::makeUnary, Important},
WeightedOption{&Self::makeBinary, Important},
&Self::makeSelect)
.add(FeatureSet::Multivalue, &Self::makeTupleExtract);
}
if (type.isSingle() && !type.isRef()) {
options.add(FeatureSet::MVP, {&Self::makeLoad, Important});
options.add(FeatureSet::SIMD, &Self::makeSIMD);
}
if (type.isInteger()) {
options.add(FeatureSet::Atomics, &Self::makeAtomic);
}
if (type == Type::i32) {
options.add(FeatureSet::ReferenceTypes, &Self::makeRefIsNull);
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeRefEq,
&Self::makeRefTest,
&Self::makeI31Get);
}
if (type.isTuple()) {
options.add(FeatureSet::Multivalue, &Self::makeTupleMake);
}
if (type.isRef()) {
auto heapType = type.getHeapType();
if (heapType.isBasic()) {
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeBasicRef);
} else {
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeCompoundRef);
}
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeRefCast);
}
if (wasm.features.hasGC()) {
if (typeStructFields.find(type) != typeStructFields.end()) {
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeStructGet);
}
if (typeArrays.find(type) != typeArrays.end()) {
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeArrayGet);
}
}
return (this->*pick(options))(type);
}
Expression* TranslateToFuzzReader::_makenone() {
auto choice = upTo(100);
if (choice < LOGGING_PERCENT) {
if (choice < LOGGING_PERCENT / 2) {
return makeLogging();
} else {
return makeMemoryHashLogging();
}
}
using Self = TranslateToFuzzReader;
auto options = FeatureOptions<Expression* (Self::*)(Type)>();
using WeightedOption = decltype(options)::WeightedOption;
options
.add(FeatureSet::MVP,
WeightedOption{&Self::makeLocalSet, VeryImportant},
WeightedOption{&Self::makeBlock, Important},
WeightedOption{&Self::makeIf, Important},
WeightedOption{&Self::makeLoop, Important},
WeightedOption{&Self::makeBreak, Important},
WeightedOption{&Self::makeStore, Important},
&Self::makeCall,
&Self::makeCallIndirect,
&Self::makeDrop,
&Self::makeNop,
&Self::makeGlobalSet)
.add(FeatureSet::BulkMemory, &Self::makeBulkMemory)
.add(FeatureSet::Atomics, &Self::makeAtomic)
.add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef)
.add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeStructSet)
.add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeArraySet)
.add(FeatureSet::GC | FeatureSet::ReferenceTypes,
&Self::makeArrayBulkMemoryOp);
return (this->*pick(options))(Type::none);
}
Expression* TranslateToFuzzReader::_makeunreachable() {
using Self = TranslateToFuzzReader;
auto options = FeatureOptions<Expression* (Self::*)(Type)>();
using WeightedOption = decltype(options)::WeightedOption;
options
.add(FeatureSet::MVP,
WeightedOption{&Self::makeLocalSet, VeryImportant},
WeightedOption{&Self::makeBlock, Important},
WeightedOption{&Self::makeIf, Important},
WeightedOption{&Self::makeLoop, Important},
WeightedOption{&Self::makeBreak, Important},
WeightedOption{&Self::makeStore, Important},
WeightedOption{&Self::makeUnary, Important},
WeightedOption{&Self::makeBinary, Important},
WeightedOption{&Self::makeUnreachable, Important},
&Self::makeCall,
&Self::makeCallIndirect,
&Self::makeSelect,
&Self::makeSwitch,
&Self::makeDrop,
&Self::makeReturn)
.add(FeatureSet::ExceptionHandling, &Self::makeThrow)
.add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef);
return (this->*pick(options))(Type::unreachable);
}
Expression* TranslateToFuzzReader::makeTrivial(Type type) {
struct TrivialNester {
TranslateToFuzzReader& parent;
TrivialNester(TranslateToFuzzReader& parent) : parent(parent) {
parent.trivialNesting++;
}
~TrivialNester() { parent.trivialNesting--; }
} nester(*this);
if (type.isConcrete()) {
if (funcContext && oneIn(type.isNonNullable() ? 10 : 2)) {
return makeLocalGet(type);
} else {
return makeConst(type);
}
} else if (type == Type::none) {
return makeNop(type);
}
assert(type == Type::unreachable);
Expression* ret = nullptr;
if (funcContext->func->getResults().isConcrete()) {
ret = makeTrivial(funcContext->func->getResults());
}
return builder.makeReturn(ret);
}
Expression* TranslateToFuzzReader::makeBlock(Type type) {
auto* ret = builder.makeBlock();
ret->type = type; ret->name = makeLabel();
funcContext->breakableStack.push_back(ret);
Index num = upToSquared(BLOCK_FACTOR - 1); if (nesting >= NESTING_LIMIT / 2) {
num /= 2;
if (nesting >= NESTING_LIMIT && oneIn(2)) {
num /= 2;
}
}
if (num == 0 && !oneIn(10)) {
num++;
}
while (num > 0 && !random.finished()) {
ret->list.push_back(make(Type::none));
num--;
}
if (!random.finished() && type.isConcrete() && oneIn(2)) {
ret->list.push_back(makeBreak(Type::unreachable));
} else {
ret->list.push_back(make(type));
}
funcContext->breakableStack.pop_back();
if (type.isConcrete()) {
ret->finalize(type);
} else {
ret->finalize();
}
if (ret->type != type) {
assert(type == Type::unreachable && ret->type == Type::none);
return builder.makeSequence(ret, make(Type::unreachable));
}
return ret;
}
Expression* TranslateToFuzzReader::makeLoop(Type type) {
auto* ret = wasm.allocator.alloc<Loop>();
ret->type = type; ret->name = makeLabel();
funcContext->breakableStack.push_back(ret);
funcContext->hangStack.push_back(ret);
if (oneIn(2)) {
ret->body = makeMaybeBlock(type);
} else {
std::vector<Expression*> list;
list.push_back(makeMaybeBlock(Type::none)); list.push_back(builder.makeBreak(ret->name, nullptr, makeCondition()));
list.push_back(make(type)); ret->body = builder.makeBlock(list, type);
}
funcContext->breakableStack.pop_back();
funcContext->hangStack.pop_back();
ret->finalize(type);
return ret;
}
Expression* TranslateToFuzzReader::makeCondition() {
auto* ret = make(Type::i32);
if (oneIn(2)) {
ret = builder.makeUnary(UnaryOp::EqZInt32, ret);
}
return ret;
}
Expression* TranslateToFuzzReader::makeMaybeBlock(Type type) {
if (nesting >= NESTING_LIMIT || oneIn(3)) {
return make(type);
} else {
return makeBlock(type);
}
}
Expression* TranslateToFuzzReader::buildIf(const struct ThreeArgs& args,
Type type) {
return builder.makeIf(args.a, args.b, args.c, type);
}
Expression* TranslateToFuzzReader::makeIf(Type type) {
auto* condition = makeCondition();
funcContext->hangStack.push_back(nullptr);
Expression* ret;
if (type == Type::none && oneIn(2)) {
ret = buildIf({condition, makeMaybeBlock(type), nullptr}, type);
} else {
auto trueType = type;
auto falseType = type;
switch (upTo(20)) {
case 0:
trueType = Type::unreachable;
break;
case 1:
falseType = Type::unreachable;
break;
}
ret = buildIf(
{condition, makeMaybeBlock(trueType), makeMaybeBlock(falseType)}, type);
}
funcContext->hangStack.pop_back();
return ret;
}
Expression* TranslateToFuzzReader::makeTry(Type type) {
auto* body = make(type);
std::vector<Name> catchTags;
std::vector<Expression*> catchBodies;
auto numTags = upTo(MAX_TRY_CATCHES);
std::unordered_set<Tag*> usedTags;
for (Index i = 0; i < numTags; i++) {
if (wasm.tags.empty()) {
addTag();
}
auto* tag = pick(wasm.tags).get();
if (usedTags.count(tag)) {
continue;
}
usedTags.insert(tag);
catchTags.push_back(tag->name);
}
numTags = catchTags.size();
auto numCatches = numTags;
if (numTags == 0 || oneIn(2)) {
numCatches++;
}
for (Index i = 0; i < numCatches; i++) {
Expression* prefix = nullptr;
if (i < numTags) {
auto tagType = wasm.getTag(catchTags[i])->sig.params;
if (tagType != Type::none) {
auto* pop = builder.makePop(tagType);
auto index = builder.addVar(funcContext->func, tagType);
prefix = builder.makeLocalSet(index, pop);
}
}
auto* catchBody = make(type);
if (prefix) {
catchBody = builder.makeSequence(prefix, catchBody);
}
catchBodies.push_back(catchBody);
}
return builder.makeTry(body, catchTags, catchBodies);
}
Expression* TranslateToFuzzReader::makeBreak(Type type) {
if (funcContext->breakableStack.empty()) {
return makeTrivial(type);
}
Expression* condition = nullptr;
if (type != Type::unreachable) {
funcContext->hangStack.push_back(nullptr);
condition = makeCondition();
}
int tries = TRIES;
while (tries-- > 0) {
auto* target = pick(funcContext->breakableStack);
auto name = getTargetName(target);
auto valueType = getTargetType(target);
if (type.isConcrete()) {
if (valueType != type) {
continue;
}
auto* ret = builder.makeBreak(name, make(type), condition);
funcContext->hangStack.pop_back();
return ret;
} else if (type == Type::none) {
if (valueType != Type::none) {
continue;
}
auto* ret = builder.makeBreak(name, nullptr, condition);
funcContext->hangStack.pop_back();
return ret;
} else {
assert(type == Type::unreachable);
if (valueType != Type::none) {
continue;
}
size_t conditions = 0;
int i = funcContext->hangStack.size();
while (--i >= 0) {
auto* item = funcContext->hangStack[i];
if (item == nullptr) {
conditions++;
} else if (auto* loop = item->cast<Loop>()) {
if (loop->name == name) {
break;
}
}
}
switch (conditions) {
case 0: {
if (!oneIn(4)) {
continue;
}
break;
}
case 1: {
if (!oneIn(2)) {
continue;
}
break;
}
default: {
if (oneIn(conditions + 1)) {
continue;
}
}
}
return builder.makeBreak(name);
}
}
if (type != Type::unreachable) {
funcContext->hangStack.pop_back();
}
return makeTrivial(type);
}
Expression* TranslateToFuzzReader::makeCall(Type type) {
int tries = TRIES;
bool isReturn;
while (tries-- > 0) {
Function* target = funcContext->func;
if (!wasm.functions.empty() && !oneIn(wasm.functions.size())) {
target = pick(wasm.functions).get();
}
isReturn = type == Type::unreachable && wasm.features.hasTailCall() &&
funcContext->func->getResults() == target->getResults();
if (target->getResults() != type && !isReturn) {
continue;
}
std::vector<Expression*> args;
for (const auto& argType : target->getParams()) {
args.push_back(make(argType));
}
return builder.makeCall(target->name, args, type, isReturn);
}
return makeTrivial(type);
}
Expression* TranslateToFuzzReader::makeCallIndirect(Type type) {
auto& randomElem = wasm.elementSegments[upTo(wasm.elementSegments.size())];
auto& data = randomElem->data;
if (data.empty()) {
return make(type);
}
Index start = upTo(data.size());
Index i = start;
Function* targetFn;
bool isReturn;
while (1) {
if (auto* get = data[i]->dynCast<RefFunc>()) {
targetFn = wasm.getFunction(get->func);
isReturn = type == Type::unreachable && wasm.features.hasTailCall() &&
funcContext->func->getResults() == targetFn->getResults();
if (targetFn->getResults() == type || isReturn) {
break;
}
}
i++;
if (i == data.size()) {
i = 0;
}
if (i == start) {
return makeTrivial(type);
}
}
Expression* target;
if (!allowOOB || !oneIn(10)) {
target = builder.makeConst(int32_t(i));
} else {
target = make(Type::i32);
}
std::vector<Expression*> args;
for (const auto& type : targetFn->getParams()) {
args.push_back(make(type));
}
return builder.makeCallIndirect(
funcrefTableName, target, args, targetFn->type, isReturn);
}
Expression* TranslateToFuzzReader::makeCallRef(Type type) {
Function* target;
bool isReturn;
size_t i = 0;
while (1) {
if (i == TRIES || wasm.functions.empty()) {
return makeTrivial(type);
}
target = wasm.functions[upTo(wasm.functions.size())].get();
isReturn = type == Type::unreachable && wasm.features.hasTailCall() &&
funcContext->func->getResults() == target->getResults();
if (target->getResults() == type || isReturn) {
break;
}
i++;
}
std::vector<Expression*> args;
for (const auto& type : target->getParams()) {
args.push_back(make(type));
}
return builder.makeCallRef(
builder.makeRefFunc(target->name, target->type), args, type, isReturn);
}
Expression* TranslateToFuzzReader::makeLocalGet(Type type) {
auto& locals = funcContext->typeLocals[type];
if (!locals.empty()) {
return builder.makeLocalGet(pick(locals), type);
}
if (trivialNesting) {
return makeConst(type);
}
auto choice = upTo(3);
if (choice == 0) {
return makeConst(type);
}
auto index = builder.addVar(funcContext->func, type);
LocalSet* tee = nullptr;
if (choice == 1 || type.isNonNullable()) {
tee = builder.makeLocalTee(index, make(type), type);
}
funcContext->typeLocals[type].push_back(index);
if (tee) {
return tee;
}
return builder.makeLocalGet(index, type);
}
Expression* TranslateToFuzzReader::makeLocalSet(Type type) {
bool tee = type != Type::none;
Type valueType;
if (tee) {
valueType = type;
} else {
valueType = getConcreteType();
}
auto& locals = funcContext->typeLocals[valueType];
if (locals.empty()) {
return makeTrivial(type);
}
auto* value = make(valueType);
if (tee) {
return builder.makeLocalTee(pick(locals), value, valueType);
} else {
return builder.makeLocalSet(pick(locals), value);
}
}
Expression* TranslateToFuzzReader::makeGlobalGet(Type type) {
auto it = globalsByType.find(type);
if (it == globalsByType.end() || it->second.empty()) {
return makeTrivial(type);
}
auto name = pick(it->second);
assert(name != HANG_LIMIT_GLOBAL);
return builder.makeGlobalGet(name, type);
}
Expression* TranslateToFuzzReader::makeGlobalSet(Type type) {
assert(type == Type::none);
type = getConcreteType();
auto it = mutableGlobalsByType.find(type);
if (it == mutableGlobalsByType.end() || it->second.empty()) {
return makeTrivial(Type::none);
}
auto name = pick(it->second);
assert(name != HANG_LIMIT_GLOBAL);
return builder.makeGlobalSet(name, make(type));
}
Expression* TranslateToFuzzReader::makeTupleMake(Type type) {
assert(wasm.features.hasMultivalue());
assert(type.isTuple());
std::vector<Expression*> elements;
for (const auto& t : type) {
elements.push_back(make(t));
}
return builder.makeTupleMake(std::move(elements));
}
Expression* TranslateToFuzzReader::makeTupleExtract(Type type) {
if (!type.isDefaultable()) {
return makeTrivial(type);
}
assert(wasm.features.hasMultivalue());
assert(type.isSingle() && type.isConcrete());
Type tupleType = getTupleType();
std::vector<size_t> extractIndices;
size_t i = 0;
for (const auto& t : tupleType) {
if (t == type) {
extractIndices.push_back(i);
}
++i;
}
if (extractIndices.size() == 0) {
std::vector<Type> newElements(tupleType.begin(), tupleType.end());
size_t injected = upTo(newElements.size());
newElements[injected] = type;
tupleType = Type(newElements);
extractIndices.push_back(injected);
}
Index index = pick(extractIndices);
Expression* child = make(tupleType);
return builder.makeTupleExtract(child, index);
}
Expression* TranslateToFuzzReader::makePointer() {
auto* ret = make(wasm.memories[0]->indexType);
if (!allowOOB || !oneIn(10)) {
if (wasm.memories[0]->is64()) {
ret = builder.makeBinary(
AndInt64, ret, builder.makeConst(int64_t(USABLE_MEMORY - 1)));
} else {
ret = builder.makeBinary(
AndInt32, ret, builder.makeConst(int32_t(USABLE_MEMORY - 1)));
}
}
return ret;
}
Expression* TranslateToFuzzReader::makeNonAtomicLoad(Type type) {
auto offset = logify(get());
auto ptr = makePointer();
switch (type.getBasic()) {
case Type::i32: {
bool signed_ = get() & 1;
switch (upTo(3)) {
case 0:
return builder.makeLoad(
1, signed_, offset, 1, ptr, type, wasm.memories[0]->name);
case 1:
return builder.makeLoad(
2, signed_, offset, pick(1, 2), ptr, type, wasm.memories[0]->name);
case 2:
return builder.makeLoad(4,
signed_,
offset,
pick(1, 2, 4),
ptr,
type,
wasm.memories[0]->name);
}
WASM_UNREACHABLE("unexpected value");
}
case Type::i64: {
bool signed_ = get() & 1;
switch (upTo(4)) {
case 0:
return builder.makeLoad(
1, signed_, offset, 1, ptr, type, wasm.memories[0]->name);
case 1:
return builder.makeLoad(
2, signed_, offset, pick(1, 2), ptr, type, wasm.memories[0]->name);
case 2:
return builder.makeLoad(4,
signed_,
offset,
pick(1, 2, 4),
ptr,
type,
wasm.memories[0]->name);
case 3:
return builder.makeLoad(8,
signed_,
offset,
pick(1, 2, 4, 8),
ptr,
type,
wasm.memories[0]->name);
}
WASM_UNREACHABLE("unexpected value");
}
case Type::f32: {
return builder.makeLoad(
4, false, offset, pick(1, 2, 4), ptr, type, wasm.memories[0]->name);
}
case Type::f64: {
return builder.makeLoad(
8, false, offset, pick(1, 2, 4, 8), ptr, type, wasm.memories[0]->name);
}
case Type::v128: {
if (!wasm.features.hasSIMD()) {
return makeTrivial(type);
}
return builder.makeLoad(16,
false,
offset,
pick(1, 2, 4, 8, 16),
ptr,
type,
wasm.memories[0]->name);
}
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("invalid type");
}
WASM_UNREACHABLE("invalid type");
}
Expression* TranslateToFuzzReader::makeLoad(Type type) {
if (!allowMemory || type.isRef()) {
return makeTrivial(type);
}
auto* ret = makeNonAtomicLoad(type);
if (type != Type::i32 && type != Type::i64) {
return ret;
}
if (!wasm.features.hasAtomics() || oneIn(2)) {
return ret;
}
auto* load = ret->cast<Load>();
wasm.memories[0]->shared = true;
load->isAtomic = true;
load->signed_ = false;
load->align = load->bytes;
return load;
}
Expression* TranslateToFuzzReader::makeNonAtomicStore(Type type) {
if (type == Type::unreachable) {
auto* ret = makeNonAtomicStore(getStorableType());
auto* store = ret->dynCast<Store>();
if (!store) {
return ret;
}
switch (upTo(3)) {
case 0:
store->ptr = make(Type::unreachable);
break;
case 1:
store->value = make(Type::unreachable);
break;
case 2:
store->ptr = make(Type::unreachable);
store->value = make(Type::unreachable);
break;
}
store->memory = wasm.memories[0]->name;
store->finalize();
return store;
}
if (type == Type::none) {
type = getStorableType();
}
auto offset = logify(get());
auto ptr = makePointer();
auto value = make(type);
switch (type.getBasic()) {
case Type::i32: {
switch (upTo(3)) {
case 0:
return builder.makeStore(
1, offset, 1, ptr, value, type, wasm.memories[0]->name);
case 1:
return builder.makeStore(
2, offset, pick(1, 2), ptr, value, type, wasm.memories[0]->name);
case 2:
return builder.makeStore(
4, offset, pick(1, 2, 4), ptr, value, type, wasm.memories[0]->name);
}
WASM_UNREACHABLE("invalid value");
}
case Type::i64: {
switch (upTo(4)) {
case 0:
return builder.makeStore(
1, offset, 1, ptr, value, type, wasm.memories[0]->name);
case 1:
return builder.makeStore(
2, offset, pick(1, 2), ptr, value, type, wasm.memories[0]->name);
case 2:
return builder.makeStore(
4, offset, pick(1, 2, 4), ptr, value, type, wasm.memories[0]->name);
case 3:
return builder.makeStore(8,
offset,
pick(1, 2, 4, 8),
ptr,
value,
type,
wasm.memories[0]->name);
}
WASM_UNREACHABLE("invalid value");
}
case Type::f32: {
return builder.makeStore(
4, offset, pick(1, 2, 4), ptr, value, type, wasm.memories[0]->name);
}
case Type::f64: {
return builder.makeStore(
8, offset, pick(1, 2, 4, 8), ptr, value, type, wasm.memories[0]->name);
}
case Type::v128: {
if (!wasm.features.hasSIMD()) {
return makeTrivial(type);
}
return builder.makeStore(16,
offset,
pick(1, 2, 4, 8, 16),
ptr,
value,
type,
wasm.memories[0]->name);
}
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("invalid type");
}
WASM_UNREACHABLE("invalid type");
}
Expression* TranslateToFuzzReader::makeStore(Type type) {
if (!allowMemory || type.isRef()) {
return makeTrivial(type);
}
auto* ret = makeNonAtomicStore(type);
auto* store = ret->dynCast<Store>();
if (!store) {
return ret;
}
if (store->value->type != Type::i32 && store->value->type != Type::i64) {
return store;
}
if (!wasm.features.hasAtomics() || oneIn(2)) {
return store;
}
wasm.memories[0]->shared = true;
store->isAtomic = true;
store->align = store->bytes;
return store;
}
Literal TranslateToFuzzReader::tweak(Literal value) {
auto type = value.type;
if (type.isVector()) {
return value;
}
switch (upTo(5)) {
case 0:
value = value.add(Literal::makeNegOne(type));
break;
case 1:
value = value.add(Literal::makeOne(type));
break;
default: {
}
}
if (type.isFloat() && oneIn(2)) {
const int RANGE = 1000;
auto RANGE_LITERAL = Literal::makeFromInt32(RANGE, type);
auto adjustment = Literal::makeFromInt32(upTo(2 * RANGE + 1), type);
adjustment = adjustment.sub(RANGE_LITERAL);
adjustment = adjustment.div(RANGE_LITERAL);
value = value.add(adjustment);
}
if (oneIn(2)) {
value = value.mul(Literal::makeNegOne(type));
}
return value;
}
Literal TranslateToFuzzReader::makeLiteral(Type type) {
if (type == Type::v128) {
switch (upTo(6)) {
case 0:
return Literal(std::array<Literal, 16>{{makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32)}});
case 1:
return Literal(std::array<Literal, 8>{{makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32)}});
case 2:
return Literal(std::array<Literal, 4>{{makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32),
makeLiteral(Type::i32)}});
case 3:
return Literal(std::array<Literal, 2>{
{makeLiteral(Type::i64), makeLiteral(Type::i64)}});
case 4:
return Literal(std::array<Literal, 4>{{makeLiteral(Type::f32),
makeLiteral(Type::f32),
makeLiteral(Type::f32),
makeLiteral(Type::f32)}});
case 5:
return Literal(std::array<Literal, 2>{
{makeLiteral(Type::f64), makeLiteral(Type::f64)}});
default:
WASM_UNREACHABLE("unexpected value");
}
}
switch (upTo(4)) {
case 0: {
switch (type.getBasic()) {
case Type::i32:
return Literal(get32());
case Type::i64:
return Literal(get64());
case Type::f32:
return Literal(getFloat());
case Type::f64:
return Literal(getDouble());
case Type::v128:
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("invalid type");
}
break;
}
case 1: {
int64_t small;
switch (upTo(6)) {
case 0:
small = int8_t(get());
break;
case 1:
small = uint8_t(get());
break;
case 2:
small = int16_t(get16());
break;
case 3:
small = uint16_t(get16());
break;
case 4:
small = int32_t(get32());
break;
case 5:
small = uint32_t(get32());
break;
default:
WASM_UNREACHABLE("invalid value");
}
switch (type.getBasic()) {
case Type::i32:
return Literal(int32_t(small));
case Type::i64:
return Literal(int64_t(small));
case Type::f32:
return Literal(float(small));
case Type::f64:
return Literal(double(small));
case Type::v128:
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
break;
}
case 2: {
Literal value;
switch (type.getBasic()) {
case Type::i32:
value = Literal(pick<int32_t>(0,
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max(),
std::numeric_limits<int16_t>::min(),
std::numeric_limits<int16_t>::max(),
std::numeric_limits<int32_t>::min(),
std::numeric_limits<int32_t>::max(),
std::numeric_limits<uint8_t>::max(),
std::numeric_limits<uint16_t>::max(),
std::numeric_limits<uint32_t>::max()));
break;
case Type::i64:
value = Literal(pick<int64_t>(0,
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max(),
std::numeric_limits<int16_t>::min(),
std::numeric_limits<int16_t>::max(),
std::numeric_limits<int32_t>::min(),
std::numeric_limits<int32_t>::max(),
std::numeric_limits<int64_t>::min(),
std::numeric_limits<int64_t>::max(),
std::numeric_limits<uint8_t>::max(),
std::numeric_limits<uint16_t>::max(),
std::numeric_limits<uint32_t>::max(),
std::numeric_limits<uint64_t>::max()));
break;
case Type::f32:
value = Literal(pick<float>(0.0f,
-0.0f,
std::numeric_limits<float>::min(),
std::numeric_limits<float>::max(),
std::numeric_limits<int32_t>::min(),
std::numeric_limits<int32_t>::max(),
std::numeric_limits<int64_t>::min(),
std::numeric_limits<int64_t>::max(),
std::numeric_limits<uint32_t>::max(),
std::numeric_limits<uint64_t>::max()));
break;
case Type::f64:
value = Literal(pick<double>(0.0,
-0.0,
std::numeric_limits<float>::min(),
std::numeric_limits<float>::max(),
std::numeric_limits<double>::min(),
std::numeric_limits<double>::max(),
std::numeric_limits<int32_t>::min(),
std::numeric_limits<int32_t>::max(),
std::numeric_limits<int64_t>::min(),
std::numeric_limits<int64_t>::max(),
std::numeric_limits<uint32_t>::max(),
std::numeric_limits<uint64_t>::max()));
break;
case Type::v128:
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
return tweak(value);
}
case 3: {
Literal value;
switch (type.getBasic()) {
case Type::i32:
value = Literal(int32_t(1) << upTo(32));
break;
case Type::i64:
value = Literal(int64_t(1) << upTo(64));
break;
case Type::f32:
value = Literal(float(int64_t(1) << upTo(64)));
break;
case Type::f64:
value = Literal(double(int64_t(1) << upTo(64)));
break;
case Type::v128:
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
return tweak(value);
}
}
WASM_UNREACHABLE("invalid value");
}
Expression* TranslateToFuzzReader::makeRefFuncConst(Type type) {
auto heapType = type.getHeapType();
if (heapType == HeapType::func) {
Function* target = funcContext ? funcContext->func : nullptr;
if (!wasm.functions.empty() && (!target || !oneIn(wasm.functions.size()))) {
target = pick(wasm.functions).get();
}
if (target) {
return builder.makeRefFunc(target->name, target->type);
}
}
if (heapType == HeapType::func) {
heapType = Signature(Type::none, Type::none);
}
for (auto& func : wasm.functions) {
if (Type::isSubType(Type(func->type, NonNullable), type)) {
return builder.makeRefFunc(func->name, func->type);
}
}
if ((type.isNullable() && oneIn(2)) ||
(type.isNonNullable() && oneIn(16) && funcContext)) {
Expression* ret = builder.makeRefNull(HeapType::nofunc);
if (!type.isNullable()) {
assert(funcContext);
ret = builder.makeRefAs(RefAsNonNull, ret);
}
return ret;
}
auto* body = heapType.getSignature().results == Type::none
? (Expression*)builder.makeNop()
: (Expression*)builder.makeUnreachable();
auto* func = wasm.addFunction(builder.makeFunction(
Names::getValidFunctionName(wasm, "ref_func_target"), heapType, {}, body));
return builder.makeRefFunc(func->name, heapType);
}
Expression* TranslateToFuzzReader::makeConst(Type type) {
if (type.isRef()) {
assert(wasm.features.hasReferenceTypes());
if (type.isNullable() && oneIn(8)) {
return builder.makeRefNull(type.getHeapType());
}
if (type.getHeapType().isBasic()) {
return makeBasicRef(type);
} else {
return makeCompoundRef(type);
}
} else if (type.isTuple()) {
std::vector<Expression*> operands;
for (const auto& t : type) {
operands.push_back(makeConst(t));
}
return builder.makeTupleMake(std::move(operands));
} else {
assert(type.isBasic());
return builder.makeConst(makeLiteral(type));
}
}
Expression* TranslateToFuzzReader::makeBasicRef(Type type) {
assert(type.isRef());
auto heapType = type.getHeapType();
assert(heapType.isBasic());
assert(wasm.features.hasReferenceTypes());
switch (heapType.getBasic()) {
case HeapType::ext: {
auto null = builder.makeRefNull(HeapType::ext);
if (!type.isNullable()) {
assert(funcContext);
return builder.makeRefAs(RefAsNonNull, null);
}
return null;
}
case HeapType::func: {
return makeRefFuncConst(type);
}
case HeapType::any: {
Nullability nullability = getSubType(type.getNullability());
HeapType subtype;
switch (upTo(3)) {
case 0:
subtype = HeapType::i31;
break;
case 1:
subtype = HeapType::struct_;
break;
case 2:
subtype = HeapType::array;
break;
}
return makeConst(Type(subtype, nullability));
}
case HeapType::eq: {
if (!wasm.features.hasGC()) {
assert(type.isNullable());
return builder.makeRefNull(HeapType::none);
}
auto nullability = getSubType(type.getNullability());
HeapType subtype;
switch (upTo(3)) {
case 0:
subtype = HeapType::i31;
break;
case 1:
subtype = HeapType::struct_;
break;
case 2:
subtype = HeapType::array;
break;
}
return makeConst(Type(subtype, nullability));
}
case HeapType::i31: {
assert(wasm.features.hasGC());
if (type.isNullable() && oneIn(4)) {
return builder.makeRefNull(HeapType::none);
}
return builder.makeRefI31(makeConst(Type::i32));
}
case HeapType::struct_: {
assert(wasm.features.hasGC());
static HeapType trivialStruct = HeapType(Struct());
return builder.makeStructNew(trivialStruct, std::vector<Expression*>{});
}
case HeapType::array: {
static HeapType trivialArray =
HeapType(Array(Field(Field::PackedType::i8, Immutable)));
return builder.makeArrayNewFixed(trivialArray, {});
}
case HeapType::exn: {
auto null = builder.makeRefNull(HeapType::exn);
if (!type.isNullable()) {
assert(funcContext);
return builder.makeRefAs(RefAsNonNull, null);
}
return null;
}
case HeapType::string:
return builder.makeStringConst(std::to_string(upTo(1024)));
case HeapType::stringview_wtf8:
case HeapType::stringview_wtf16:
case HeapType::stringview_iter:
WASM_UNREACHABLE("TODO: strings");
case HeapType::none:
case HeapType::noext:
case HeapType::nofunc:
case HeapType::noexn: {
auto null = builder.makeRefNull(heapType);
if (!type.isNullable()) {
assert(funcContext);
return builder.makeRefAs(RefAsNonNull, null);
}
return null;
}
}
WASM_UNREACHABLE("invalid basic ref type");
}
Expression* TranslateToFuzzReader::makeCompoundRef(Type type) {
assert(type.isRef());
auto heapType = type.getHeapType();
assert(!heapType.isBasic());
assert(wasm.features.hasReferenceTypes());
const auto LIMIT = NESTING_LIMIT + 1;
AutoNester nester(*this);
if (type.isNullable() &&
(random.finished() || nesting >= LIMIT || oneIn(LIMIT - nesting + 1))) {
return builder.makeRefNull(heapType);
}
if (type.isNonNullable() && (random.finished() || nesting >= LIMIT)) {
if (funcContext && !funcContext->typeLocals[type].empty()) {
return makeLocalGet(type);
}
return builder.makeRefAs(RefAsNonNull, builder.makeRefNull(heapType));
}
auto makeChild = [&](Type type) {
return funcContext ? make(type) : makeTrivial(type);
};
if (heapType.isSignature()) {
return makeRefFuncConst(type);
} else if (type.isStruct()) {
auto& fields = heapType.getStruct().fields;
std::vector<Expression*> values;
if (std::any_of(
fields.begin(),
fields.end(),
[&](const Field& field) { return !field.type.isDefaultable(); }) ||
oneIn(2)) {
for (auto& field : fields) {
values.push_back(makeChild(field.type));
}
if (!values.empty()) {
nester.add(values.size() - 1);
}
}
return builder.makeStructNew(heapType, values);
} else if (type.isArray()) {
auto element = heapType.getArray().element;
Expression* init = nullptr;
if (!element.type.isDefaultable() || oneIn(2)) {
init = makeChild(element.type);
}
auto* count = builder.makeConst(int32_t(upTo(MAX_ARRAY_SIZE)));
return builder.makeArrayNew(type.getHeapType(), count, init);
} else {
WASM_UNREACHABLE("bad user-defined ref type");
}
}
Expression* TranslateToFuzzReader::makeTrappingRefUse(HeapType type) {
auto percent = upTo(100);
if (percent < 5) {
return make(Type(type, Nullable));
}
auto nonNull = Type(type, NonNullable);
if (percent < 70 || !funcContext) {
return make(nonNull);
}
return makeLocalGet(nonNull);
}
Expression* TranslateToFuzzReader::buildUnary(const UnaryArgs& args) {
return builder.makeUnary(args.a, args.b);
}
Expression* TranslateToFuzzReader::makeUnary(Type type) {
assert(!type.isTuple());
if (type == Type::unreachable) {
if (auto* unary = makeUnary(getSingleConcreteType())->dynCast<Unary>()) {
return builder.makeUnary(unary->op, make(Type::unreachable));
}
return makeTrivial(type);
}
if (type.isRef()) {
return makeTrivial(type);
}
switch (type.getBasic()) {
case Type::i32: {
auto singleConcreteType = getSingleConcreteType();
if (singleConcreteType.isRef()) {
return makeTrivial(type);
}
switch (singleConcreteType.getBasic()) {
case Type::i32: {
auto op = pick(
FeatureOptions<UnaryOp>()
.add(FeatureSet::MVP, EqZInt32, ClzInt32, CtzInt32, PopcntInt32)
.add(FeatureSet::SignExt, ExtendS8Int32, ExtendS16Int32));
return buildUnary({op, make(Type::i32)});
}
case Type::i64:
return buildUnary({pick(EqZInt64, WrapInt64), make(Type::i64)});
case Type::f32: {
auto op = pick(FeatureOptions<UnaryOp>()
.add(FeatureSet::MVP,
TruncSFloat32ToInt32,
TruncUFloat32ToInt32,
ReinterpretFloat32)
.add(FeatureSet::TruncSat,
TruncSatSFloat32ToInt32,
TruncSatUFloat32ToInt32));
return buildUnary({op, make(Type::f32)});
}
case Type::f64: {
auto op = pick(
FeatureOptions<UnaryOp>()
.add(FeatureSet::MVP, TruncSFloat64ToInt32, TruncUFloat64ToInt32)
.add(FeatureSet::TruncSat,
TruncSatSFloat64ToInt32,
TruncSatUFloat64ToInt32));
return buildUnary({op, make(Type::f64)});
}
case Type::v128: {
assert(wasm.features.hasSIMD());
return buildUnary({pick(AnyTrueVec128,
AllTrueVecI8x16,
AllTrueVecI16x8,
AllTrueVecI32x4),
make(Type::v128)});
}
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
WASM_UNREACHABLE("invalid type");
}
case Type::i64: {
switch (upTo(4)) {
case 0: {
auto op =
pick(FeatureOptions<UnaryOp>()
.add(FeatureSet::MVP, ClzInt64, CtzInt64, PopcntInt64)
.add(FeatureSet::SignExt,
ExtendS8Int64,
ExtendS16Int64,
ExtendS32Int64));
return buildUnary({op, make(Type::i64)});
}
case 1:
return buildUnary(
{pick(ExtendSInt32, ExtendUInt32), make(Type::i32)});
case 2: {
auto op = pick(
FeatureOptions<UnaryOp>()
.add(FeatureSet::MVP, TruncSFloat32ToInt64, TruncUFloat32ToInt64)
.add(FeatureSet::TruncSat,
TruncSatSFloat32ToInt64,
TruncSatUFloat32ToInt64));
return buildUnary({op, make(Type::f32)});
}
case 3: {
auto op = pick(FeatureOptions<UnaryOp>()
.add(FeatureSet::MVP,
TruncSFloat64ToInt64,
TruncUFloat64ToInt64,
ReinterpretFloat64)
.add(FeatureSet::TruncSat,
TruncSatSFloat64ToInt64,
TruncSatUFloat64ToInt64));
return buildUnary({op, make(Type::f64)});
}
}
WASM_UNREACHABLE("invalid value");
}
case Type::f32: {
switch (upTo(4)) {
case 0:
return buildUnary({pick(NegFloat32,
AbsFloat32,
CeilFloat32,
FloorFloat32,
TruncFloat32,
NearestFloat32,
SqrtFloat32),
make(Type::f32)});
case 1:
return buildUnary({pick(ConvertUInt32ToFloat32,
ConvertSInt32ToFloat32,
ReinterpretInt32),
make(Type::i32)});
case 2:
return buildUnary(
{pick(ConvertUInt64ToFloat32, ConvertSInt64ToFloat32),
make(Type::i64)});
case 3:
return buildUnary({DemoteFloat64, make(Type::f64)});
}
WASM_UNREACHABLE("invalid value");
}
case Type::f64: {
switch (upTo(4)) {
case 0:
return buildUnary({pick(NegFloat64,
AbsFloat64,
CeilFloat64,
FloorFloat64,
TruncFloat64,
NearestFloat64,
SqrtFloat64),
make(Type::f64)});
case 1:
return buildUnary(
{pick(ConvertUInt32ToFloat64, ConvertSInt32ToFloat64),
make(Type::i32)});
case 2:
return buildUnary({pick(ConvertUInt64ToFloat64,
ConvertSInt64ToFloat64,
ReinterpretInt64),
make(Type::i64)});
case 3:
return buildUnary({PromoteFloat32, make(Type::f32)});
}
WASM_UNREACHABLE("invalid value");
}
case Type::v128: {
assert(wasm.features.hasSIMD());
switch (upTo(5)) {
case 0:
return buildUnary({pick(SplatVecI8x16, SplatVecI16x8, SplatVecI32x4),
make(Type::i32)});
case 1:
return buildUnary({SplatVecI64x2, make(Type::i64)});
case 2:
return buildUnary({SplatVecF32x4, make(Type::f32)});
case 3:
return buildUnary({SplatVecF64x2, make(Type::f64)});
case 4:
return buildUnary({pick(NotVec128,
NegVecI8x16,
NegVecI16x8,
NegVecI32x4,
NegVecI64x2,
AbsVecF32x4,
NegVecF32x4,
SqrtVecF32x4,
AbsVecF64x2,
NegVecF64x2,
SqrtVecF64x2,
TruncSatSVecF32x4ToVecI32x4,
TruncSatUVecF32x4ToVecI32x4,
ConvertSVecI32x4ToVecF32x4,
ConvertUVecI32x4ToVecF32x4,
ExtendLowSVecI8x16ToVecI16x8,
ExtendHighSVecI8x16ToVecI16x8,
ExtendLowUVecI8x16ToVecI16x8,
ExtendHighUVecI8x16ToVecI16x8,
ExtendLowSVecI16x8ToVecI32x4,
ExtendHighSVecI16x8ToVecI32x4,
ExtendLowUVecI16x8ToVecI32x4,
ExtendHighUVecI16x8ToVecI32x4),
make(Type::v128)});
}
WASM_UNREACHABLE("invalid value");
}
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
WASM_UNREACHABLE("invalid type");
}
Expression* TranslateToFuzzReader::buildBinary(const BinaryArgs& args) {
return builder.makeBinary(args.a, args.b, args.c);
}
Expression* TranslateToFuzzReader::makeBinary(Type type) {
assert(!type.isTuple());
if (type == Type::unreachable) {
if (auto* binary = makeBinary(getSingleConcreteType())->dynCast<Binary>()) {
return buildBinary(
{binary->op, make(Type::unreachable), make(Type::unreachable)});
}
return makeTrivial(type);
}
if (type.isRef()) {
return makeTrivial(type);
}
switch (type.getBasic()) {
case Type::i32: {
switch (upTo(4)) {
case 0:
return buildBinary({pick(AddInt32,
SubInt32,
MulInt32,
DivSInt32,
DivUInt32,
RemSInt32,
RemUInt32,
AndInt32,
OrInt32,
XorInt32,
ShlInt32,
ShrUInt32,
ShrSInt32,
RotLInt32,
RotRInt32,
EqInt32,
NeInt32,
LtSInt32,
LtUInt32,
LeSInt32,
LeUInt32,
GtSInt32,
GtUInt32,
GeSInt32,
GeUInt32),
make(Type::i32),
make(Type::i32)});
case 1:
return buildBinary({pick(EqInt64,
NeInt64,
LtSInt64,
LtUInt64,
LeSInt64,
LeUInt64,
GtSInt64,
GtUInt64,
GeSInt64,
GeUInt64),
make(Type::i64),
make(Type::i64)});
case 2:
return buildBinary({pick(EqFloat32,
NeFloat32,
LtFloat32,
LeFloat32,
GtFloat32,
GeFloat32),
make(Type::f32),
make(Type::f32)});
case 3:
return buildBinary({pick(EqFloat64,
NeFloat64,
LtFloat64,
LeFloat64,
GtFloat64,
GeFloat64),
make(Type::f64),
make(Type::f64)});
}
WASM_UNREACHABLE("invalid value");
}
case Type::i64: {
return buildBinary({pick(AddInt64,
SubInt64,
MulInt64,
DivSInt64,
DivUInt64,
RemSInt64,
RemUInt64,
AndInt64,
OrInt64,
XorInt64,
ShlInt64,
ShrUInt64,
ShrSInt64,
RotLInt64,
RotRInt64),
make(Type::i64),
make(Type::i64)});
}
case Type::f32: {
return buildBinary({pick(AddFloat32,
SubFloat32,
MulFloat32,
DivFloat32,
CopySignFloat32,
MinFloat32,
MaxFloat32),
make(Type::f32),
make(Type::f32)});
}
case Type::f64: {
return buildBinary({pick(AddFloat64,
SubFloat64,
MulFloat64,
DivFloat64,
CopySignFloat64,
MinFloat64,
MaxFloat64),
make(Type::f64),
make(Type::f64)});
}
case Type::v128: {
assert(wasm.features.hasSIMD());
return buildBinary({pick(EqVecI8x16,
NeVecI8x16,
LtSVecI8x16,
LtUVecI8x16,
GtSVecI8x16,
GtUVecI8x16,
LeSVecI8x16,
LeUVecI8x16,
GeSVecI8x16,
GeUVecI8x16,
EqVecI16x8,
NeVecI16x8,
LtSVecI16x8,
LtUVecI16x8,
GtSVecI16x8,
GtUVecI16x8,
LeSVecI16x8,
LeUVecI16x8,
GeSVecI16x8,
GeUVecI16x8,
EqVecI32x4,
NeVecI32x4,
LtSVecI32x4,
LtUVecI32x4,
GtSVecI32x4,
GtUVecI32x4,
LeSVecI32x4,
LeUVecI32x4,
GeSVecI32x4,
GeUVecI32x4,
EqVecF32x4,
NeVecF32x4,
LtVecF32x4,
GtVecF32x4,
LeVecF32x4,
GeVecF32x4,
EqVecF64x2,
NeVecF64x2,
LtVecF64x2,
GtVecF64x2,
LeVecF64x2,
GeVecF64x2,
AndVec128,
OrVec128,
XorVec128,
AndNotVec128,
AddVecI8x16,
AddSatSVecI8x16,
AddSatUVecI8x16,
SubVecI8x16,
SubSatSVecI8x16,
SubSatUVecI8x16,
MinSVecI8x16,
MinUVecI8x16,
MaxSVecI8x16,
MaxUVecI8x16,
AddVecI16x8,
AddSatSVecI16x8,
AddSatUVecI16x8,
SubVecI16x8,
SubSatSVecI16x8,
SubSatUVecI16x8,
MulVecI16x8,
MinSVecI16x8,
MinUVecI16x8,
MaxSVecI16x8,
MaxUVecI16x8,
AddVecI32x4,
SubVecI32x4,
MulVecI32x4,
MinSVecI32x4,
MinUVecI32x4,
MaxSVecI32x4,
MaxUVecI32x4,
DotSVecI16x8ToVecI32x4,
AddVecI64x2,
SubVecI64x2,
AddVecF32x4,
SubVecF32x4,
MulVecF32x4,
DivVecF32x4,
MinVecF32x4,
MaxVecF32x4,
AddVecF64x2,
SubVecF64x2,
MulVecF64x2,
DivVecF64x2,
MinVecF64x2,
MaxVecF64x2,
NarrowSVecI16x8ToVecI8x16,
NarrowUVecI16x8ToVecI8x16,
NarrowSVecI32x4ToVecI16x8,
NarrowUVecI32x4ToVecI16x8,
SwizzleVecI8x16),
make(Type::v128),
make(Type::v128)});
}
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
WASM_UNREACHABLE("invalid type");
}
Expression* TranslateToFuzzReader::buildSelect(const ThreeArgs& args,
Type type) {
return builder.makeSelect(args.a, args.b, args.c, type);
}
Expression* TranslateToFuzzReader::makeSelect(Type type) {
Type subType1 = getSubType(type);
Type subType2 = getSubType(type);
return buildSelect({make(Type::i32), make(subType1), make(subType2)}, type);
}
Expression* TranslateToFuzzReader::makeSwitch(Type type) {
assert(type == Type::unreachable);
if (funcContext->breakableStack.empty()) {
return make(type);
}
int tries = TRIES;
std::vector<Name> names;
Type valueType = Type::unreachable;
while (tries-- > 0) {
auto* target = pick(funcContext->breakableStack);
auto name = getTargetName(target);
auto currValueType = getTargetType(target);
if (names.empty()) {
valueType = currValueType;
} else {
if (valueType != currValueType) {
continue; }
}
names.push_back(name);
}
if (names.size() < 2) {
return make(type);
}
auto default_ = names.back();
names.pop_back();
auto temp1 = make(Type::i32),
temp2 = valueType.isConcrete() ? make(valueType) : nullptr;
return builder.makeSwitch(names, default_, temp1, temp2);
}
Expression* TranslateToFuzzReader::makeDrop(Type type) {
return builder.makeDrop(
make(type == Type::unreachable ? type : getConcreteType()));
}
Expression* TranslateToFuzzReader::makeReturn(Type type) {
return builder.makeReturn(funcContext->func->getResults().isConcrete()
? make(funcContext->func->getResults())
: nullptr);
}
Expression* TranslateToFuzzReader::makeNop(Type type) {
assert(type == Type::none);
return builder.makeNop();
}
Expression* TranslateToFuzzReader::makeUnreachable(Type type) {
assert(type == Type::unreachable);
return builder.makeUnreachable();
}
Expression* TranslateToFuzzReader::makeAtomic(Type type) {
assert(wasm.features.hasAtomics());
if (!allowMemory) {
return makeTrivial(type);
}
wasm.memories[0]->shared = true;
if (type == Type::none) {
return builder.makeAtomicFence();
}
if (type == Type::i32 && oneIn(2)) {
if (ATOMIC_WAITS && oneIn(2)) {
auto* ptr = makePointer();
auto expectedType = pick(Type::i32, Type::i64);
auto* expected = make(expectedType);
auto* timeout = make(Type::i64);
return builder.makeAtomicWait(ptr,
expected,
timeout,
expectedType,
logify(get()),
wasm.memories[0]->name);
} else {
auto* ptr = makePointer();
auto* count = make(Type::i32);
return builder.makeAtomicNotify(
ptr, count, logify(get()), wasm.memories[0]->name);
}
}
Index bytes;
switch (type.getBasic()) {
case Type::i32: {
switch (upTo(3)) {
case 0:
bytes = 1;
break;
case 1:
bytes = pick(1, 2);
break;
case 2:
bytes = pick(1, 2, 4);
break;
default:
WASM_UNREACHABLE("invalide value");
}
break;
}
case Type::i64: {
switch (upTo(4)) {
case 0:
bytes = 1;
break;
case 1:
bytes = pick(1, 2);
break;
case 2:
bytes = pick(1, 2, 4);
break;
case 3:
bytes = pick(1, 2, 4, 8);
break;
default:
WASM_UNREACHABLE("invalide value");
}
break;
}
default:
WASM_UNREACHABLE("unexpected type");
}
auto offset = logify(get());
auto* ptr = makePointer();
if (oneIn(2)) {
auto* value = make(type);
return builder.makeAtomicRMW(
pick(RMWAdd, RMWSub, RMWAnd, RMWOr, RMWXor, RMWXchg),
bytes,
offset,
ptr,
value,
type,
wasm.memories[0]->name);
} else {
auto* expected = make(type);
auto* replacement = make(type);
return builder.makeAtomicCmpxchg(
bytes, offset, ptr, expected, replacement, type, wasm.memories[0]->name);
}
}
Expression* TranslateToFuzzReader::makeSIMD(Type type) {
assert(wasm.features.hasSIMD());
if (type.isRef()) {
return makeTrivial(type);
}
if (type != Type::v128) {
return makeSIMDExtract(type);
}
switch (upTo(7)) {
case 0:
return makeUnary(Type::v128);
case 1:
return makeBinary(Type::v128);
case 2:
return makeSIMDReplace();
case 3:
return makeSIMDShuffle();
case 4:
return makeSIMDTernary();
case 5:
return makeSIMDShift();
case 6:
return makeSIMDLoad();
}
WASM_UNREACHABLE("invalid value");
}
Expression* TranslateToFuzzReader::makeSIMDExtract(Type type) {
auto op = static_cast<SIMDExtractOp>(0);
switch (type.getBasic()) {
case Type::i32:
op = pick(ExtractLaneSVecI8x16,
ExtractLaneUVecI8x16,
ExtractLaneSVecI16x8,
ExtractLaneUVecI16x8,
ExtractLaneVecI32x4);
break;
case Type::i64:
op = ExtractLaneVecI64x2;
break;
case Type::f32:
op = ExtractLaneVecF32x4;
break;
case Type::f64:
op = ExtractLaneVecF64x2;
break;
case Type::v128:
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
Expression* vec = make(Type::v128);
uint8_t index = 0;
switch (op) {
case ExtractLaneSVecI8x16:
case ExtractLaneUVecI8x16:
index = upTo(16);
break;
case ExtractLaneSVecI16x8:
case ExtractLaneUVecI16x8:
index = upTo(8);
break;
case ExtractLaneVecI32x4:
case ExtractLaneVecF32x4:
index = upTo(4);
break;
case ExtractLaneVecI64x2:
case ExtractLaneVecF64x2:
index = upTo(2);
break;
}
return builder.makeSIMDExtract(op, vec, index);
}
Expression* TranslateToFuzzReader::makeSIMDReplace() {
SIMDReplaceOp op = pick(ReplaceLaneVecI8x16,
ReplaceLaneVecI16x8,
ReplaceLaneVecI32x4,
ReplaceLaneVecI64x2,
ReplaceLaneVecF32x4,
ReplaceLaneVecF64x2);
Expression* vec = make(Type::v128);
uint8_t index;
Type lane_t;
switch (op) {
case ReplaceLaneVecI8x16:
index = upTo(16);
lane_t = Type::i32;
break;
case ReplaceLaneVecI16x8:
index = upTo(8);
lane_t = Type::i32;
break;
case ReplaceLaneVecI32x4:
index = upTo(4);
lane_t = Type::i32;
break;
case ReplaceLaneVecI64x2:
index = upTo(2);
lane_t = Type::i64;
break;
case ReplaceLaneVecF32x4:
index = upTo(4);
lane_t = Type::f32;
break;
case ReplaceLaneVecF64x2:
index = upTo(2);
lane_t = Type::f64;
break;
default:
WASM_UNREACHABLE("unexpected op");
}
Expression* value = make(lane_t);
return builder.makeSIMDReplace(op, vec, index, value);
}
Expression* TranslateToFuzzReader::makeSIMDShuffle() {
Expression* left = make(Type::v128);
Expression* right = make(Type::v128);
std::array<uint8_t, 16> mask;
for (size_t i = 0; i < 16; ++i) {
mask[i] = upTo(32);
}
return builder.makeSIMDShuffle(left, right, mask);
}
Expression* TranslateToFuzzReader::makeSIMDTernary() {
SIMDTernaryOp op = Bitselect;
Expression* a = make(Type::v128);
Expression* b = make(Type::v128);
Expression* c = make(Type::v128);
return builder.makeSIMDTernary(op, a, b, c);
}
Expression* TranslateToFuzzReader::makeSIMDShift() {
SIMDShiftOp op = pick(ShlVecI8x16,
ShrSVecI8x16,
ShrUVecI8x16,
ShlVecI16x8,
ShrSVecI16x8,
ShrUVecI16x8,
ShlVecI32x4,
ShrSVecI32x4,
ShrUVecI32x4,
ShlVecI64x2,
ShrSVecI64x2,
ShrUVecI64x2);
Expression* vec = make(Type::v128);
Expression* shift = make(Type::i32);
return builder.makeSIMDShift(op, vec, shift);
}
Expression* TranslateToFuzzReader::makeSIMDLoad() {
SIMDLoadOp op = pick(Load8SplatVec128,
Load16SplatVec128,
Load32SplatVec128,
Load64SplatVec128,
Load8x8SVec128,
Load8x8UVec128,
Load16x4SVec128,
Load16x4UVec128,
Load32x2SVec128,
Load32x2UVec128);
Address offset = logify(get());
Address align;
switch (op) {
case Load8SplatVec128:
align = 1;
break;
case Load16SplatVec128:
align = pick(1, 2);
break;
case Load32SplatVec128:
align = pick(1, 2, 4);
break;
case Load64SplatVec128:
case Load8x8SVec128:
case Load8x8UVec128:
case Load16x4SVec128:
case Load16x4UVec128:
case Load32x2SVec128:
case Load32x2UVec128:
align = pick(1, 2, 4, 8);
break;
case Load32ZeroVec128:
case Load64ZeroVec128:
WASM_UNREACHABLE("Unexpected SIMD loads");
}
Expression* ptr = makePointer();
return builder.makeSIMDLoad(op, offset, align, ptr, wasm.memories[0]->name);
}
Expression* TranslateToFuzzReader::makeBulkMemory(Type type) {
if (!allowMemory) {
return makeTrivial(type);
}
assert(wasm.features.hasBulkMemory());
assert(type == Type::none);
switch (upTo(4)) {
case 0:
return makeMemoryInit();
case 1:
return makeDataDrop();
case 2:
return makeMemoryCopy();
case 3:
return makeMemoryFill();
}
WASM_UNREACHABLE("invalid value");
}
Expression* TranslateToFuzzReader::makeRefIsNull(Type type) {
assert(type == Type::i32);
assert(wasm.features.hasReferenceTypes());
return builder.makeRefIsNull(make(getReferenceType()));
}
Expression* TranslateToFuzzReader::makeRefEq(Type type) {
assert(type == Type::i32);
assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
auto* left = make(getEqReferenceType());
auto* right = make(getEqReferenceType());
return builder.makeRefEq(left, right);
}
Expression* TranslateToFuzzReader::makeRefTest(Type type) {
assert(type == Type::i32);
assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
Type refType, castType;
switch (upTo(3)) {
case 0:
refType = getReferenceType();
castType = getReferenceType();
if (refType.getHeapType().getBottom() ==
castType.getHeapType().getBottom()) {
break;
}
[[fallthrough]];
case 1:
refType = getReferenceType();
castType = getSubType(refType);
break;
case 2:
castType = getReferenceType();
refType = getSubType(castType);
break;
default:
WASM_UNREACHABLE("bad case");
}
return builder.makeRefTest(make(refType), castType);
}
Expression* TranslateToFuzzReader::makeRefCast(Type type) {
assert(type.isRef());
assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
Type refType;
switch (upTo(3)) {
case 0:
refType = getReferenceType();
if (refType.getHeapType().getBottom() == type.getHeapType().getBottom()) {
break;
}
[[fallthrough]];
case 1: {
refType = getSuperType(type);
break;
}
case 2:
refType = getSubType(type);
break;
default:
WASM_UNREACHABLE("bad case");
}
return builder.makeRefCast(make(refType), type);
}
Expression* TranslateToFuzzReader::makeStructGet(Type type) {
auto& structFields = typeStructFields[type];
assert(!structFields.empty());
auto [structType, fieldIndex] = pick(structFields);
auto* ref = makeTrappingRefUse(structType);
return builder.makeStructGet(fieldIndex, ref, type);
}
Expression* TranslateToFuzzReader::makeStructSet(Type type) {
assert(type == Type::none);
if (mutableStructFields.empty()) {
return makeTrivial(type);
}
auto [structType, fieldIndex] = pick(mutableStructFields);
auto fieldType = structType.getStruct().fields[fieldIndex].type;
auto* ref = makeTrappingRefUse(structType);
auto* value = make(fieldType);
return builder.makeStructSet(fieldIndex, ref, value);
}
static auto makeArrayBoundsCheck(Expression* ref,
Expression* index,
Function* func,
Builder& builder,
Expression* length = nullptr) {
auto tempRef = builder.addVar(func, ref->type);
auto tempIndex = builder.addVar(func, index->type);
auto* teeRef = builder.makeLocalTee(tempRef, ref, ref->type);
auto* teeIndex = builder.makeLocalTee(tempIndex, index, index->type);
auto* getSize = builder.makeArrayLen(teeRef);
Expression* effectiveIndex = teeIndex;
Expression* getLength = nullptr;
if (length) {
auto tempLength = builder.addVar(func, length->type);
auto* teeLength = builder.makeLocalTee(tempLength, length, length->type);
effectiveIndex = builder.makeBinary(AddInt32, effectiveIndex, teeLength);
getLength = builder.makeLocalGet(tempLength, length->type);
}
struct BoundsCheck {
Expression* condition;
Expression* getRef;
Expression* getIndex;
Expression* getLength = nullptr;
} result = {builder.makeBinary(LtUInt32, effectiveIndex, getSize),
builder.makeLocalGet(tempRef, ref->type),
builder.makeLocalGet(tempIndex, index->type),
getLength};
return result;
}
Expression* TranslateToFuzzReader::makeArrayGet(Type type) {
auto& arrays = typeArrays[type];
assert(!arrays.empty());
auto arrayType = pick(arrays);
auto* ref = makeTrappingRefUse(arrayType);
auto* index = make(Type::i32);
if (allowOOB && oneIn(10)) {
return builder.makeArrayGet(ref, index, type);
}
auto check = makeArrayBoundsCheck(ref, index, funcContext->func, builder);
auto* get = builder.makeArrayGet(check.getRef, check.getIndex, type);
auto* fallback = makeTrivial(type);
return builder.makeIf(check.condition, get, fallback);
}
Expression* TranslateToFuzzReader::makeArraySet(Type type) {
assert(type == Type::none);
if (mutableArrays.empty()) {
return makeTrivial(type);
}
auto arrayType = pick(mutableArrays);
auto elementType = arrayType.getArray().element.type;
auto* index = make(Type::i32);
auto* ref = makeTrappingRefUse(arrayType);
auto* value = make(elementType);
if (allowOOB && oneIn(10)) {
return builder.makeArraySet(ref, index, value);
}
auto check = makeArrayBoundsCheck(ref, index, funcContext->func, builder);
auto* set = builder.makeArraySet(check.getRef, check.getIndex, value);
return builder.makeIf(check.condition, set);
}
Expression* TranslateToFuzzReader::makeArrayBulkMemoryOp(Type type) {
assert(type == Type::none);
if (mutableArrays.empty()) {
return makeTrivial(type);
}
auto arrayType = pick(mutableArrays);
auto element = arrayType.getArray().element;
auto* index = make(Type::i32);
auto* ref = makeTrappingRefUse(arrayType);
if (oneIn(2)) {
auto* value = make(element.type);
auto* length = make(Type::i32);
if (allowOOB && oneIn(10)) {
return builder.makeArrayFill(ref, index, value, length);
}
auto check =
makeArrayBoundsCheck(ref, index, funcContext->func, builder, length);
auto* fill = builder.makeArrayFill(
check.getRef, check.getIndex, value, check.getLength);
return builder.makeIf(check.condition, fill);
} else {
auto srcArrayType = pick(mutableArrays);
auto srcElement = srcArrayType.getArray().element;
if (!Type::isSubType(srcElement.type, element.type) ||
element.packedType != srcElement.packedType) {
srcArrayType = arrayType;
srcElement = element;
}
auto* srcIndex = make(Type::i32);
auto* srcRef = makeTrappingRefUse(srcArrayType);
auto* length = make(Type::i32);
if (allowOOB && oneIn(10)) {
return builder.makeArrayCopy(ref, index, srcRef, srcIndex, length);
}
auto check =
makeArrayBoundsCheck(ref, index, funcContext->func, builder, length);
auto srcCheck = makeArrayBoundsCheck(
srcRef, srcIndex, funcContext->func, builder, check.getLength);
auto* copy = builder.makeArrayCopy(check.getRef,
check.getIndex,
srcCheck.getRef,
srcCheck.getIndex,
srcCheck.getLength);
return builder.makeIf(check.condition,
builder.makeIf(srcCheck.condition, copy));
}
}
Expression* TranslateToFuzzReader::makeI31Get(Type type) {
assert(type == Type::i32);
assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
auto* i31 = makeTrappingRefUse(HeapType::i31);
return builder.makeI31Get(i31, bool(oneIn(2)));
}
Expression* TranslateToFuzzReader::makeThrow(Type type) {
assert(type == Type::unreachable);
if (wasm.tags.empty()) {
addTag();
}
auto* tag = pick(wasm.tags).get();
auto tagType = tag->sig.params;
std::vector<Expression*> operands;
for (auto t : tagType) {
operands.push_back(make(t));
}
return builder.makeThrow(tag, operands);
}
Expression* TranslateToFuzzReader::makeMemoryInit() {
if (!allowMemory) {
return makeTrivial(Type::none);
}
Index segIdx = upTo(wasm.dataSegments.size());
Name segment = wasm.dataSegments[segIdx]->name;
size_t totalSize = wasm.dataSegments[segIdx]->data.size();
size_t offsetVal = upTo(totalSize);
size_t sizeVal = upTo(totalSize - offsetVal);
Expression* dest = makePointer();
Expression* offset = builder.makeConst(int32_t(offsetVal));
Expression* size = builder.makeConst(int32_t(sizeVal));
return builder.makeMemoryInit(
segment, dest, offset, size, wasm.memories[0]->name);
}
Expression* TranslateToFuzzReader::makeDataDrop() {
if (!allowMemory) {
return makeTrivial(Type::none);
}
Index segIdx = upTo(wasm.dataSegments.size());
Name segment = wasm.dataSegments[segIdx]->name;
return builder.makeDataDrop(segment);
}
Expression* TranslateToFuzzReader::makeMemoryCopy() {
if (!allowMemory) {
return makeTrivial(Type::none);
}
Expression* dest = makePointer();
Expression* source = makePointer();
Expression* size = make(wasm.memories[0]->indexType);
return builder.makeMemoryCopy(
dest, source, size, wasm.memories[0]->name, wasm.memories[0]->name);
}
Expression* TranslateToFuzzReader::makeMemoryFill() {
if (!allowMemory) {
return makeTrivial(Type::none);
}
Expression* dest = makePointer();
Expression* value = make(Type::i32);
Expression* size = make(wasm.memories[0]->indexType);
return builder.makeMemoryFill(dest, value, size, wasm.memories[0]->name);
}
Type TranslateToFuzzReader::getSingleConcreteType() {
if (wasm.features.hasReferenceTypes() && !interestingHeapTypes.empty() &&
oneIn(3)) {
auto heapType = pick(interestingHeapTypes);
auto nullability = getNullability();
return Type(heapType, nullability);
}
using WeightedOption = FeatureOptions<Type>::WeightedOption;
return pick(FeatureOptions<Type>()
.add(FeatureSet::MVP,
WeightedOption{Type::i32, VeryImportant},
WeightedOption{Type::i64, VeryImportant},
WeightedOption{Type::f32, VeryImportant},
WeightedOption{Type::f64, VeryImportant})
.add(FeatureSet::SIMD, WeightedOption{Type::v128, Important})
.add(FeatureSet::ReferenceTypes,
Type(HeapType::func, Nullable),
Type(HeapType::ext, Nullable))
.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
Type(HeapType::any, Nullable),
Type(HeapType::eq, Nullable),
Type(HeapType::eq, NonNullable),
Type(HeapType::i31, Nullable),
Type(HeapType::struct_, Nullable),
Type(HeapType::struct_, NonNullable),
Type(HeapType::array, Nullable),
Type(HeapType::array, NonNullable)));
}
Type TranslateToFuzzReader::getReferenceType() {
if (wasm.features.hasReferenceTypes() && !interestingHeapTypes.empty() &&
oneIn(2)) {
auto heapType = pick(interestingHeapTypes);
auto nullability = getNullability();
return Type(heapType, nullability);
}
return pick(FeatureOptions<Type>()
.add(FeatureSet::ReferenceTypes, Type(HeapType::func, Nullable))
.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
Type(HeapType::func, NonNullable),
Type(HeapType::any, NonNullable),
Type(HeapType::eq, Nullable),
Type(HeapType::eq, NonNullable),
Type(HeapType::i31, Nullable),
Type(HeapType::i31, NonNullable),
Type(HeapType::struct_, Nullable),
Type(HeapType::struct_, NonNullable),
Type(HeapType::array, Nullable),
Type(HeapType::array, NonNullable)));
}
Type TranslateToFuzzReader::getEqReferenceType() {
if (oneIn(2) && !interestingHeapTypes.empty()) {
auto heapType = pick(interestingHeapTypes);
if (HeapType::isSubType(heapType, HeapType::eq)) {
auto nullability = getNullability();
return Type(heapType, nullability);
}
}
return pick(
FeatureOptions<Type>().add(FeatureSet::ReferenceTypes | FeatureSet::GC,
Type(HeapType::eq, Nullable),
Type(HeapType::eq, NonNullable),
Type(HeapType::i31, Nullable),
Type(HeapType::i31, NonNullable),
Type(HeapType::struct_, Nullable),
Type(HeapType::struct_, NonNullable),
Type(HeapType::array, Nullable),
Type(HeapType::array, NonNullable)));
}
Type TranslateToFuzzReader::getMVPType() {
return pick(Type::i32, Type::i64, Type::f32, Type::f64);
}
Type TranslateToFuzzReader::getTupleType() {
std::vector<Type> elements;
size_t maxElements = 2 + upTo(MAX_TUPLE_SIZE - 1);
for (size_t i = 0; i < maxElements; ++i) {
auto type = getSingleConcreteType();
if (type.isDefaultable()) {
elements.push_back(type);
}
}
while (elements.size() < 2) {
elements.push_back(getMVPType());
}
return Type(elements);
}
Type TranslateToFuzzReader::getConcreteType() {
if (wasm.features.hasMultivalue() && oneIn(5)) {
return getTupleType();
} else {
return getSingleConcreteType();
}
}
Type TranslateToFuzzReader::getControlFlowType() {
if (oneIn(10)) {
return Type::none;
} else {
return getConcreteType();
}
}
Type TranslateToFuzzReader::getStorableType() {
return pick(
FeatureOptions<Type>()
.add(FeatureSet::MVP, Type::i32, Type::i64, Type::f32, Type::f64)
.add(FeatureSet::SIMD, Type::v128));
}
Type TranslateToFuzzReader::getLoggableType() { return pick(loggableTypes); }
bool TranslateToFuzzReader::isLoggableType(Type type) {
return std::find(loggableTypes.begin(), loggableTypes.end(), type) !=
loggableTypes.end();
}
Nullability TranslateToFuzzReader::getNullability() {
if (wasm.features.hasGC() && oneIn(2)) {
return NonNullable;
}
return Nullable;
}
Nullability TranslateToFuzzReader::getSubType(Nullability nullability) {
if (nullability == NonNullable) {
return NonNullable;
}
return getNullability();
}
HeapType TranslateToFuzzReader::getSubType(HeapType type) {
if (oneIn(3)) {
return type;
}
if (type.isBasic() && oneIn(2)) {
switch (type.getBasic()) {
case HeapType::func:
return pick(FeatureOptions<HeapType>()
.add(FeatureSet::ReferenceTypes, HeapType::func)
.add(FeatureSet::GC, HeapType::nofunc));
case HeapType::ext:
return pick(FeatureOptions<HeapType>()
.add(FeatureSet::ReferenceTypes, HeapType::ext)
.add(FeatureSet::GC, HeapType::noext));
case HeapType::any:
assert(wasm.features.hasReferenceTypes());
assert(wasm.features.hasGC());
return pick(HeapType::any,
HeapType::eq,
HeapType::i31,
HeapType::struct_,
HeapType::array,
HeapType::none);
case HeapType::eq:
assert(wasm.features.hasReferenceTypes());
assert(wasm.features.hasGC());
return pick(HeapType::eq,
HeapType::i31,
HeapType::struct_,
HeapType::array,
HeapType::none);
case HeapType::i31:
return pick(HeapType::i31, HeapType::none);
case HeapType::struct_:
return pick(HeapType::struct_, HeapType::none);
case HeapType::array:
return pick(HeapType::array, HeapType::none);
case HeapType::exn:
return HeapType::exn;
case HeapType::string:
return HeapType::string;
case HeapType::stringview_wtf8:
case HeapType::stringview_wtf16:
case HeapType::stringview_iter:
WASM_UNREACHABLE("TODO: fuzz strings");
case HeapType::none:
case HeapType::noext:
case HeapType::nofunc:
case HeapType::noexn:
break;
}
}
auto iter = interestingHeapSubTypes.find(type);
if (iter != interestingHeapSubTypes.end()) {
auto& subTypes = iter->second;
if (!subTypes.empty()) {
return pick(subTypes);
}
}
return type;
}
Type TranslateToFuzzReader::getSubType(Type type) {
if (type.isTuple()) {
std::vector<Type> types;
for (const auto& t : type) {
types.push_back(getSubType(t));
}
return Type(types);
} else if (type.isRef()) {
auto heapType = getSubType(type.getHeapType());
auto nullability = getSubType(type.getNullability());
auto subType = Type(heapType, nullability);
if (GCTypeUtils::isUninhabitable(subType) &&
!GCTypeUtils::isUninhabitable(type) && !oneIn(20)) {
return type;
}
return subType;
} else {
assert(type.isBasic());
return type;
}
}
Nullability TranslateToFuzzReader::getSuperType(Nullability nullability) {
if (nullability == Nullable) {
return Nullable;
}
return getNullability();
}
HeapType TranslateToFuzzReader::getSuperType(HeapType type) {
std::vector<HeapType> supers;
while (1) {
supers.push_back(type);
if (auto super = type.getDeclaredSuperType()) {
type = *super;
} else {
break;
}
}
return pick(supers);
}
Type TranslateToFuzzReader::getSuperType(Type type) {
auto heapType = getSuperType(type.getHeapType());
auto nullability = getSuperType(type.getNullability());
auto superType = Type(heapType, nullability);
if (GCTypeUtils::isUninhabitable(superType)) {
superType = Type(heapType, Nullable);
}
return superType;
}
Name TranslateToFuzzReader::getTargetName(Expression* target) {
if (auto* block = target->dynCast<Block>()) {
return block->name;
} else if (auto* loop = target->dynCast<Loop>()) {
return loop->name;
}
WASM_UNREACHABLE("unexpected expr type");
}
Type TranslateToFuzzReader::getTargetType(Expression* target) {
if (auto* block = target->dynCast<Block>()) {
return block->type;
} else if (target->is<Loop>()) {
return Type::none;
}
WASM_UNREACHABLE("unexpected expr type");
}
}