| /* |
| * Copyright 2017 WebAssembly Community Group participants |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // |
| // Translate a binary stream of bytes into a valid wasm module, *somehow*. |
| // This is helpful for fuzzing. |
| // |
| |
| /* |
| high chance for set at start of loop |
| high chance of get of a set local in the scope of that scope |
| high chance of a tee in that case => loop var |
| */ |
| |
| // TODO Generate exception handling instructions |
| |
| #include "ir/memory-utils.h" |
| #include <ir/find_all.h> |
| #include <ir/literal-utils.h> |
| #include <ir/manipulation.h> |
| #include <ir/utils.h> |
| #include <support/file.h> |
| #include <tools/optimization-options.h> |
| #include <wasm-builder.h> |
| |
| namespace wasm { |
| |
| // helper structs, since list initialization has a fixed order of |
| // evaluation, avoiding UB |
| |
| struct ThreeArgs { |
| Expression* a; |
| Expression* b; |
| Expression* c; |
| }; |
| |
| struct UnaryArgs { |
| UnaryOp a; |
| Expression* b; |
| }; |
| |
| struct BinaryArgs { |
| BinaryOp a; |
| Expression* b; |
| Expression* c; |
| }; |
| |
| // main reader |
| |
| class TranslateToFuzzReader { |
| public: |
| TranslateToFuzzReader(Module& wasm, std::string& filename) |
| : wasm(wasm), builder(wasm) { |
| auto input(read_file<std::vector<char>>(filename, Flags::Binary)); |
| readData(input); |
| } |
| |
| TranslateToFuzzReader(Module& wasm, std::vector<char> input) |
| : wasm(wasm), builder(wasm) { |
| readData(input); |
| } |
| |
| void pickPasses(OptimizationOptions& options) { |
| while (options.passes.size() < 20 && !finishedInput && !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 setAllowMemory(bool allowMemory_) { allowMemory = allowMemory_; } |
| |
| void setAllowOOB(bool allowOOB_) { allowOOB = allowOOB_; } |
| |
| void build() { |
| if (allowMemory) { |
| setupMemory(); |
| } |
| setupTable(); |
| setupGlobals(); |
| if (wasm.features.hasExceptionHandling()) { |
| setupEvents(); |
| } |
| addImportLoggingSupport(); |
| // keep adding functions until we run out of input |
| while (!finishedInput) { |
| auto* func = addFunction(); |
| addInvocations(func); |
| } |
| if (HANG_LIMIT > 0) { |
| addHangLimitSupport(); |
| } |
| finalizeTable(); |
| } |
| |
| private: |
| Module& wasm; |
| Builder builder; |
| std::vector<char> bytes; // the input bytes |
| size_t pos; // the position in the input |
| // whether we already cycled through all the input (if so, we should try to |
| // finish things off) |
| bool finishedInput; |
| |
| // The maximum amount of params to each function. |
| static const int MAX_PARAMS = 10; |
| |
| // The maximum amount of vars in each function. |
| static const int MAX_VARS = 20; |
| |
| // The maximum number of globals in a module. |
| static const int MAX_GLOBALS = 20; |
| |
| // The maximum number of tuple elements. |
| static const int MAX_TUPLE_SIZE = 6; |
| |
| // some things require luck, try them a few times |
| static const int TRIES = 10; |
| |
| // beyond a nesting limit, greatly decrease the chance to continue to nest |
| static const int NESTING_LIMIT = 11; |
| |
| // the maximum size of a block |
| static const int BLOCK_FACTOR = 5; |
| |
| // the memory that we use, a small portion so that we have a good chance of |
| // looking at writes (we also look outside of this region with small |
| // probability) this should be a power of 2 |
| static const int USABLE_MEMORY = 16; |
| |
| // the number of runtime iterations (function calls, loop backbranches) we |
| // allow before we stop execution with a trap, to prevent hangs. 0 means |
| // no hang protection. |
| static const int HANG_LIMIT = 10; |
| |
| // Whether to emit memory operations like loads and stores. |
| bool allowMemory = true; |
| |
| // Whether to emit loads, stores, and call_indirects that may be out |
| // of bounds (which traps in wasm, and is undefined behavior in C). |
| bool allowOOB = true; |
| |
| // Whether to emit atomic waits (which in single-threaded mode, may hang...) |
| static const bool ATOMIC_WAITS = false; |
| |
| // After we finish the input, we start going through it again, but xoring |
| // so it's not identical |
| int xorFactor = 0; |
| |
| // The chance to emit a logging operation for a none expression. We |
| // randomize this in each function. |
| unsigned LOGGING_PERCENT = 0; |
| |
| void readData(std::vector<char> input) { |
| bytes.swap(input); |
| pos = 0; |
| finishedInput = false; |
| // ensure *some* input to be read |
| if (bytes.size() == 0) { |
| bytes.push_back(0); |
| } |
| } |
| |
| int8_t get() { |
| if (pos == bytes.size()) { |
| // we ran out of input, go to the start for more stuff |
| finishedInput = true; |
| pos = 0; |
| xorFactor++; |
| } |
| return bytes[pos++] ^ xorFactor; |
| } |
| |
| int16_t get16() { |
| auto temp = uint16_t(get()) << 8; |
| return temp | uint16_t(get()); |
| } |
| |
| int32_t get32() { |
| auto temp = uint32_t(get16()) << 16; |
| return temp | uint32_t(get16()); |
| } |
| |
| int64_t get64() { |
| auto temp = uint64_t(get32()) << 32; |
| return temp | uint64_t(get32()); |
| } |
| |
| float getFloat() { return Literal(get32()).reinterpretf32(); } |
| |
| double getDouble() { return Literal(get64()).reinterpretf64(); } |
| |
| Type getSubType(Type type) { |
| if (type.isMulti()) { |
| std::vector<Type> types; |
| for (auto& t : type) { |
| types.push_back(getSubType(t)); |
| } |
| return Type(types); |
| } |
| SmallVector<Type, 2> options; |
| options.push_back(type); // includes itself |
| TODO_SINGLE_COMPOUND(type); |
| switch (type.getBasic()) { |
| case Type::externref: |
| if (wasm.features.hasExceptionHandling()) { |
| options.push_back(Type::exnref); |
| } |
| options.push_back(Type::funcref); |
| // falls through |
| case Type::funcref: |
| case Type::exnref: |
| options.push_back(Type::nullref); |
| break; |
| default: |
| break; |
| } |
| return pick(options); |
| } |
| |
| void setupMemory() { |
| // Add memory itself |
| MemoryUtils::ensureExists(wasm.memory); |
| if (wasm.features.hasBulkMemory()) { |
| size_t memCovered = 0; |
| // need at least one segment for memory.inits |
| size_t numSegments = upTo(8) + 1; |
| for (size_t i = 0; i < numSegments; i++) { |
| Memory::Segment segment; |
| 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; |
| } |
| wasm.memory.segments.push_back(segment); |
| } |
| } else { |
| // init some data |
| wasm.memory.segments.emplace_back(builder.makeConst(int32_t(0))); |
| auto num = upTo(USABLE_MEMORY * 2); |
| for (size_t i = 0; i < num; i++) { |
| auto value = upTo(512); |
| wasm.memory.segments[0].data.push_back(value >= 256 ? 0 |
| : (value & 0xff)); |
| } |
| } |
| // Add memory hasher helper (for the hash, see hash.h). The function looks |
| // like: |
| // function hashMemory() { |
| // hash = 5381; |
| // hash = ((hash << 5) + hash) ^ mem[0]; |
| // hash = ((hash << 5) + hash) ^ mem[1]; |
| // .. |
| // return hash; |
| // } |
| std::vector<Expression*> contents; |
| contents.push_back( |
| builder.makeLocalSet(0, builder.makeConst(uint32_t(5381)))); |
| 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(uint32_t(0)), Type::i32)))); |
| } |
| 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)); |
| // Export memory so JS fuzzing can use it |
| wasm.addExport(builder.makeExport("memory", "0", ExternalKind::Memory)); |
| } |
| |
| void setupTable() { |
| wasm.table.exists = true; |
| wasm.table.segments.emplace_back(builder.makeConst(int32_t(0))); |
| } |
| |
| std::map<Type, std::vector<Name>> globalsByType; |
| |
| void setupGlobals() { |
| for (size_t index = upTo(MAX_GLOBALS); index > 0; --index) { |
| auto type = getConcreteType(); |
| auto* global = |
| builder.makeGlobal(std::string("global$") + std::to_string(index), |
| type, |
| makeConst(type), |
| Builder::Mutable); |
| wasm.addGlobal(global); |
| globalsByType[type].push_back(global->name); |
| } |
| } |
| |
| void setupEvents() { |
| Index num = upTo(3); |
| for (size_t i = 0; i < num; i++) { |
| auto* event = |
| builder.makeEvent(std::string("event$") + std::to_string(i), |
| WASM_EVENT_ATTRIBUTE_EXCEPTION, |
| Signature(getControlFlowType(), Type::none)); |
| wasm.addEvent(event); |
| } |
| } |
| |
| void finalizeTable() { |
| wasm.table.initial = wasm.table.segments[0].data.size(); |
| wasm.table.max = |
| oneIn(2) ? Address(Table::kUnlimitedSize) : wasm.table.initial; |
| } |
| |
| const Name HANG_LIMIT_GLOBAL = "hangLimit"; |
| |
| void addHangLimitSupport() { |
| auto* glob = builder.makeGlobal(HANG_LIMIT_GLOBAL, |
| Type::i32, |
| builder.makeConst(int32_t(HANG_LIMIT)), |
| Builder::Mutable); |
| wasm.addGlobal(glob); |
| |
| auto* func = new Function; |
| func->name = "hangLimitInitializer"; |
| func->sig = Signature(Type::none, Type::none); |
| func->body = |
| builder.makeGlobalSet(glob->name, builder.makeConst(int32_t(HANG_LIMIT))); |
| wasm.addFunction(func); |
| |
| auto* export_ = new Export; |
| export_->name = func->name; |
| export_->value = func->name; |
| export_->kind = ExternalKind::Function; |
| wasm.addExport(export_); |
| } |
| |
| void addImportLoggingSupport() { |
| for (auto type : getLoggableTypes()) { |
| auto* func = new Function; |
| Name name = std::string("log-") + type.toString(); |
| func->name = name; |
| func->module = "fuzzing-support"; |
| func->base = name; |
| func->sig = Signature(type, Type::none); |
| wasm.addFunction(func); |
| } |
| } |
| |
| Expression* makeHangLimitCheck() { |
| return builder.makeSequence( |
| builder.makeIf( |
| builder.makeUnary(UnaryOp::EqZInt32, |
| builder.makeGlobalGet(HANG_LIMIT_GLOBAL, Type::i32)), |
| makeTrivial(Type::unreachable)), |
| builder.makeGlobalSet( |
| HANG_LIMIT_GLOBAL, |
| builder.makeBinary(BinaryOp::SubInt32, |
| builder.makeGlobalGet(HANG_LIMIT_GLOBAL, Type::i32), |
| builder.makeConst(int32_t(1))))); |
| } |
| |
| // function generation state |
| |
| Function* func = nullptr; |
| std::vector<Expression*> breakableStack; // things we can break to |
| Index labelIndex; |
| |
| // a list of things relevant to computing the odds of an infinite loop, |
| // which we try to minimize the risk of |
| std::vector<Expression*> hangStack; |
| |
| std::map<Type, std::vector<Index>> |
| typeLocals; // type => list of locals with that type |
| |
| Function* addFunction() { |
| LOGGING_PERCENT = upToSquared(100); |
| Index num = wasm.functions.size(); |
| func = new Function; |
| func->name = std::string("func_") + std::to_string(num); |
| assert(typeLocals.empty()); |
| Index numParams = upToSquared(MAX_PARAMS); |
| std::vector<Type> params; |
| params.reserve(numParams); |
| for (Index i = 0; i < numParams; i++) { |
| auto type = getSingleConcreteType(); |
| typeLocals[type].push_back(params.size()); |
| params.push_back(type); |
| } |
| func->sig = Signature(Type(params), getControlFlowType()); |
| Index numVars = upToSquared(MAX_VARS); |
| for (Index i = 0; i < numVars; i++) { |
| auto type = getConcreteType(); |
| typeLocals[type].push_back(params.size() + func->vars.size()); |
| func->vars.push_back(type); |
| } |
| labelIndex = 0; |
| assert(breakableStack.empty()); |
| assert(hangStack.empty()); |
| // with small chance, make the body unreachable |
| auto bodyType = func->sig.results; |
| if (oneIn(10)) { |
| bodyType = Type::unreachable; |
| } |
| // with reasonable chance make the body a block |
| if (oneIn(2)) { |
| func->body = makeBlock(bodyType); |
| } else { |
| func->body = make(bodyType); |
| } |
| // Our OOB checks are already in the code, and if we recombine/mutate we |
| // may end up breaking them. TODO: do them after the fact, like with the |
| // hang limit checks. |
| if (allowOOB) { |
| // Recombinations create duplicate code patterns. |
| recombine(func); |
| // Mutations add random small changes, which can subtly break duplicate |
| // code patterns. |
| mutate(func); |
| // TODO: liveness operations on gets, with some prob alter a get to one |
| // with more possible sets. |
| // Recombination, mutation, etc. can break validation; fix things up |
| // after. |
| fixLabels(func); |
| } |
| // Add hang limit checks after all other operations on the function body. |
| if (HANG_LIMIT > 0) { |
| addHangLimitChecks(func); |
| } |
| assert(breakableStack.empty()); |
| assert(hangStack.empty()); |
| wasm.addFunction(func); |
| // export some, but not all (to allow inlining etc.). make sure to |
| // export at least one, though, to keep each testcase interesting |
| if (num == 0 || oneIn(2)) { |
| auto* export_ = new Export; |
| export_->name = func->name; |
| export_->value = func->name; |
| export_->kind = ExternalKind::Function; |
| wasm.addExport(export_); |
| } |
| // add some to the table |
| while (oneIn(3) && !finishedInput) { |
| wasm.table.segments[0].data.push_back(func->name); |
| } |
| // cleanup |
| typeLocals.clear(); |
| return func; |
| } |
| |
| void addHangLimitChecks(Function* func) { |
| // loop limit |
| FindAll<Loop> loops(func->body); |
| for (auto* loop : loops.list) { |
| loop->body = |
| builder.makeSequence(makeHangLimitCheck(), loop->body, loop->type); |
| } |
| // recursion limit |
| func->body = |
| builder.makeSequence(makeHangLimitCheck(), func->body, func->sig.results); |
| } |
| |
| void recombine(Function* func) { |
| // Don't always do this. |
| if (oneIn(2)) { |
| return; |
| } |
| // First, scan and group all expressions by type. |
| struct Scanner |
| : public PostWalker<Scanner, UnifiedExpressionVisitor<Scanner>> { |
| // A map of all expressions, categorized by type. |
| std::map<Type, std::vector<Expression*>> exprsByType; |
| |
| void visitExpression(Expression* curr) { |
| exprsByType[curr->type].push_back(curr); |
| } |
| }; |
| Scanner scanner; |
| scanner.walk(func->body); |
| // Potentially trim the list of possible picks, so replacements are more |
| // likely to collide. |
| 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); |
| } |
| // Replace them with copies, to avoid a copy into one altering another copy |
| for (auto& pair : scanner.exprsByType) { |
| for (auto*& item : pair.second) { |
| item = ExpressionManipulator::copy(item, wasm); |
| } |
| } |
| // Second, with some probability replace an item with another item having |
| // the same type. (This is not always valid due to nesting of labels, but |
| // we'll fix that up later.) |
| 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)) { |
| // Replace it! |
| auto& candidates = scanner.exprsByType[curr->type]; |
| assert(!candidates.empty()); // this expression itself must be there |
| replaceCurrent( |
| ExpressionManipulator::copy(parent.pick(candidates), wasm)); |
| } |
| } |
| }; |
| Modder modder(wasm, scanner, *this); |
| modder.walk(func->body); |
| } |
| |
| void mutate(Function* func) { |
| // Don't always do this. |
| if (oneIn(2)) { |
| return; |
| } |
| struct Modder |
| : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> { |
| Module& wasm; |
| TranslateToFuzzReader& parent; |
| |
| Modder(Module& wasm, TranslateToFuzzReader& parent) |
| : wasm(wasm), parent(parent) {} |
| |
| void visitExpression(Expression* curr) { |
| if (parent.oneIn(10)) { |
| // Replace it! |
| // (This is not always valid due to nesting of labels, but |
| // we'll fix that up later.) |
| replaceCurrent(parent.make(curr->type)); |
| } |
| } |
| }; |
| Modder modder(wasm, *this); |
| modder.walk(func->body); |
| } |
| |
| // Fix up changes that may have broken validation - types are correct in our |
| // modding, but not necessarily labels. |
| void fixLabels(Function* func) { |
| struct Fixer : public ControlFlowWalker<Fixer> { |
| Module& wasm; |
| TranslateToFuzzReader& parent; |
| |
| Fixer(Module& wasm, TranslateToFuzzReader& parent) |
| : wasm(wasm), parent(parent) {} |
| |
| // Track seen names to find duplication, which is invalid. |
| std::set<Name> seen; |
| |
| void visitBlock(Block* curr) { |
| if (curr->name.is()) { |
| if (seen.count(curr->name)) { |
| replace(); |
| } else { |
| seen.insert(curr->name); |
| } |
| } |
| } |
| |
| void visitLoop(Loop* curr) { |
| if (curr->name.is()) { |
| if (seen.count(curr->name)) { |
| replace(); |
| } else { |
| seen.insert(curr->name); |
| } |
| } |
| } |
| |
| void visitSwitch(Switch* curr) { |
| for (auto name : curr->targets) { |
| if (replaceIfInvalid(name)) { |
| return; |
| } |
| } |
| replaceIfInvalid(curr->default_); |
| } |
| |
| void visitBreak(Break* curr) { replaceIfInvalid(curr->name); } |
| |
| bool replaceIfInvalid(Name target) { |
| if (!hasBreakTarget(target)) { |
| // There is no valid parent, replace with something trivially safe. |
| replace(); |
| return true; |
| } |
| return false; |
| } |
| |
| 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]; |
| if (Block* block = curr->dynCast<Block>()) { |
| if (name == block->name) { |
| return true; |
| } |
| } else if (Loop* loop = curr->dynCast<Loop>()) { |
| if (name == loop->name) { |
| return true; |
| } |
| } else { |
| // an if, ignorable |
| assert(curr->is<If>()); |
| } |
| if (i == 0) { |
| return false; |
| } |
| i--; |
| } |
| } |
| }; |
| Fixer fixer(wasm, *this); |
| fixer.walk(func->body); |
| ReFinalize().walkFunctionInModule(func, &wasm); |
| } |
| |
| // the fuzzer external interface sends in zeros (simpler to compare |
| // across invocations from JS or wasm-opt etc.). Add invocations in |
| // the wasm, so they run everywhere |
| void addInvocations(Function* func) { |
| std::vector<Expression*> invocations; |
| while (oneIn(2) && !finishedInput) { |
| std::vector<Expression*> args; |
| for (auto& type : func->sig.params) { |
| args.push_back(makeConst(type)); |
| } |
| Expression* invoke = |
| builder.makeCall(func->name, args, func->sig.results); |
| if (func->sig.results.isConcrete()) { |
| invoke = builder.makeDrop(invoke); |
| } |
| invocations.push_back(invoke); |
| // log out memory in some cases |
| if (oneIn(2)) { |
| invocations.push_back(makeMemoryHashLogging()); |
| } |
| } |
| if (invocations.empty()) { |
| return; |
| } |
| auto* invoker = new Function; |
| invoker->name = func->name.str + std::string("_invoker"); |
| invoker->sig = Signature(Type::none, Type::none); |
| invoker->body = builder.makeBlock(invocations); |
| wasm.addFunction(invoker); |
| auto* export_ = new Export; |
| export_->name = invoker->name; |
| export_->value = invoker->name; |
| export_->kind = ExternalKind::Function; |
| wasm.addExport(export_); |
| } |
| |
| Name makeLabel() { |
| return std::string("label$") + std::to_string(labelIndex++); |
| } |
| |
| // Weighting for the core make* methods. Some nodes are important enough that |
| // we should do them quite often. |
| static const size_t VeryImportant = 4; |
| static const size_t Important = 2; |
| |
| // always call the toplevel make(type) command, not the internal specific ones |
| |
| int nesting = 0; |
| |
| Expression* make(Type type) { |
| // when we should stop, emit something small (but not necessarily trivial) |
| if (finishedInput || nesting >= 5 * NESTING_LIMIT || // hard 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(); |
| } |
| // we should create the right type of thing |
| assert(Type::isSubType(ret->type, type)); |
| nesting--; |
| return ret; |
| } |
| |
| Expression* _makeConcrete(Type type) { |
| bool canMakeControlFlow = |
| !type.isMulti() || wasm.features.has(FeatureSet::Multivalue); |
| 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); |
| } |
| 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); |
| } |
| if (type.isMulti()) { |
| options.add(FeatureSet::Multivalue, &Self::makeTupleMake); |
| } |
| return (this->*pick(options))(type); |
| } |
| |
| Expression* _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); |
| return (this->*pick(options))(Type::none); |
| } |
| |
| Expression* _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); |
| return (this->*pick(options))(Type::unreachable); |
| } |
| |
| // make something with no chance of infinite recursion |
| Expression* makeTrivial(Type type) { |
| if (type.isConcrete()) { |
| if (oneIn(2)) { |
| return makeLocalGet(type); |
| } else { |
| return makeConst(type); |
| } |
| } else if (type == Type::none) { |
| return makeNop(type); |
| } |
| assert(type == Type::unreachable); |
| Expression* ret = nullptr; |
| if (func->sig.results.isConcrete()) { |
| ret = makeTrivial(func->sig.results); |
| } |
| return builder.makeReturn(ret); |
| } |
| |
| // specific expression creators |
| |
| Expression* makeBlock(Type type) { |
| auto* ret = builder.makeBlock(); |
| ret->type = type; // so we have it during child creation |
| ret->name = makeLabel(); |
| breakableStack.push_back(ret); |
| Index num = upToSquared(BLOCK_FACTOR - 1); // we add another later |
| if (nesting >= NESTING_LIMIT / 2) { |
| // smaller blocks past the limit |
| num /= 2; |
| if (nesting >= NESTING_LIMIT && oneIn(2)) { |
| // smaller blocks past the limit |
| num /= 2; |
| } |
| } |
| // not likely to have a block of size 1 |
| if (num == 0 && !oneIn(10)) { |
| num++; |
| } |
| while (num > 0 && !finishedInput) { |
| ret->list.push_back(make(Type::none)); |
| num--; |
| } |
| // give a chance to make the final element an unreachable break, instead |
| // of concrete - a common pattern (branch to the top of a loop etc.) |
| if (!finishedInput && type.isConcrete() && oneIn(2)) { |
| ret->list.push_back(makeBreak(Type::unreachable)); |
| } else { |
| ret->list.push_back(make(type)); |
| } |
| breakableStack.pop_back(); |
| if (type.isConcrete()) { |
| ret->finalize(type); |
| } else { |
| ret->finalize(); |
| } |
| if (ret->type != type) { |
| // e.g. we might want an unreachable block, but a child breaks to it |
| assert(type == Type::unreachable && ret->type == Type::none); |
| return builder.makeSequence(ret, make(Type::unreachable)); |
| } |
| return ret; |
| } |
| |
| Expression* makeLoop(Type type) { |
| auto* ret = wasm.allocator.alloc<Loop>(); |
| ret->type = type; // so we have it during child creation |
| ret->name = makeLabel(); |
| breakableStack.push_back(ret); |
| hangStack.push_back(ret); |
| // either create random content, or do something more targeted |
| if (oneIn(2)) { |
| ret->body = makeMaybeBlock(type); |
| } else { |
| // ensure a branch back. also optionally create some loop vars |
| std::vector<Expression*> list; |
| list.push_back(makeMaybeBlock(Type::none)); // primary contents |
| // possible branch back |
| list.push_back(builder.makeBreak(ret->name, nullptr, makeCondition())); |
| list.push_back(make(type)); // final element, so we have the right type |
| ret->body = builder.makeBlock(list, type); |
| } |
| breakableStack.pop_back(); |
| hangStack.pop_back(); |
| ret->finalize(type); |
| return ret; |
| } |
| |
| Expression* makeCondition() { |
| // we want a 50-50 chance for the condition to be taken, for interesting |
| // execution paths. by itself, there is bias (e.g. most consts are "yes") |
| // so even that out with noise |
| auto* ret = make(Type::i32); |
| if (oneIn(2)) { |
| ret = builder.makeUnary(UnaryOp::EqZInt32, ret); |
| } |
| return ret; |
| } |
| |
| // make something, with a good chance of it being a block |
| Expression* makeMaybeBlock(Type type) { |
| // if past the limit, prefer not to emit blocks |
| if (nesting >= NESTING_LIMIT || oneIn(3)) { |
| return make(type); |
| } else { |
| return makeBlock(type); |
| } |
| } |
| |
| Expression* buildIf(const struct ThreeArgs& args, Type type) { |
| return builder.makeIf(args.a, args.b, args.c, type); |
| } |
| |
| Expression* makeIf(Type type) { |
| auto* condition = makeCondition(); |
| hangStack.push_back(nullptr); |
| auto* ret = |
| buildIf({condition, makeMaybeBlock(type), makeMaybeBlock(type)}, type); |
| hangStack.pop_back(); |
| return ret; |
| } |
| |
| Expression* makeBreak(Type type) { |
| if (breakableStack.empty()) { |
| return makeTrivial(type); |
| } |
| Expression* condition = nullptr; |
| if (type != Type::unreachable) { |
| hangStack.push_back(nullptr); |
| condition = makeCondition(); |
| } |
| // we need to find a proper target to break to; try a few times |
| int tries = TRIES; |
| while (tries-- > 0) { |
| auto* target = pick(breakableStack); |
| auto name = getTargetName(target); |
| auto valueType = getTargetType(target); |
| if (type.isConcrete()) { |
| // we are flowing out a value |
| if (valueType != type) { |
| // we need to break to a proper place |
| continue; |
| } |
| auto* ret = builder.makeBreak(name, make(type), condition); |
| hangStack.pop_back(); |
| return ret; |
| } else if (type == Type::none) { |
| if (valueType != Type::none) { |
| // we need to break to a proper place |
| continue; |
| } |
| auto* ret = builder.makeBreak(name, nullptr, condition); |
| hangStack.pop_back(); |
| return ret; |
| } else { |
| assert(type == Type::unreachable); |
| if (valueType != Type::none) { |
| // we need to break to a proper place |
| continue; |
| } |
| // we are about to make an *un*conditional break. if it is |
| // to a loop, we prefer there to be a condition along the |
| // way, to reduce the chance of infinite looping |
| size_t conditions = 0; |
| int i = hangStack.size(); |
| while (--i >= 0) { |
| auto* item = hangStack[i]; |
| if (item == nullptr) { |
| conditions++; |
| } else if (auto* loop = item->cast<Loop>()) { |
| if (loop->name == name) { |
| // we found the target, no more conditions matter |
| 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); |
| } |
| } |
| // we failed to find something |
| if (type != Type::unreachable) { |
| hangStack.pop_back(); |
| } |
| return makeTrivial(type); |
| } |
| |
| Expression* makeCall(Type type) { |
| // seems ok, go on |
| int tries = TRIES; |
| bool isReturn; |
| while (tries-- > 0) { |
| Function* target = func; |
| if (!wasm.functions.empty() && !oneIn(wasm.functions.size())) { |
| target = pick(wasm.functions).get(); |
| } |
| isReturn = type == Type::unreachable && wasm.features.hasTailCall() && |
| func->sig.results == target->sig.results; |
| if (target->sig.results != type && !isReturn) { |
| continue; |
| } |
| // we found one! |
| std::vector<Expression*> args; |
| for (auto& argType : target->sig.params) { |
| args.push_back(make(argType)); |
| } |
| return builder.makeCall(target->name, args, type, isReturn); |
| } |
| // we failed to find something |
| return make(type); |
| } |
| |
| Expression* makeCallIndirect(Type type) { |
| auto& data = wasm.table.segments[0].data; |
| if (data.empty()) { |
| return make(type); |
| } |
| // look for a call target with the right type |
| Index start = upTo(data.size()); |
| Index i = start; |
| Function* targetFn; |
| bool isReturn; |
| while (1) { |
| // TODO: handle unreachable |
| targetFn = wasm.getFunction(data[i]); |
| isReturn = type == Type::unreachable && wasm.features.hasTailCall() && |
| func->sig.results == targetFn->sig.results; |
| if (targetFn->sig.results == type || isReturn) { |
| break; |
| } |
| i++; |
| if (i == data.size()) { |
| i = 0; |
| } |
| if (i == start) { |
| return make(type); |
| } |
| } |
| // with high probability, make sure the type is valid otherwise, most are |
| // going to trap |
| Expression* target; |
| if (!allowOOB || !oneIn(10)) { |
| target = builder.makeConst(int32_t(i)); |
| } else { |
| target = make(Type::i32); |
| } |
| std::vector<Expression*> args; |
| for (auto& type : targetFn->sig.params) { |
| args.push_back(make(type)); |
| } |
| return builder.makeCallIndirect(target, args, targetFn->sig, isReturn); |
| } |
| |
| Expression* makeLocalGet(Type type) { |
| auto& locals = typeLocals[type]; |
| if (locals.empty()) { |
| return makeConst(type); |
| } |
| return builder.makeLocalGet(pick(locals), type); |
| } |
| |
| Expression* makeLocalSet(Type type) { |
| bool tee = type != Type::none; |
| Type valueType; |
| if (tee) { |
| valueType = type; |
| } else { |
| valueType = getConcreteType(); |
| } |
| auto& locals = 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* makeGlobalGet(Type type) { |
| auto it = globalsByType.find(type); |
| if (it == globalsByType.end() || it->second.empty()) { |
| return makeConst(type); |
| } |
| return builder.makeGlobalGet(pick(it->second), type); |
| } |
| |
| Expression* makeGlobalSet(Type type) { |
| assert(type == Type::none); |
| type = getConcreteType(); |
| auto it = globalsByType.find(type); |
| if (it == globalsByType.end() || it->second.empty()) { |
| return makeTrivial(Type::none); |
| } |
| auto* value = make(type); |
| return builder.makeGlobalSet(pick(it->second), value); |
| } |
| |
| Expression* makeTupleMake(Type type) { |
| assert(wasm.features.hasMultivalue()); |
| assert(type.isMulti()); |
| std::vector<Expression*> elements; |
| for (auto& t : type) { |
| elements.push_back(make(t)); |
| } |
| return builder.makeTupleMake(std::move(elements)); |
| } |
| |
| Expression* makeTupleExtract(Type type) { |
| assert(wasm.features.hasMultivalue()); |
| assert(type.isSingle() && type.isConcrete()); |
| Type tupleType = getTupleType(); |
| |
| // Find indices from which we can extract `type` |
| std::vector<size_t> extractIndices; |
| size_t i = 0; |
| for (auto& t : tupleType) { |
| if (t == type) { |
| extractIndices.push_back(i); |
| } |
| ++i; |
| } |
| |
| // If there are none, inject one |
| 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* makePointer() { |
| auto* ret = make(Type::i32); |
| // with high probability, mask the pointer so it's in a reasonable |
| // range. otherwise, most pointers are going to be out of range and |
| // most memory ops will just trap |
| if (!allowOOB || !oneIn(10)) { |
| ret = builder.makeBinary( |
| AndInt32, ret, builder.makeConst(int32_t(USABLE_MEMORY - 1))); |
| } |
| return ret; |
| } |
| |
| Expression* 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); |
| case 1: |
| return builder.makeLoad(2, signed_, offset, pick(1, 2), ptr, type); |
| case 2: |
| return builder.makeLoad( |
| 4, signed_, offset, pick(1, 2, 4), ptr, type); |
| } |
| 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); |
| case 1: |
| return builder.makeLoad(2, signed_, offset, pick(1, 2), ptr, type); |
| case 2: |
| return builder.makeLoad( |
| 4, signed_, offset, pick(1, 2, 4), ptr, type); |
| case 3: |
| return builder.makeLoad( |
| 8, signed_, offset, pick(1, 2, 4, 8), ptr, type); |
| } |
| WASM_UNREACHABLE("unexpected value"); |
| } |
| case Type::f32: { |
| return builder.makeLoad(4, false, offset, pick(1, 2, 4), ptr, type); |
| } |
| case Type::f64: { |
| return builder.makeLoad(8, false, offset, pick(1, 2, 4, 8), ptr, type); |
| } |
| case Type::v128: { |
| if (!wasm.features.hasSIMD()) { |
| return makeTrivial(type); |
| } |
| return builder.makeLoad( |
| 16, false, offset, pick(1, 2, 4, 8, 16), ptr, type); |
| } |
| case Type::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| case Type::none: |
| case Type::unreachable: |
| WASM_UNREACHABLE("invalid type"); |
| } |
| WASM_UNREACHABLE("invalid type"); |
| } |
| |
| Expression* makeLoad(Type type) { |
| // reference types cannot be stored in memory |
| 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; |
| } |
| // make it atomic |
| auto* load = ret->cast<Load>(); |
| wasm.memory.shared = true; |
| load->isAtomic = true; |
| load->signed_ = false; |
| load->align = load->bytes; |
| return load; |
| } |
| |
| Expression* makeNonAtomicStore(Type type) { |
| if (type == Type::unreachable) { |
| // make a normal store, then make it 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->finalize(); |
| return store; |
| } |
| // the type is none or unreachable. we also need to pick the value |
| // type. |
| 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); |
| case 1: |
| return builder.makeStore(2, offset, pick(1, 2), ptr, value, type); |
| case 2: |
| return builder.makeStore( |
| 4, offset, pick(1, 2, 4), ptr, value, type); |
| } |
| WASM_UNREACHABLE("invalid value"); |
| } |
| case Type::i64: { |
| switch (upTo(4)) { |
| case 0: |
| return builder.makeStore(1, offset, 1, ptr, value, type); |
| case 1: |
| return builder.makeStore(2, offset, pick(1, 2), ptr, value, type); |
| case 2: |
| return builder.makeStore( |
| 4, offset, pick(1, 2, 4), ptr, value, type); |
| case 3: |
| return builder.makeStore( |
| 8, offset, pick(1, 2, 4, 8), ptr, value, type); |
| } |
| WASM_UNREACHABLE("invalid value"); |
| } |
| case Type::f32: { |
| return builder.makeStore(4, offset, pick(1, 2, 4), ptr, value, type); |
| } |
| case Type::f64: { |
| return builder.makeStore(8, offset, pick(1, 2, 4, 8), ptr, value, type); |
| } |
| case Type::v128: { |
| if (!wasm.features.hasSIMD()) { |
| return makeTrivial(type); |
| } |
| return builder.makeStore( |
| 16, offset, pick(1, 2, 4, 8, 16), ptr, value, type); |
| } |
| case Type::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| case Type::none: |
| case Type::unreachable: |
| WASM_UNREACHABLE("invalid type"); |
| } |
| WASM_UNREACHABLE("invalid type"); |
| } |
| |
| Expression* 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; |
| } |
| // make it atomic |
| wasm.memory.shared = true; |
| store->isAtomic = true; |
| store->align = store->bytes; |
| return store; |
| } |
| |
| Literal makeLiteral(Type type) { |
| if (type == Type::v128) { |
| // generate each lane individually for random lane interpretation |
| 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"); |
| } |
| } |
| |
| // Optional tweaking of the value by a small adjustment. |
| auto tweak = [this, type](Literal value) { |
| // +- 1 |
| switch (upTo(5)) { |
| case 0: |
| value = value.add(Literal::makeFromInt32(-1, type)); |
| break; |
| case 1: |
| value = value.add(Literal::makeFromInt32(1, type)); |
| break; |
| default: { |
| } |
| } |
| // For floats, optionally add a non-integer adjustment in +- [-1, 1] |
| if (type.isFloat() && oneIn(2)) { |
| const int RANGE = 1000; |
| auto RANGE_LITERAL = Literal::makeFromInt32(RANGE, type); |
| // adjustment -> [0, 2 * RANGE] |
| auto adjustment = Literal::makeFromInt32(upTo(2 * RANGE + 1), type); |
| // adjustment -> [-RANGE, RANGE] |
| adjustment = adjustment.sub(RANGE_LITERAL); |
| // adjustment -> [-1, 1] |
| adjustment = adjustment.div(RANGE_LITERAL); |
| value = value.add(adjustment); |
| } |
| // Flip sign. |
| if (oneIn(2)) { |
| value = value.mul(Literal::makeFromInt32(-1, type)); |
| } |
| return value; |
| }; |
| |
| switch (upTo(4)) { |
| case 0: { |
| // totally random, entire range |
| 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::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| case Type::none: |
| case Type::unreachable: |
| WASM_UNREACHABLE("invalid type"); |
| } |
| break; |
| } |
| case 1: { |
| // small range |
| 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::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| case Type::none: |
| case Type::unreachable: |
| WASM_UNREACHABLE("unexpected type"); |
| } |
| break; |
| } |
| case 2: { |
| // special values |
| 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, |
| 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, |
| 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::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| case Type::none: |
| case Type::unreachable: |
| WASM_UNREACHABLE("unexpected type"); |
| } |
| return tweak(value); |
| } |
| case 3: { |
| // powers of 2 |
| 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::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| case Type::none: |
| case Type::unreachable: |
| WASM_UNREACHABLE("unexpected type"); |
| } |
| return tweak(value); |
| } |
| } |
| WASM_UNREACHABLE("invalid value"); |
| } |
| |
| Expression* makeConst(Type type) { |
| if (type.isRef()) { |
| assert(wasm.features.hasReferenceTypes()); |
| // Check if we can use ref.func. |
| // 'func' is the pointer to the last created function and can be null when |
| // we set up globals (before we create any functions), in which case we |
| // can't use ref.func. |
| if (type == Type::funcref && func && oneIn(2)) { |
| // First set to target to the last created function, and try to select |
| // among other existing function if possible |
| Function* target = func; |
| if (!wasm.functions.empty() && !oneIn(wasm.functions.size())) { |
| target = pick(wasm.functions).get(); |
| } |
| return builder.makeRefFunc(target->name); |
| } |
| return builder.makeRefNull(); |
| } |
| if (type.isMulti()) { |
| std::vector<Expression*> operands; |
| for (auto& t : type) { |
| operands.push_back(makeConst(t)); |
| } |
| return builder.makeTupleMake(std::move(operands)); |
| } |
| auto* ret = wasm.allocator.alloc<Const>(); |
| ret->value = makeLiteral(type); |
| ret->type = type; |
| return ret; |
| } |
| |
| Expression* buildUnary(const UnaryArgs& args) { |
| return builder.makeUnary(args.a, args.b); |
| } |
| |
| Expression* makeUnary(Type type) { |
| assert(!type.isMulti()); |
| if (type == Type::unreachable) { |
| if (auto* unary = makeUnary(getSingleConcreteType())->dynCast<Unary>()) { |
| return builder.makeUnary(unary->op, make(Type::unreachable)); |
| } |
| // give up |
| return makeTrivial(type); |
| } |
| // There's no unary ops for reference types |
| if (type.isRef()) { |
| return makeTrivial(type); |
| } |
| |
| switch (type.getBasic()) { |
| case Type::i32: { |
| auto singleConcreteType = getSingleConcreteType(); |
| TODO_SINGLE_COMPOUND(singleConcreteType); |
| 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(AnyTrueVecI8x16, |
| AllTrueVecI8x16, |
| AnyTrueVecI16x8, |
| AllTrueVecI16x8, |
| AnyTrueVecI32x4, |
| AllTrueVecI32x4, |
| AnyTrueVecI64x2, |
| AllTrueVecI64x2), |
| make(Type::v128)}); |
| } |
| case Type::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| return makeTrivial(type); |
| 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, |
| TruncSatSVecF64x2ToVecI64x2, |
| TruncSatUVecF64x2ToVecI64x2, |
| ConvertSVecI32x4ToVecF32x4, |
| ConvertUVecI32x4ToVecF32x4, |
| ConvertSVecI64x2ToVecF64x2, |
| ConvertUVecI64x2ToVecF64x2, |
| WidenLowSVecI8x16ToVecI16x8, |
| WidenHighSVecI8x16ToVecI16x8, |
| WidenLowUVecI8x16ToVecI16x8, |
| WidenHighUVecI8x16ToVecI16x8, |
| WidenLowSVecI16x8ToVecI32x4, |
| WidenHighSVecI16x8ToVecI32x4, |
| WidenLowUVecI16x8ToVecI32x4, |
| WidenHighUVecI16x8ToVecI32x4), |
| make(Type::v128)}); |
| } |
| WASM_UNREACHABLE("invalid value"); |
| } |
| case Type::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| case Type::none: |
| case Type::unreachable: |
| WASM_UNREACHABLE("unexpected type"); |
| } |
| WASM_UNREACHABLE("invalid type"); |
| } |
| |
| Expression* buildBinary(const BinaryArgs& args) { |
| return builder.makeBinary(args.a, args.b, args.c); |
| } |
| |
| Expression* makeBinary(Type type) { |
| assert(!type.isMulti()); |
| if (type == Type::unreachable) { |
| if (auto* binary = |
| makeBinary(getSingleConcreteType())->dynCast<Binary>()) { |
| return buildBinary( |
| {binary->op, make(Type::unreachable), make(Type::unreachable)}); |
| } |
| // give up |
| return makeTrivial(type); |
| } |
| // There's no binary ops for reference types |
| 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, |
| MulVecI8x16, |
| 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, |
| SwizzleVec8x16), |
| make(Type::v128), |
| make(Type::v128)}); |
| } |
| case Type::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| case Type::none: |
| case Type::unreachable: |
| WASM_UNREACHABLE("unexpected type"); |
| } |
| WASM_UNREACHABLE("invalid type"); |
| } |
| |
| Expression* buildSelect(const ThreeArgs& args, Type type) { |
| return builder.makeSelect(args.a, args.b, args.c, type); |
| } |
| |
| Expression* makeSelect(Type type) { |
| Type subType1 = getSubType(type); |
| Type subType2 = getSubType(type); |
| return buildSelect({make(Type::i32), make(subType1), make(subType2)}, type); |
| } |
| |
| Expression* makeSwitch(Type type) { |
| assert(type == Type::unreachable); |
| if (breakableStack.empty()) { |
| return make(type); |
| } |
| // we need to find proper targets to break to; try a bunch |
| int tries = TRIES; |
| std::vector<Name> names; |
| Type valueType = Type::unreachable; |
| while (tries-- > 0) { |
| auto* target = pick(breakableStack); |
| auto name = getTargetName(target); |
| auto currValueType = getTargetType(target); |
| if (names.empty()) { |
| valueType = currValueType; |
| } else { |
| if (valueType != currValueType) { |
| continue; // all values must be the same |
| } |
| } |
| names.push_back(name); |
| } |
| if (names.size() < 2) { |
| // we failed to find enough |
| 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* makeDrop(Type type) { |
| return builder.makeDrop( |
| make(type == Type::unreachable ? type : getConcreteType())); |
| } |
| |
| Expression* makeReturn(Type type) { |
| return builder.makeReturn( |
| func->sig.results.isConcrete() ? make(func->sig.results) : nullptr); |
| } |
| |
| Expression* makeNop(Type type) { |
| assert(type == Type::none); |
| return builder.makeNop(); |
| } |
| |
| Expression* makeUnreachable(Type type) { |
| assert(type == Type::unreachable); |
| return builder.makeUnreachable(); |
| } |
| |
| Expression* makeAtomic(Type type) { |
| assert(wasm.features.hasAtomics()); |
| if (!allowMemory) { |
| return makeTrivial(type); |
| } |
| wasm.memory.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())); |
| } else { |
| auto* ptr = makePointer(); |
| auto* count = make(Type::i32); |
| return builder.makeAtomicNotify(ptr, count, logify(get())); |
| } |
| } |
| 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(AtomicRMWOp::Add, |
| AtomicRMWOp::Sub, |
| AtomicRMWOp::And, |
| AtomicRMWOp::Or, |
| AtomicRMWOp::Xor, |
| AtomicRMWOp::Xchg), |
| bytes, |
| offset, |
| ptr, |
| value, |
| type); |
| } else { |
| auto* expected = make(type); |
| auto* replacement = make(type); |
| return builder.makeAtomicCmpxchg( |
| bytes, offset, ptr, expected, replacement, type); |
| } |
| } |
| |
| Expression* 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* 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::funcref: |
| case Type::externref: |
| case Type::nullref: |
| case Type::exnref: |
| 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* 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* 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* makeSIMDTernary() { |
| // TODO: Enable qfma/qfms once it is implemented in V8 and the interpreter |
| // SIMDTernaryOp op = pick(Bitselect, |
| // QFMAF32x4, |
| // QFMSF32x4, |
| // QFMAF64x2, |
| // QFMSF64x2); |
| 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* 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* makeSIMDLoad() { |
| // TODO: add Load{32,64}Zero if merged to proposal |
| SIMDLoadOp op = pick(LoadSplatVec8x16, |
| LoadSplatVec16x8, |
| LoadSplatVec32x4, |
| LoadSplatVec64x2, |
| LoadExtSVec8x8ToVecI16x8, |
| LoadExtUVec8x8ToVecI16x8, |
| LoadExtSVec16x4ToVecI32x4, |
| LoadExtUVec16x4ToVecI32x4, |
| LoadExtSVec32x2ToVecI64x2, |
| LoadExtUVec32x2ToVecI64x2); |
| Address offset = logify(get()); |
| Address align; |
| switch (op) { |
| case LoadSplatVec8x16: |
| align = 1; |
| break; |
| case LoadSplatVec16x8: |
| align = pick(1, 2); |
| break; |
| case LoadSplatVec32x4: |
| align = pick(1, 2, 4); |
| break; |
| case LoadSplatVec64x2: |
| case LoadExtSVec8x8ToVecI16x8: |
| case LoadExtUVec8x8ToVecI16x8: |
| case LoadExtSVec16x4ToVecI32x4: |
| case LoadExtUVec16x4ToVecI32x4: |
| case LoadExtSVec32x2ToVecI64x2: |
| case LoadExtUVec32x2ToVecI64x2: |
| align = pick(1, 2, 4, 8); |
| break; |
| case Load32Zero: |
| case Load64Zero: |
| WASM_UNREACHABLE("Unexpected SIMD loads"); |
| } |
| Expression* ptr = makePointer(); |
| return builder.makeSIMDLoad(op, offset, align, ptr); |
| } |
| |
| Expression* 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* makeRefIsNull(Type type) { |
| assert(type == Type::i32); |
| assert(wasm.features.hasReferenceTypes()); |
| Type refType; |
| if (wasm.features.hasExceptionHandling()) { |
| refType = |
| pick(Type::funcref, Type::externref, Type::nullref, Type::exnref); |
| } else { |
| refType = pick(Type::funcref, Type::externref, Type::nullref); |
| } |
| return builder.makeRefIsNull(make(refType)); |
| } |
| |
| Expression* makeMemoryInit() { |
| if (!allowMemory) { |
| return makeTrivial(Type::none); |
| } |
| uint32_t segment = upTo(wasm.memory.segments.size()); |
| size_t totalSize = wasm.memory.segments[segment].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); |
| } |
| |
| Expression* makeDataDrop() { |
| if (!allowMemory) { |
| return makeTrivial(Type::none); |
| } |
| return builder.makeDataDrop(upTo(wasm.memory.segments.size())); |
| } |
| |
| Expression* makeMemoryCopy() { |
| if (!allowMemory) { |
| return makeTrivial(Type::none); |
| } |
| Expression* dest = makePointer(); |
| Expression* source = makePointer(); |
| Expression* size = make(Type::i32); |
| return builder.makeMemoryCopy(dest, source, size); |
| } |
| |
| Expression* makeMemoryFill() { |
| if (!allowMemory) { |
| return makeTrivial(Type::none); |
| } |
| Expression* dest = makePointer(); |
| Expression* value = makePointer(); |
| Expression* size = make(Type::i32); |
| return builder.makeMemoryFill(dest, value, size); |
| } |
| |
| // special makers |
| |
| Expression* makeLogging() { |
| auto type = getLoggableType(); |
| return builder.makeCall( |
| std::string("log-") + type.toString(), {make(type)}, Type::none); |
| } |
| |
| Expression* makeMemoryHashLogging() { |
| auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32); |
| return builder.makeCall(std::string("log-i32"), {hash}, Type::none); |
| } |
| |
| // special getters |
| std::vector<Type> getSingleConcreteTypes() { |
| return items( |
| FeatureOptions<Type>() |
| .add(FeatureSet::MVP, Type::i32, Type::i64, Type::f32, Type::f64) |
| .add(FeatureSet::SIMD, Type::v128) |
| .add(FeatureSet::ReferenceTypes, |
| Type::funcref, |
| Type::externref, |
| Type::nullref) |
| .add(FeatureSet::ReferenceTypes | FeatureSet::ExceptionHandling, |
| Type::exnref)); |
| } |
| |
| Type getSingleConcreteType() { return pick(getSingleConcreteTypes()); } |
| |
| Type getTupleType() { |
| std::vector<Type> elements; |
| size_t numElements = 2 + upTo(MAX_TUPLE_SIZE - 1); |
| elements.resize(numElements); |
| for (size_t i = 0; i < numElements; ++i) { |
| elements[i] = getSingleConcreteType(); |
| } |
| return Type(elements); |
| } |
| |
| Type getConcreteType() { |
| if (wasm.features.hasMultivalue() && oneIn(5)) { |
| return getTupleType(); |
| } else { |
| return getSingleConcreteType(); |
| } |
| } |
| |
| Type getControlFlowType() { |
| if (oneIn(10)) { |
| return Type::none; |
| } else { |
| return getConcreteType(); |
| } |
| } |
| |
| Type getStorableType() { |
| return pick( |
| FeatureOptions<Type>() |
| .add(FeatureSet::MVP, Type::i32, Type::i64, Type::f32, Type::f64) |
| .add(FeatureSet::SIMD, Type::v128)); |
| } |
| |
| // - funcref cannot be logged because referenced functions can be inlined or |
| // removed during optimization |
| // - there's no point in logging externref because it is opaque |
| // - don't bother logging tuples |
| std::vector<Type> getLoggableTypes() { |
| return items( |
| FeatureOptions<Type>() |
| .add(FeatureSet::MVP, Type::i32, Type::i64, Type::f32, Type::f64) |
| .add(FeatureSet::SIMD, Type::v128) |
| .add(FeatureSet::ReferenceTypes, Type::nullref) |
| .add(FeatureSet::ReferenceTypes | FeatureSet::ExceptionHandling, |
| Type::exnref)); |
| } |
| |
| Type getLoggableType() { return pick(getLoggableTypes()); } |
| |
| // statistical distributions |
| |
| // 0 to the limit, logarithmic scale |
| Index logify(Index x) { |
| return std::floor(std::log(std::max(Index(1) + x, Index(1)))); |
| } |
| |
| // one of the integer values in [0, x) |
| // this isn't a perfectly uniform distribution, but it's fast |
| // and reasonable |
| Index upTo(Index x) { |
| if (x == 0) { |
| return 0; |
| } |
| Index raw; |
| if (x <= 255) { |
| raw = get(); |
| } else if (x <= 65535) { |
| raw = get16(); |
| } else { |
| raw = get32(); |
| } |
| auto ret = raw % x; |
| // use extra bits as "noise" for later |
| xorFactor += raw / x; |
| return ret; |
| } |
| |
| bool oneIn(Index x) { return upTo(x) == 0; } |
| |
| bool onceEvery(Index x) { |
| static int counter = 0; |
| counter++; |
| return counter % x == 0; |
| } |
| |
| // apply upTo twice, generating a skewed distribution towards |
| // low values |
| Index upToSquared(Index x) { return upTo(upTo(x)); } |
| |
| // pick from a vector-like container |
| template<typename T> const typename T::value_type& pick(const T& vec) { |
| assert(!vec.empty()); |
| auto index = upTo(vec.size()); |
| return vec[index]; |
| } |
| |
| // pick from a fixed list |
| template<typename T, typename... Args> T pick(T first, Args... args) { |
| auto num = sizeof...(Args) + 1; |
| auto temp = upTo(num); |
| return pickGivenNum<T>(temp, first, args...); |
| } |
| |
| template<typename T> T pickGivenNum(size_t num, T first) { |
| assert(num == 0); |
| return first; |
| } |
| |
| // Trick to avoid a bug in GCC 7.x. |
| // Upstream bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82800 |
| #define GCC_VERSION \ |
| (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) |
| #if GCC_VERSION > 70000 && GCC_VERSION < 70300 |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" |
| #endif |
| |
| template<typename T, typename... Args> |
| T pickGivenNum(size_t num, T first, Args... args) { |
| if (num == 0) { |
| return first; |
| } |
| return pickGivenNum<T>(num - 1, args...); |
| } |
| |
| #if GCC_VERSION > 70000 && GCC_VERSION < 70300 |
| #pragma GCC diagnostic pop |
| #endif |
| |
| template<typename T> struct FeatureOptions { |
| template<typename... Ts> |
| FeatureOptions<T>& add(FeatureSet feature, T option, Ts... rest) { |
| options[feature].push_back(option); |
| return add(feature, rest...); |
| } |
| |
| struct WeightedOption { |
| T option; |
| size_t weight; |
| }; |
| |
| template<typename... Ts> |
| FeatureOptions<T>& |
| add(FeatureSet feature, WeightedOption weightedOption, Ts... rest) { |
| for (size_t i = 0; i < weightedOption.weight; i++) { |
| options[feature].push_back(weightedOption.option); |
| } |
| return add(feature, rest...); |
| } |
| |
| FeatureOptions<T>& add(FeatureSet feature) { return *this; } |
| |
| std::map<FeatureSet, std::vector<T>> options; |
| }; |
| |
| template<typename T> std::vector<T> items(FeatureOptions<T>& picker) { |
| std::vector<T> matches; |
| for (const auto& item : picker.options) { |
| if (wasm.features.has(item.first)) { |
| matches.reserve(matches.size() + item.second.size()); |
| matches.insert(matches.end(), item.second.begin(), item.second.end()); |
| } |
| } |
| return matches; |
| } |
| |
| template<typename T> const T pick(FeatureOptions<T>& picker) { |
| return pick(items(picker)); |
| } |
| |
| // utilities |
| |
| Name 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 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"); |
| } |
| }; |
| |
| } // namespace wasm |
| |
| // XXX Switch class has a condition?! is it real? should the node type be the |
| // value type if it exists?! |
| |
| // TODO copy an existing function and replace just one node in it |