/* * 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. // /* memory too 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 #include 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) : wasm(wasm), builder(wasm) {} void read(std::string& filename) { auto input(read_file>(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); } build(); } private: Module& wasm; Builder builder; std::vector 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) 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; // 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 build() { setupMemory(); setupTable(); // keep adding functions until we run out of input while (!finishedInput) { addFunction(); } if (HANG_LIMIT > 0) { addHangLimitSupport(); } if (DE_NAN) { addDeNanSupport(); } finalizeTable(); } void setupMemory() { wasm.memory.exists = true; // use one page wasm.memory.initial = wasm.memory.max = 1; } void setupTable() { wasm.table.exists = true; wasm.table.segments.emplace_back(builder.makeConst(Literal(int32_t(0)))); } 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 = new Global; glob->name = HANG_LIMIT_GLOBAL; glob->type = i32; glob->init = builder.makeConst(Literal(int32_t(HANG_LIMIT))); glob->mutable_ = true; 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 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 hangStack; std::map> 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: ret = _makei32(); break; case i64: ret = _makei64(); break; case f32: ret = _makef32(); break; case f64: ret = _makef64(); break; case none: ret = _makenone(); break; case unreachable: ret = _makeunreachable(); break; default: WASM_UNREACHABLE(); } nesting--; return ret; } Expression* _makei32() { switch (upTo(13)) { case 0: return makeBlock(i32); case 1: return makeIf(i32); case 2: return makeLoop(i32); case 3: return makeBreak(i32); case 4: return makeCall(i32); case 5: return makeCallIndirect(i32); case 6: return makeGetLocal(i32); case 7: return makeSetLocal(i32); case 8: return makeLoad(i32); case 9: return makeConst(i32); case 10: return makeUnary(i32); case 11: return makeBinary(i32); case 12: return makeSelect(i32); } WASM_UNREACHABLE(); } Expression* _makei64() { switch (upTo(13)) { case 0: return makeBlock(i64); case 1: return makeIf(i64); case 2: return makeLoop(i64); case 3: return makeBreak(i64); case 4: return makeCall(i64); case 5: return makeCallIndirect(i64); case 6: return makeGetLocal(i64); case 7: return makeSetLocal(i64); case 8: return makeLoad(i64); case 9: return makeConst(i64); case 10: return makeUnary(i64); case 11: return makeBinary(i64); case 12: return makeSelect(i64); } WASM_UNREACHABLE(); } Expression* _makef32() { switch (upTo(13)) { case 0: return makeBlock(f32); case 1: return makeIf(f32); case 2: return makeLoop(f32); case 3: return makeBreak(f32); case 4: return makeCall(f32); case 5: return makeCallIndirect(f32); case 6: return makeGetLocal(f32); case 7: return makeSetLocal(f32); case 8: return makeLoad(f32); case 9: return makeConst(f32); case 10: return makeUnary(f32); case 11: return makeBinary(f32); case 12: return makeSelect(f32); } WASM_UNREACHABLE(); } Expression* _makef64() { switch (upTo(13)) { case 0: return makeBlock(f64); case 1: return makeIf(f64); case 2: return makeLoop(f64); case 3: return makeBreak(f64); case 4: return makeCall(f64); case 5: return makeCallIndirect(f64); case 6: return makeGetLocal(f64); case 7: return makeSetLocal(f64); case 8: return makeLoad(f64); case 9: return makeConst(f64); case 10: return makeUnary(f64); case 11: return makeBinary(f64); case 12: return makeSelect(f64); } WASM_UNREACHABLE(); } Expression* _makenone() { switch (upTo(10)) { 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); } 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; } } 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(); ret->type = type; // so we have it during child creation ret->name = makeLabel(); breakableStack.push_back(ret); hangStack.push_back(ret); ret->body = makeMaybeBlock(type); 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()) { if (loop->name == name) { // we found the target, no more conditions matter break; } } } switch (conditions) { case 0: if (!oneIn(4)) continue; case 1: if (!oneIn(2)) continue; 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 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 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* 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; } Expression* makeLoad(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(); } } Store* makeStore(WasmType type) { if (type == unreachable) { // make a normal store, then make it unreachable auto* ret = makeStore(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(); } } 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(0, -1, 1, std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max())); break; case i64: value = Literal(pick(0, -1, 1, std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max())); break; case f32: value = Literal(pick(0, -1, 1, std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max())); break; case f64: value = Literal(pick(0, -1, 1, std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max())); break; default: WASM_UNREACHABLE(); } // tweak around special values if (oneIn(3)) { value = value.add(LiteralUtils::makeLiteralFromInt32(upTo(3) - 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(); 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()) { 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()) { 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 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(); } // 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; } // apply upTo twice, generating a skewed distribution towards // low values Index upToSquared(Index x) { return upTo(upTo(x)); } // pick from a vector template const T& vectorPick(const std::vector& vec) { assert(!vec.empty()); auto index = upTo(vec.size()); return vec[index]; } // pick from a fixed list template T pick(T first, Args... args) { auto num = sizeof...(Args) + 1; auto temp = upTo(num); return pickGivenNum(temp, first, args...); } template T pickGivenNum(size_t num, T first) { assert(num == 0); return first; } template T pickGivenNum(size_t num, T first, Args... args) { if (num == 0) return first; return pickGivenNum(num - 1, args...); } // utilities Name getTargetName(Expression* target) { if (auto* block = target->dynCast()) { return block->name; } else if (auto* loop = target->dynCast()) { return loop->name; } WASM_UNREACHABLE(); } WasmType getTargetType(Expression* target) { if (auto* block = target->dynCast()) { return block->type; } else if (target->is()) { 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?!