diff options
Diffstat (limited to 'src/tools/fuzzing.h')
-rw-r--r-- | src/tools/fuzzing.h | 1352 |
1 files changed, 1352 insertions, 0 deletions
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h new file mode 100644 index 000000000..978d4e443 --- /dev/null +++ b/src/tools/fuzzing.h @@ -0,0 +1,1352 @@ +/* + * 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 +*/ + +#include <wasm-builder.h> +#include <ir/literal-utils.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, Flags::Release)); + bytes.swap(input); + pos = 0; + finishedInput = false; + // ensure *some* input to be read + if (bytes.size() == 0) { + bytes.push_back(0); + } + } + + 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(); + } + } + 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 build() { + setupMemory(); + setupTable(); + setupGlobals(); + // keep adding functions until we run out of input + while (!finishedInput) { + addFunction(); + } + if (HANG_LIMIT > 0) { + addHangLimitSupport(); + } + if (DE_NAN) { + addDeNanSupport(); + } + finalizeTable(); + } + +private: + Module& wasm; + Builder builder; + std::vector<char> bytes; // the input bytes + size_t pos; // the position in the input + bool finishedInput; // whether we already cycled through all the input (if so, we should try to finish things off) + + // 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; + + // reduce the chance for a function to call itself by this factor + static const int RECURSION_FACTOR = 10; + + // 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 = 32; + + // 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 = 100; + + // Optionally remove NaNs, which are a source of nondeterminism (which makes + // cross-VM comparisons harder) + static const bool DE_NAN = true; + + // Whether to emit atomics + static const bool ATOMICS = true; + + // after we finish the input, we start going through it again, but xoring + // so it's not identical + int xorFactor = 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(); + } + + void setupMemory() { + wasm.memory.exists = true; + // use one page + wasm.memory.initial = wasm.memory.max = 1; + // init some data + wasm.memory.segments.emplace_back(builder.makeConst(Literal(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)); + } + } + + void setupTable() { + wasm.table.exists = true; + wasm.table.segments.emplace_back(builder.makeConst(Literal(int32_t(0)))); + } + + std::map<WasmType, std::vector<Name>> globalsByType; + + void setupGlobals() { + size_t index = 0; + for (auto type : { i32, i64, f32, f64 }) { + auto num = upTo(3); + for (size_t i = 0; i < num; i++) { + auto* glob = builder.makeGlobal( + std::string("global$") + std::to_string(index++), + type, + makeConst(type), + Builder::Mutable + ); + wasm.addGlobal(glob); + globalsByType[type].push_back(glob->name); + } + } + } + + void finalizeTable() { + wasm.table.initial = wasm.table.segments[0].data.size(); + wasm.table.max = oneIn(2) ? Address(Table::kMaxSize) : wasm.table.initial; + } + + const Name HANG_LIMIT_GLOBAL = "hangLimit"; + + void addHangLimitSupport() { + auto* glob = builder.makeGlobal( + HANG_LIMIT_GLOBAL, + i32, + builder.makeConst(Literal(int32_t(HANG_LIMIT))), + Builder::Mutable + ); + wasm.addGlobal(glob); + + auto* func = new Function; + func->name = "hangLimitInitializer"; + func->result = none; + func->body = builder.makeSetGlobal(glob->name, + builder.makeConst(Literal(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_); + } + + Expression* makeHangLimitCheck() { + return builder.makeSequence( + builder.makeIf( + builder.makeUnary( + UnaryOp::EqZInt32, + builder.makeGetGlobal(HANG_LIMIT_GLOBAL, i32) + ), + makeTrivial(unreachable) + ), + builder.makeSetGlobal( + HANG_LIMIT_GLOBAL, + builder.makeBinary( + BinaryOp::SubInt32, + builder.makeGetGlobal(HANG_LIMIT_GLOBAL, i32), + builder.makeConst(Literal(int32_t(1))) + ) + ) + ); + } + + void addDeNanSupport() { + auto add = [&](Name name, WasmType type, Literal literal, BinaryOp op) { + auto* func = new Function; + func->name = name; + func->params.push_back(type); + func->result = type; + func->body = builder.makeIf( + builder.makeBinary( + op, + builder.makeGetLocal(0, type), + builder.makeGetLocal(0, type) + ), + builder.makeGetLocal(0, type), + builder.makeConst(literal) + ); + wasm.addFunction(func); + }; + add("deNan32", f32, Literal(float(0)), EqFloat32); + add("deNan64", f64, Literal(double(0)), EqFloat64); + } + + Expression* makeDeNanOp(Expression* expr) { + if (!DE_NAN) return expr; + if (expr->type == f32) { + return builder.makeCall("deNan32", { expr }, f32); + } else if (expr->type == f64) { + return builder.makeCall("deNan64", { expr }, f64); + } + return expr; // unreachable etc. is fine + } + + // function generation state + + Function* func; + 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<WasmType, std::vector<Index>> typeLocals; // type => list of locals with that type + + void addFunction() { + Index num = wasm.functions.size(); + func = new Function; + func->name = std::string("func_") + std::to_string(num); + func->result = getReachableType(); + assert(typeLocals.empty()); + Index numParams = upToSquared(5); + for (Index i = 0; i < numParams; i++) { + auto type = getConcreteType(); + typeLocals[type].push_back(func->params.size()); + func->params.push_back(type); + } + Index numVars = upToSquared(10); + for (Index i = 0; i < numVars; i++) { + auto type = getConcreteType(); + typeLocals[type].push_back(func->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->result; + if (oneIn(10)) { + bodyType = unreachable; + } + // with reasonable chance make the body a block + if (oneIn(2)) { + func->body = makeBlock(bodyType); + } else { + func->body = make(bodyType); + } + if (HANG_LIMIT > 0) { + func->body = builder.makeSequence( + makeHangLimitCheck(), + func->body + ); + } + 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)) { + func->type = ensureFunctionType(getSig(func), &wasm)->name; + 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)) { + wasm.table.segments[0].data.push_back(func->name); + } + // cleanup + typeLocals.clear(); + } + + Name makeLabel() { + return std::string("label$") + std::to_string(labelIndex++); + } + + // always call the toplevel make(type) command, not the internal specific ones + + int nesting = 0; + + Expression* make(WasmType 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 (isConcreteWasmType(type)) { + if (oneIn(2)) { + return makeConst(type); + } else { + return makeGetLocal(type); + } + } else if (type == none) { + if (oneIn(2)) { + return makeNop(type); + } else { + return makeSetLocal(type); + } + } + assert(type == unreachable); + return makeTrivial(type); + } + nesting++; + Expression* ret; + switch (type) { + case i32: + case i64: + case f32: + case f64: ret = _makeConcrete(type); break; + case none: ret = _makenone(); break; + case unreachable: ret = _makeunreachable(); break; + default: WASM_UNREACHABLE(); + } + assert(ret->type == type); // we should create the right type of thing + nesting--; + return ret; + } + + Expression* _makeConcrete(WasmType type) { + switch (upTo(15)) { + case 0: return makeBlock(type); + case 1: return makeIf(type); + case 2: return makeLoop(type); + case 3: return makeBreak(type); + case 4: return makeCall(type); + case 5: return makeCallIndirect(type); + case 6: return makeGetLocal(type); + case 7: return makeSetLocal(type); + case 8: return makeLoad(type); + case 9: return makeConst(type); + case 10: return makeUnary(type); + case 11: return makeBinary(type); + case 12: return makeSelect(type); + case 13: return makeGetGlobal(type); + case 14: return makeAtomic(type); + } + WASM_UNREACHABLE(); + } + + Expression* _makenone() { + switch (upTo(11)) { + case 0: return makeBlock(none); + case 1: return makeIf(none); + case 2: return makeLoop(none); + case 3: return makeBreak(none); + case 4: return makeCall(none); + case 5: return makeCallIndirect(none); + case 6: return makeSetLocal(none); + case 7: return makeStore(none); + case 8: return makeDrop(none); + case 9: return makeNop(none); + case 10: return makeSetGlobal(none); + } + WASM_UNREACHABLE(); + } + + Expression* _makeunreachable() { + switch (upTo(15)) { + case 0: return makeBlock(unreachable); + case 1: return makeIf(unreachable); + case 2: return makeLoop(unreachable); + case 3: return makeBreak(unreachable); + case 4: return makeCall(unreachable); + case 5: return makeCallIndirect(unreachable); + case 6: return makeSetLocal(unreachable); + case 7: return makeStore(unreachable); + case 8: return makeUnary(unreachable); + case 9: return makeBinary(unreachable); + case 10: return makeSelect(unreachable); + case 11: return makeSwitch(unreachable); + case 12: return makeDrop(unreachable); + case 13: return makeReturn(unreachable); + case 14: return makeUnreachable(unreachable); + } + WASM_UNREACHABLE(); + } + + // make something with no chance of infinite recursion + Expression* makeTrivial(WasmType type) { + if (isConcreteWasmType(type)) { + if (oneIn(2)) { + return makeGetLocal(type); + } else { + return makeConst(type); + } + } else if (type == none) { + return makeNop(type); + } + assert(type == unreachable); + Expression* ret = nullptr; + if (isConcreteWasmType(func->result)) { + ret = makeTrivial(func->result); + } + return builder.makeReturn(ret); + } + + // specific expression creators + + Expression* makeBlock(WasmType 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(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 && isConcreteWasmType(type) && oneIn(2)) { + ret->list.push_back(makeBreak(unreachable)); + } else { + ret->list.push_back(make(type)); + } + breakableStack.pop_back(); + if (isConcreteWasmType(type)) { + 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 == unreachable && ret->type == none); + return builder.makeSequence(ret, make(unreachable)); + } + return ret; + } + + Expression* makeLoop(WasmType 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(none)); // primary contents + list.push_back(builder.makeBreak(ret->name, nullptr, makeCondition())); // possible branch back + list.push_back(make(type)); // final element, so we have the right type + ret->body = builder.makeBlock(list); + } + breakableStack.pop_back(); + hangStack.pop_back(); + if (HANG_LIMIT > 0) { + ret->body = builder.makeSequence( + makeHangLimitCheck(), + ret->body + ); + } + ret->finalize(); + 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(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(WasmType type) { + // if past the limit, prefer not to emit blocks + if (nesting >= NESTING_LIMIT || oneIn(3)) { + return make(type); + } else { + return makeBlock(type); + } + } + + Expression* makeIf(WasmType type) { + auto* condition = makeCondition(); + hangStack.push_back(nullptr); + auto* ret = makeIf({ condition, makeMaybeBlock(type), makeMaybeBlock(type) }); + hangStack.pop_back(); + return ret; + } + + Expression* makeIf(const struct ThreeArgs& args) { + return builder.makeIf(args.a, args.b, args.c); + } + + Expression* makeBreak(WasmType type) { + if (breakableStack.empty()) return makeTrivial(type); + Expression* condition = nullptr; + if (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 = vectorPick(breakableStack); + auto name = getTargetName(target); + auto valueType = getTargetType(target); + if (isConcreteWasmType(type)) { + // 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 == none) { + if (valueType != 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 == unreachable); + if (valueType != 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 != unreachable) { + hangStack.pop_back(); + } + return makeTrivial(type); + } + + Expression* makeCall(WasmType type) { + // seems ok, go on + int tries = TRIES; + while (tries-- > 0) { + Function* target = func; + if (!wasm.functions.empty() && !oneIn(wasm.functions.size())) { + target = vectorPick(wasm.functions).get(); + } + if (target->result != type) continue; + // reduce the odds of recursion dramatically, to limit infinite loops + if (target == func && !oneIn(RECURSION_FACTOR * TRIES)) continue; + // we found one! + std::vector<Expression*> args; + for (auto argType : target->params) { + args.push_back(make(argType)); + } + return builder.makeCall(target->name, args, type); + } + // we failed to find something + return make(type); + } + + Expression* makeCallIndirect(WasmType 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* func; + while (1) { + // TODO: handle unreachable + func = wasm.getFunction(data[i]); + if (func->result == type) { + 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 (!oneIn(10)) { + target = builder.makeConst(Literal(int32_t(i))); + } else { + target = make(i32); + } + std::vector<Expression*> args; + for (auto type : func->params) { + args.push_back(make(type)); + } + func->type = ensureFunctionType(getSig(func), &wasm)->name; + return builder.makeCallIndirect( + func->type, + target, + args, + func->result + ); + } + + Expression* makeGetLocal(WasmType type) { + auto& locals = typeLocals[type]; + if (locals.empty()) return makeConst(type); + return builder.makeGetLocal(vectorPick(locals), type); + } + + Expression* makeSetLocal(WasmType type) { + bool tee = type != none; + WasmType 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.makeTeeLocal(vectorPick(locals), value); + } else { + return builder.makeSetLocal(vectorPick(locals), value); + } + } + + Expression* makeGetGlobal(WasmType type) { + auto& globals = globalsByType[type]; + if (globals.empty()) return makeConst(type); + return builder.makeGetGlobal(vectorPick(globals), type); + } + + Expression* makeSetGlobal(WasmType type) { + assert(type == none); + type = getConcreteType(); + auto& globals = globalsByType[type]; + if (globals.empty()) return makeTrivial(none); + auto* value = make(type); + return builder.makeSetGlobal(vectorPick(globals), value); + } + + Expression* makePointer() { + auto* ret = make(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 (!oneIn(10)) { + ret = builder.makeBinary(AndInt32, + ret, + builder.makeConst(Literal(int32_t(USABLE_MEMORY - 1))) + ); + } + return ret; + } + + Load* makeNonAtomicLoad(WasmType type) { + auto offset = logify(get()); + auto ptr = makePointer(); + switch (type) { + case 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(); + } + case 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(); + } + case f32: { + return builder.makeLoad(4, false, offset, pick(1, 2, 4), ptr, type); + } + case f64: { + return builder.makeLoad(8, false, offset, pick(1, 2, 4, 8), ptr, type); + } + default: WASM_UNREACHABLE(); + } + } + + Expression* makeLoad(WasmType type) { + auto* ret = makeNonAtomicLoad(type); + if (type != i32 && type != i64) return ret; + if (!ATOMICS || oneIn(2)) return ret; + // make it atomic + wasm.memory.shared = true; + ret->isAtomic = true; + ret->signed_ = false; + ret->align = ret->bytes; + return ret; + } + + Store* makeNonAtomicStore(WasmType type) { + if (type == unreachable) { + // make a normal store, then make it unreachable + auto* ret = makeNonAtomicStore(getConcreteType()); + switch (upTo(3)) { + case 0: ret->ptr = make(unreachable); break; + case 1: ret->value = make(unreachable); break; + case 2: ret->ptr = make(unreachable); ret->value = make(unreachable); break; + } + ret->finalize(); + return ret; + } + // the type is none or unreachable. we also need to pick the value + // type. + if (type == none) { + type = getConcreteType(); + } + auto offset = logify(get()); + auto ptr = makePointer(); + auto value = make(type); + switch (type) { + case 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(); + } + case 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(); + } + case f32: { + return builder.makeStore(4, offset, pick(1, 2, 4), ptr, value, type); + } + case f64: { + return builder.makeStore(8, offset, pick(1, 2, 4, 8), ptr, value, type); + } + default: WASM_UNREACHABLE(); + } + } + + Store* makeStore(WasmType type) { + auto* ret = makeNonAtomicStore(type); + if (ret->value->type != i32 && ret->value->type != i64) return ret; + if (!ATOMICS || oneIn(2)) return ret; + // make it atomic + wasm.memory.shared = true; + ret->isAtomic = true; + ret->align = ret->bytes; + return ret; + } + + Expression* makeConst(WasmType type) { + Literal value; + switch (upTo(4)) { + case 0: { + // totally random, entire range + switch (type) { + case i32: value = Literal(get32()); break; + case i64: value = Literal(get64()); break; + case f32: value = Literal(getFloat()); break; + case f64: value = Literal(getDouble()); break; + default: WASM_UNREACHABLE(); + } + 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(); + } + switch (type) { + case i32: value = Literal(int32_t(small)); break; + case i64: value = Literal(int64_t(small)); break; + case f32: value = Literal(float(small)); break; + case f64: value = Literal(double(small)); break; + default: WASM_UNREACHABLE(); + } + break; + } + case 2: { + // special values + switch (type) { + case 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 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 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 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; + default: WASM_UNREACHABLE(); + } + // tweak around special values + if (oneIn(3)) { // +- 1 + value = value.add(LiteralUtils::makeLiteralFromInt32(upTo(3) - 1, type)); + } + if (oneIn(2)) { // flip sign + value = value.mul(LiteralUtils::makeLiteralFromInt32(-1, type)); + } + break; + } + case 3: { + // powers of 2 + switch (type) { + case i32: value = Literal(int32_t(1) << upTo(32)); break; + case i64: value = Literal(int64_t(1) << upTo(64)); break; + case f32: value = Literal(float(int64_t(1) << upTo(64))); break; + case f64: value = Literal(double(int64_t(1) << upTo(64))); break; + default: WASM_UNREACHABLE(); + } + // maybe negative + if (oneIn(2)) { + value = value.mul(LiteralUtils::makeLiteralFromInt32(-1, type)); + } + } + } + auto* ret = wasm.allocator.alloc<Const>(); + ret->value = value; + ret->type = value.type; + return ret; + } + + Expression* makeUnary(const UnaryArgs& args) { + return builder.makeUnary(args.a, args.b); + } + + Expression* makeUnary(WasmType type) { + if (type == unreachable) { + if (auto* unary = makeUnary(getConcreteType())->dynCast<Unary>()) { + return makeDeNanOp(builder.makeUnary(unary->op, make(unreachable))); + } + // give up + return makeTrivial(type); + } + switch (type) { + case i32: { + switch (upTo(4)) { + case 0: return makeUnary({ pick(EqZInt32, ClzInt32, CtzInt32, PopcntInt32), make(i32) }); + case 1: return makeUnary({ pick(EqZInt64, WrapInt64), make(i64) }); + case 2: return makeUnary({ pick(TruncSFloat32ToInt32, TruncUFloat32ToInt32, ReinterpretFloat32), make(f32) }); + case 3: return makeUnary({ pick(TruncSFloat64ToInt32, TruncUFloat64ToInt32), make(f64) }); + } + WASM_UNREACHABLE(); + } + case i64: { + switch (upTo(4)) { + case 0: return makeUnary({ pick(ClzInt64, CtzInt64, PopcntInt64), make(i64) }); + case 1: return makeUnary({ pick(ExtendSInt32, ExtendUInt32), make(i32) }); + case 2: return makeUnary({ pick(TruncSFloat32ToInt64, TruncUFloat32ToInt64), make(f32) }); + case 3: return makeUnary({ pick(TruncSFloat64ToInt64, TruncUFloat64ToInt64, ReinterpretFloat64), make(f64) }); + } + WASM_UNREACHABLE(); + } + case f32: { + switch (upTo(4)) { + case 0: return makeDeNanOp(makeUnary({ pick(NegFloat32, AbsFloat32, CeilFloat32, FloorFloat32, TruncFloat32, NearestFloat32, SqrtFloat32), make(f32) })); + case 1: return makeDeNanOp(makeUnary({ pick(ConvertUInt32ToFloat32, ConvertSInt32ToFloat32, ReinterpretInt32), make(i32) })); + case 2: return makeDeNanOp(makeUnary({ pick(ConvertUInt64ToFloat32, ConvertSInt64ToFloat32), make(i64) })); + case 3: return makeDeNanOp(makeUnary({ DemoteFloat64, make(f64) })); + } + WASM_UNREACHABLE(); + } + case f64: { + switch (upTo(4)) { + case 0: return makeDeNanOp(makeUnary({ pick(NegFloat64, AbsFloat64, CeilFloat64, FloorFloat64, TruncFloat64, NearestFloat64, SqrtFloat64), make(f64) })); + case 1: return makeDeNanOp(makeUnary({ pick(ConvertUInt32ToFloat64, ConvertSInt32ToFloat64), make(i32) })); + case 2: return makeDeNanOp(makeUnary({ pick(ConvertUInt64ToFloat64, ConvertSInt64ToFloat64, ReinterpretInt64), make(i64) })); + case 3: return makeDeNanOp(makeUnary({ PromoteFloat32, make(f32) })); + } + WASM_UNREACHABLE(); + } + default: WASM_UNREACHABLE(); + } + WASM_UNREACHABLE(); + } + + Expression* makeBinary(const BinaryArgs& args) { + return builder.makeBinary(args.a, args.b, args.c); + } + + Expression* makeBinary(WasmType type) { + if (type == unreachable) { + if (auto* binary = makeBinary(getConcreteType())->dynCast<Binary>()) { + return makeDeNanOp(makeBinary({ binary->op, make(unreachable), make(unreachable) })); + } + // give up + return makeTrivial(type); + } + switch (type) { + case i32: { + switch (upTo(4)) { + case 0: return makeBinary({ 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(i32), make(i32) }); + case 1: return makeBinary({ pick(EqInt64, NeInt64, LtSInt64, LtUInt64, LeSInt64, LeUInt64, GtSInt64, GtUInt64, GeSInt64, GeUInt64), make(i64), make(i64) }); + case 2: return makeBinary({ pick(EqFloat32, NeFloat32, LtFloat32, LeFloat32, GtFloat32, GeFloat32), make(f32), make(f32) }); + case 3: return makeBinary({ pick(EqFloat64, NeFloat64, LtFloat64, LeFloat64, GtFloat64, GeFloat64), make(f64), make(f64) }); + } + WASM_UNREACHABLE(); + } + case i64: { + return makeBinary({ pick(AddInt64, SubInt64, MulInt64, DivSInt64, DivUInt64, RemSInt64, RemUInt64, AndInt64, OrInt64, XorInt64, ShlInt64, ShrUInt64, ShrSInt64, RotLInt64, RotRInt64), make(i64), make(i64) }); + } + case f32: { + return makeDeNanOp(makeBinary({ pick(AddFloat32, SubFloat32, MulFloat32, DivFloat32, CopySignFloat32, MinFloat32, MaxFloat32), make(f32), make(f32) })); + } + case f64: { + return makeDeNanOp(makeBinary({ pick(AddFloat64, SubFloat64, MulFloat64, DivFloat64, CopySignFloat64, MinFloat64, MaxFloat64), make(f64), make(f64) })); + } + default: WASM_UNREACHABLE(); + } + WASM_UNREACHABLE(); + } + + Expression* makeSelect(const ThreeArgs& args) { + return builder.makeSelect(args.a, args.b, args.c); + } + + Expression* makeSelect(WasmType type) { + return makeDeNanOp(makeSelect({ make(i32), make(type), make(type) })); + } + + Expression* makeSwitch(WasmType type) { + assert(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; + WasmType valueType = unreachable; + while (tries-- > 0) { + auto* target = vectorPick(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(i32), temp2 = isConcreteWasmType(valueType) ? make(valueType) : nullptr; + return builder.makeSwitch(names, default_, temp1, temp2); + } + + Expression* makeDrop(WasmType type) { + return builder.makeDrop(make(type == unreachable ? type : getConcreteType())); + } + + Expression* makeReturn(WasmType type) { + return builder.makeReturn(isConcreteWasmType(func->result) ? make(func->result) : nullptr); + } + + Expression* makeNop(WasmType type) { + assert(type == none); + return builder.makeNop(); + } + + Expression* makeUnreachable(WasmType type) { + assert(type == unreachable); + return builder.makeUnreachable(); + } + + Expression* makeAtomic(WasmType type) { + if (!ATOMICS || (type != i32 && type != i64)) return makeTrivial(type); + wasm.memory.shared = true; + if (type == i32 && oneIn(2)) { + if (oneIn(2)) { + auto* ptr = makePointer(); + auto expectedType = pick(i32, i64); + auto* expected = make(expectedType); + auto* timeout = make(i64); + return builder.makeAtomicWait(ptr, expected, timeout, expectedType); + } else { + auto* ptr = makePointer(); + auto* count = make(i32); + return builder.makeAtomicWake(ptr, count); + } + } + Index bytes; + switch (type) { + case 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(); + } + break; + } + case 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(); + } + break; + } + default: WASM_UNREACHABLE(); + } + 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); + } + } + + // special getters + + WasmType getType() { + switch (upTo(6)) { + case 0: return i32; + case 1: return i64; + case 2: return f32; + case 3: return f64; + case 4: return none; + case 5: return unreachable; + } + WASM_UNREACHABLE(); + } + + WasmType getReachableType() { + switch (upTo(5)) { + case 0: return i32; + case 1: return i64; + case 2: return f32; + case 3: return f64; + case 4: return none; + } + WASM_UNREACHABLE(); + } + + WasmType getConcreteType() { + switch (upTo(4)) { + case 0: return i32; + case 1: return i64; + case 2: return f32; + case 3: return f64; + } + WASM_UNREACHABLE(); + } + + // 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 + template<typename T> + const T& vectorPick(const std::vector<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; + } + + 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...); + } + + // 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(); + } + + WasmType getTargetType(Expression* target) { + if (auto* block = target->dynCast<Block>()) { + return block->type; + } else if (target->is<Loop>()) { + return none; + } + WASM_UNREACHABLE(); + } +}; + +} // namespace wasm + +// XXX Switch class has a condition?! is it real? should the node type be the value type if it exists?! |