| /* |
| * 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. |
| // |
| |
| #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) : wasm(wasm), builder(wasm) {} |
| |
| void read(std::string& filename) { |
| 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); |
| } |
| build(); |
| } |
| |
| 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) |
| 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(); |
| // keep adding functions until we run out of input |
| while (!finishedInput) { |
| addFunction(); |
| } |
| if (HANG_LIMIT > 0) { |
| addHangLimitSupport(); |
| } |
| if (DE_NAN) { |
| addDeNanSupport(); |
| } |
| } |
| |
| void setupMemory() { |
| wasm.memory.exists = true; |
| // use one page |
| wasm.memory.initial = wasm.memory.max = 1; |
| } |
| |
| 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<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)) { |
| auto* export_ = new Export; |
| export_->name = func->name; |
| export_->value = func->name; |
| export_->kind = ExternalKind::Function; |
| wasm.addExport(export_); |
| } |
| // 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<Loop>(); |
| 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<Loop>()) { |
| 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<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) { |
| return make(type); // TODO |
| } |
| |
| 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(3)) { |
| 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 |
| int32_t small; |
| switch (upTo(4)) { |
| 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; |
| 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, -1, 1, |
| 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, -1, 1, |
| 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, -1, 1, |
| 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, -1, 1, |
| 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(); |
| } |
| break; |
| } |
| } |
| 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(); |
| } |
| |
| // 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<typename T> |
| const T& vectorPick(const std::vector<T>& vec) { |
| // TODO: get32? |
| 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?! |