Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions scripts/fuzz_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
# parameters


LOG_LIMIT = 125
FUZZ_OPTS = ['--mvp-features'] # may want to add '--no-fuzz-nans' for cross-VM testing

INPUT_SIZE_LIMIT = 250 * 1024

LOG_LIMIT = 125


# utilities

Expand Down Expand Up @@ -122,9 +125,8 @@ def run_vm(cmd):
results = []
# append to this list to add results from VMs
results += [fix_output(run_vm([in_bin('wasm-opt'), prefix + 'wasm', '--fuzz-exec-before']))]
results += [fix_output(run_vm([os.path.expanduser('d8'), '--experimental-wasm-sat_f2i_conversions', prefix + 'js', '--', prefix + 'wasm']))]
results += [fix_output(run_vm([os.path.expanduser('d8-debug'), '--experimental-wasm-sat_f2i_conversions', '--wasm-tier-up', prefix + 'js', '--', prefix + 'wasm']))]
results += [fix_output(run_vm([os.path.expanduser('d8-debug'), '--experimental-wasm-sat_f2i_conversions', '--no-wasm-tier-up', prefix + 'js', '--', prefix + 'wasm']))]
results += [fix_output(run_vm([os.path.expanduser('d8'), prefix + 'js', '--', prefix + 'wasm']))]
# results += [fix_output(run_vm([os.path.expanduser('~/.jsvu/jsc'), prefix + 'js', '--', prefix + 'wasm']))]
# spec has no mechanism to not halt on a trap. so we just check until the first trap, basically
# run(['../spec/interpreter/wasm', prefix + 'wasm'])
# results += [fix_spec_output(run_unchecked(['../spec/interpreter/wasm', prefix + 'wasm', '-e', open(prefix + 'wat').read()]))]
Expand All @@ -146,7 +148,7 @@ def test_one(infile, opts):

# fuzz vms
# gather VM outputs on input file
run([in_bin('wasm-opt'), infile, '-ttf', '--emit-js-wrapper=a.js', '--emit-spec-wrapper=a.wat', '-o', 'a.wasm', '--mvp-features'])
run([in_bin('wasm-opt'), infile, '-ttf', '--emit-js-wrapper=a.js', '--emit-spec-wrapper=a.wat', '-o', 'a.wasm'] + FUZZ_OPTS)
wasm_size = os.stat('a.wasm').st_size
bytes += wasm_size
print('pre js size :', os.stat('a.js').st_size, ' wasm size:', wasm_size)
Expand Down
2 changes: 2 additions & 0 deletions src/literal.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ class Literal {
bool operator==(const Literal& other) const;
bool operator!=(const Literal& other) const;

bool isNaN();

static uint32_t NaNPayload(float f);
static uint64_t NaNPayload(double f);
static float setQuietNaN(float f);
Expand Down
25 changes: 20 additions & 5 deletions src/tools/fuzzing.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,15 @@ class TranslateToFuzzReader {
std::cout << "shrink level: " << options.passOptions.shrinkLevel << '\n';
}

void build(FeatureSet features_) {
void setFeatures(FeatureSet features_) {
features = features_;
}

void setAllowNaNs(bool allowNaNs_) {
allowNaNs = allowNaNs_;
}

void build() {
setupMemory();
setupTable();
setupGlobals();
Expand All @@ -137,7 +144,7 @@ class TranslateToFuzzReader {
if (HANG_LIMIT > 0) {
addHangLimitSupport();
}
if (DE_NAN) {
if (!allowNaNs) {
addDeNanSupport();
}
finalizeTable();
Expand Down Expand Up @@ -178,7 +185,7 @@ class TranslateToFuzzReader {
// Optionally remove NaNs, which are a source of nondeterminism (which makes
// cross-VM comparisons harder)
// TODO: de-NaN SIMD values
static const bool DE_NAN = true;
bool allowNaNs = true;

// Features allowed to be emitted
FeatureSet features = FeatureSet::All;
Expand Down Expand Up @@ -361,7 +368,7 @@ class TranslateToFuzzReader {
}

Expression* makeDeNanOp(Expression* expr) {
if (!DE_NAN) return expr;
if (allowNaNs) return expr;
if (expr->type == f32) {
return builder.makeCall("deNan32", { expr }, f32);
} else if (expr->type == f64) {
Expand Down Expand Up @@ -1213,7 +1220,7 @@ class TranslateToFuzzReader {
return store;
}

Literal makeLiteral(Type type) {
Literal makeArbitraryLiteral(Type type) {
if (type == v128) {
// generate each lane individually for random lane interpretation
switch (upTo(6)) {
Expand Down Expand Up @@ -1344,6 +1351,14 @@ class TranslateToFuzzReader {
WASM_UNREACHABLE();
}

Literal makeLiteral(Type type) {
auto ret = makeArbitraryLiteral(type);
if (!allowNaNs && ret.isNaN()) {
ret = Literal::makeFromInt32(0, type);
}
return ret;
}

Expression* makeConst(Type type) {
auto* ret = wasm.allocator.alloc<Const>();
ret->value = makeLiteral(type);
Expand Down
2 changes: 1 addition & 1 deletion src/tools/js-wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ static std::string generateJSWrapper(Module& wasm) {
}
ret += ";\n";
ret += "} catch (e) {\n";
ret += " console.log('exception: ' + e);\n";
ret += " console.log('exception!' /* + e */);\n";
ret += "}\n";
}
return ret;
Expand Down
8 changes: 7 additions & 1 deletion src/tools/wasm-opt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ int main(int argc, const char* argv[]) {
std::string extraFuzzCommand;
bool translateToFuzz = false;
bool fuzzPasses = false;
bool fuzzNaNs = true;
std::string emitJSWrapper;
std::string emitSpecWrapper;
std::string inputSourceMapFilename;
Expand Down Expand Up @@ -112,6 +113,9 @@ int main(int argc, const char* argv[]) {
.add("--fuzz-passes", "-fp", "Pick a random set of passes to run, useful for fuzzing. this depends on translate-to-fuzz (it picks the passes from the input)",
Options::Arguments::Zero,
[&](Options *o, const std::string& arguments) { fuzzPasses = true; })
.add("--no-fuzz-nans", "", "don't emit NaNs when fuzzing, and remove them at runtime as well (helps avoid nondeterminism between VMs)",
Options::Arguments::Zero,
[&](Options *o, const std::string& arguments) { fuzzNaNs = false; })
.add("--emit-js-wrapper", "-ejw", "Emit a JavaScript wrapper file that can run the wasm with some test values, useful for fuzzing",
Options::Arguments::One,
[&](Options *o, const std::string& arguments) { emitJSWrapper = arguments; })
Expand Down Expand Up @@ -166,7 +170,9 @@ int main(int argc, const char* argv[]) {
if (fuzzPasses) {
reader.pickPasses(options);
}
reader.build(options.getFeatures());
reader.setFeatures(options.getFeatures());
reader.setAllowNaNs(fuzzNaNs);
reader.build();
if (options.passOptions.validate) {
if (!WasmValidator().validate(wasm, options.getFeatures())) {
WasmPrinter::printModule(&wasm);
Expand Down
30 changes: 27 additions & 3 deletions src/tools/wasm-reduce.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,12 @@ static std::unordered_set<Name> functionsWeTriedToRemove;

struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<Reducer>>> {
std::string command, test, working;
bool binary, verbose, debugInfo;
bool binary, deNan, verbose, debugInfo;

// test is the file we write to that the command will operate on
// working is the current temporary state, the reduction so far
Reducer(std::string command, std::string test, std::string working, bool binary, bool verbose, bool debugInfo) : command(command), test(test), working(working), binary(binary), verbose(verbose), debugInfo(debugInfo) {}
Reducer(std::string command, std::string test, std::string working, bool binary, bool deNan, bool verbose, bool debugInfo) :
command(command), test(test), working(working), binary(binary), deNan(deNan), verbose(verbose), debugInfo(debugInfo) {}

// runs passes in order to reduce, until we can't reduce any more
// the criterion here is wasm binary size
Expand Down Expand Up @@ -360,8 +361,22 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<
return (counter % factor) <= bonus;
}

bool isOkReplacement(Expression* with) {
if (deNan) {
if (auto* c = with->dynCast<Const>()) {
if (c->value.isNaN()) {
return false;
}
}
}
return true;
}

// tests a reduction on the current traversal node, and undos if it failed
bool tryToReplaceCurrent(Expression* with) {
if (!isOkReplacement(with)) {
return false;
}
auto* curr = getCurrent();
//std::cerr << "try " << curr << " => " << with << '\n';
if (curr->type != with->type) return false;
Expand All @@ -383,6 +398,9 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<

// tests a reduction on an arbitrary child
bool tryToReplaceChild(Expression*& child, Expression* with) {
if (!isOkReplacement(with)) {
return false;
}
if (child->type != with->type) return false;
if (!shouldTryToReduce()) return false;
auto* before = child;
Expand Down Expand Up @@ -865,6 +883,7 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<
int main(int argc, const char* argv[]) {
std::string input, test, working, command;
bool binary = true,
deNan = false,
verbose = false,
debugInfo = false,
force = false;
Expand Down Expand Up @@ -899,6 +918,11 @@ int main(int argc, const char* argv[]) {
[&](Options* o, const std::string& argument) {
binary = false;
})
.add("--denan", "", "Avoid nans when reducing",
Options::Arguments::Zero,
[&](Options* o, const std::string& argument) {
deNan = true;
})
.add("--verbose", "-v", "Verbose output mode",
Options::Arguments::Zero,
[&](Options* o, const std::string& argument) {
Expand Down Expand Up @@ -997,7 +1021,7 @@ int main(int argc, const char* argv[]) {
bool stopping = false;

while (1) {
Reducer reducer(command, test, working, binary, verbose, debugInfo);
Reducer reducer(command, test, working, binary, deNan, verbose, debugInfo);

// run binaryen optimization passes to reduce. passes are fast to run
// and can often reduce large amounts of code efficiently, as opposed
Expand Down
11 changes: 11 additions & 0 deletions src/wasm/literal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ bool Literal::operator!=(const Literal& other) const {
return !(*this == other);
}

bool Literal::isNaN() {
if (type == Type::f32 && std::isnan(getf32())) {
return true;
}
if (type == Type::f64 && std::isnan(getf64())) {
return true;
}
// TODO: SIMD?
return false;
}

uint32_t Literal::NaNPayload(float f) {
assert(std::isnan(f) && "expected a NaN");
// SEEEEEEE EFFFFFFF FFFFFFFF FFFFFFFF
Expand Down
10 changes: 5 additions & 5 deletions test/passes/emit-js-wrapper=a.js.wast.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,33 +51,33 @@ try {
console.log('[fuzz-exec] calling $add');
console.log('[fuzz-exec] note result: $add => ' + literal(instance.exports.add(0, 0), 'i32'));
} catch (e) {
console.log('exception: ' + e);
console.log('exception!' /* + e */);
}
if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();
try {
console.log('[fuzz-exec] calling $no_return');
instance.exports.no_return(0);
} catch (e) {
console.log('exception: ' + e);
console.log('exception!' /* + e */);
}
if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();
try {
console.log('[fuzz-exec] calling $types');
instance.exports.types(0, 0, 0, 0, 0);
} catch (e) {
console.log('exception: ' + e);
console.log('exception!' /* + e */);
}
if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();
try {
console.log('[fuzz-exec] calling $types2');
instance.exports.types2(0, 0, 0);
} catch (e) {
console.log('exception: ' + e);
console.log('exception!' /* + e */);
}
if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();
try {
console.log('[fuzz-exec] calling $types3');
console.log('[fuzz-exec] note result: $types3 => ' + literal(instance.exports.types3(0, 0, 0), 'i32'));
} catch (e) {
console.log('exception: ' + e);
console.log('exception!' /* + e */);
}
36 changes: 5 additions & 31 deletions test/passes/translate-to-fuzz.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
(import "fuzzing-support" "log-f64" (func $log-f64 (param f64)))
(memory $0 (shared 1 1))
(data (i32.const 0) "n\00\05E\00\00\00\00")
(table $0 6 6 funcref)
(elem (i32.const 0) $func_6 $func_12 $func_12 $func_12 $func_15 $func_16)
(table $0 10 10 funcref)
(elem (i32.const 0) $func_6 $func_12 $func_12 $func_12 $func_15 $func_16 $func_17 $func_17 $func_17 $func_17)
(global $global$0 (mut f32) (f32.const 536870912))
(global $global$1 (mut f32) (f32.const 2147483648))
(global $global$2 (mut f64) (f64.const -1048576))
Expand Down Expand Up @@ -526,7 +526,7 @@
(global.get $hangLimit)
)
(return
(f32.const 865309568)
(f32.const 185009408)
)
)
(global.set $hangLimit
Expand All @@ -537,15 +537,9 @@
)
)
(if (result f32)
(i32.eqz
(if (result i32)
(i32.const 709182789)
(i32.const -4)
(i32.const 873467920)
)
)
(i32.const 1230459474)
(block $label$5 (result f32)
(f32.const 59953536565248)
(f32.const 121)
)
(block $label$6 (result f32)
(f32.const 1)
Expand All @@ -557,24 +551,4 @@
(i32.const 10)
)
)
(func $deNan32 (; 19 ;) (param $0 f32) (result f32)
(if (result f32)
(f32.eq
(local.get $0)
(local.get $0)
)
(local.get $0)
(f32.const 0)
)
)
(func $deNan64 (; 20 ;) (param $0 f64) (result f64)
(if (result f64)
(f64.eq
(local.get $0)
(local.get $0)
)
(local.get $0)
(f64.const 0)
)
)
)
Loading