diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/support/topological_sort.h | 2 | ||||
-rw-r--r-- | src/tools/fuzzing.h | 2 | ||||
-rw-r--r-- | src/tools/fuzzing/fuzzing.cpp | 84 | ||||
-rw-r--r-- | src/tools/fuzzing/parameters.h | 2 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 17 |
5 files changed, 72 insertions, 35 deletions
diff --git a/src/support/topological_sort.h b/src/support/topological_sort.h index 91353dd37..3594617eb 100644 --- a/src/support/topological_sort.h +++ b/src/support/topological_sort.h @@ -27,7 +27,7 @@ namespace wasm { // CRTP utility that provides an iterator through arbitrary directed acyclic // graphs of data that will visit the data in a topologically sorted order // (https://en.wikipedia.org/wiki/Topological_sorting). In other words, the -// iterator will produce each item only after all that items predecessors have +// iterator will produce each item only after all that item's predecessors have // been produced. // // Subclasses should call `push` on all the root items in their constructors and diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 786527236..75e3a2a9a 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -106,6 +106,8 @@ private: std::unordered_map<Type, std::vector<Name>> globalsByType; std::unordered_map<Type, std::vector<Name>> mutableGlobalsByType; + std::unordered_map<Type, std::vector<Name>> immutableGlobalsByType; + std::unordered_map<Type, std::vector<Name>> importedImmutableGlobalsByType; std::vector<Type> loggableTypes; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 1b1d2cb01..f4f059deb 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -379,26 +379,46 @@ void TranslateToFuzzReader::setupGlobals() { } } + auto useGlobalLater = [&](Global* global) { + auto type = global->type; + auto name = global->name; + globalsByType[type].push_back(name); + if (global->mutable_) { + mutableGlobalsByType[type].push_back(name); + } else { + immutableGlobalsByType[type].push_back(name); + if (global->imported()) { + importedImmutableGlobalsByType[type].push_back(name); + } + } + }; + // Randomly assign some globals from initial content to be ignored for the // fuzzer to use. Such globals will only be used from initial content. This is // important to preserve some real-world patterns, like the "once" pattern in // which a global is used in one function only. (If we randomly emitted gets // and sets of such globals, we'd with very high probability end up breaking // that pattern, and not fuzzing it at all.) - // - // Pick a percentage of initial globals to ignore later down when we decide - // which to allow uses from. - auto numInitialGlobals = wasm.globals.size(); - unsigned percentIgnoredInitialGlobals = 0; - if (numInitialGlobals) { - // Only generate this random number if it will be used. - percentIgnoredInitialGlobals = upTo(100); + if (!wasm.globals.empty()) { + unsigned percentUsedInitialGlobals = upTo(100); + for (auto& global : wasm.globals) { + if (upTo(100) < percentUsedInitialGlobals) { + useGlobalLater(global.get()); + } + } } // Create new random globals. for (size_t index = upTo(MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); - auto* init = makeConst(type); + // Prefer immutable ones as they can be used in global.gets in other + // globals, for more interesting patterns. + auto mutability = oneIn(3) ? Builder::Mutable : Builder::Immutable; + + // We can only make something trivial (like a constant) in a global + // initializer. + auto* init = makeTrivial(type); + if (!FindAll<RefAs>(init).list.empty()) { // When creating this initial value we ended up emitting a RefAs, which // means we had to stop in the middle of an overly-nested struct or array, @@ -407,27 +427,16 @@ void TranslateToFuzzReader::setupGlobals() { // validate in a global. Switch to something safe instead. type = getMVPType(); init = makeConst(type); + } else if (type.isTuple() && !init->is<TupleMake>()) { + // For now we disallow anything but tuple.make at the top level of tuple + // globals (see details in wasm-binary.cpp). In the future we may allow + // global.get or other things here. + init = makeConst(type); + assert(init->is<TupleMake>()); } - auto mutability = oneIn(2) ? Builder::Mutable : Builder::Immutable; auto global = builder.makeGlobal( Names::getValidGlobalName(wasm, "global$"), type, init, mutability); - wasm.addGlobal(std::move(global)); - } - - // Set up data structures for picking globals later for get/set operations. - for (Index i = 0; i < wasm.globals.size(); i++) { - auto& global = wasm.globals[i]; - - // Apply the chance for initial globals to be ignored, see above. - if (i < numInitialGlobals && upTo(100) < percentIgnoredInitialGlobals) { - continue; - } - - // This is a global we can use later, note it. - globalsByType[global->type].push_back(global->name); - if (global->mutable_) { - mutableGlobalsByType[global->type].push_back(global->name); - } + useGlobalLater(wasm.addGlobal(std::move(global))); } } @@ -1477,9 +1486,12 @@ Expression* TranslateToFuzzReader::makeTrivial(Type type) { // using it before it was set, which would trap. if (funcContext && oneIn(type.isNonNullable() ? 10 : 2)) { return makeLocalGet(type); - } else { - return makeConst(type); } + + // Either make a const, or a global.get (which may fail to find a suitable + // global, especially in a non-function context, and if so it will make a + // constant instead). + return oneIn(2) ? makeConst(type) : makeGlobalGet(type); } else if (type == Type::none) { return makeNop(type); } @@ -1900,9 +1912,17 @@ Expression* TranslateToFuzzReader::makeLocalSet(Type type) { } Expression* TranslateToFuzzReader::makeGlobalGet(Type type) { - auto it = globalsByType.find(type); - if (it == globalsByType.end() || it->second.empty()) { - return makeTrivial(type); + // In a non-function context, like in another global, we can only get from an + // immutable global. Whether GC is enabled also matters, as it allows getting + // from a non-import. + auto& relevantGlobals = + funcContext ? globalsByType + : (wasm.features.hasGC() ? immutableGlobalsByType + : importedImmutableGlobalsByType); + auto it = relevantGlobals.find(type); + // If we have no such relevant globals give up and emit a constant instead. + if (it == relevantGlobals.end() || it->second.empty()) { + return makeConst(type); } auto name = pick(it->second); diff --git a/src/tools/fuzzing/parameters.h b/src/tools/fuzzing/parameters.h index 51531681b..eede193a7 100644 --- a/src/tools/fuzzing/parameters.h +++ b/src/tools/fuzzing/parameters.h @@ -30,7 +30,7 @@ constexpr int MAX_PARAMS = 10; constexpr int MAX_VARS = 20; // The maximum number of globals in a module. -constexpr int MAX_GLOBALS = 20; +constexpr int MAX_GLOBALS = 30; // The maximum number of tuple elements. constexpr int MAX_TUPLE_SIZE = 6; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index ec06692a4..805af1892 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -572,8 +572,23 @@ void WasmBinaryWriter::writeGlobals() { o << U32LEB(global->mutable_); if (global->type.size() == 1) { writeExpression(global->init); + } else if (auto* make = global->init->dynCast<TupleMake>()) { + // Emit the proper lane for this global. + writeExpression(make->operands[i]); } else { - writeExpression(global->init->cast<TupleMake>()->operands[i]); + // For now tuple globals must contain tuple.make. We could perhaps + // support more operations, like global.get, but the code would need to + // look something like this: + // + // auto parentIndex = getGlobalIndex(get->name); + // o << int8_t(BinaryConsts::GlobalGet) << U32LEB(parentIndex + i); + // + // That is, we must emit the instruction here, and not defer to + // writeExpression, as writeExpression writes an entire expression at a + // time (and not just one of the lanes). As emitting an instruction here + // is less clean, and there is no important use case for global.get of + // one tuple global to another, we disallow this. + WASM_UNREACHABLE("unsupported tuple global operation"); } o << int8_t(BinaryConsts::End); ++i; |