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
52 changes: 37 additions & 15 deletions scripts/fuzz_shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,34 @@ function toAddressType(table, index) {
return index;
}

// Simple deterministic hashing, on an unsigned 32-bit seed. See e.g.
// https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine
var hashSeed;

function hasHashSeed() {
return hashSeed !== undefined;
}

function hashCombine(value) {
// hashSeed must be set before we do anything.
assert(hasHashSeed());

hashSeed ^= value + 0x9e3779b9 + (hashSeed << 6) + (hashSeed >>> 2);
return hashSeed >>> 0;
}

// Get a random 32-bit number. This is like hashCombine but does not take a
// parameter.
function randomBits() {
return hashCombine(-1);
}

// Return true with probability 1 in n. E.g. oneIn(3) returns false 2/3 of the
// time, and true 1/3 of the time.
function oneIn(n) {
return (randomBits() % n) == 0;
}

// Set up the imports.
var tempRet0;
var imports = {
Expand Down Expand Up @@ -274,7 +302,10 @@ var imports = {

// Sleep a given amount of ms (when JSPI) and return a given id after that.
'sleep': (ms, id) => {
if (!JSPI) {
// Also avoid sleeping even in JSPI mode, rarely, just to add variety
// here. Only do this when we have a hash seed, that is, when we are
// allowing randomness.
if (!JSPI || (hasHashSeed() && oneIn(10))) {
return id;
}
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -371,20 +402,15 @@ function build(binary) {
}
}

// Simple deterministic hashing, on an unsigned 32-bit seed. See e.g.
// https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine
function hashCombine(seed, value) {
seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >>> 2);
return seed >>> 0;
}

// Run the code by calling exports. The optional |ordering| parameter indicates
// howe we should order the calls to the exports: if it is not provided, we call
// them in the natural order, which allows our output to be compared to other
// executions of the wasm (e.g. from wasm-opt --fuzz-exec). If |ordering| is
// provided, it is a random seed we use to make deterministic choices on
// the order of calls.
/* async */ function callExports(ordering) {
hashSeed = ordering;

// Call the exports we were told, or if we were not given an explicit list,
// call them all.
let relevantExports = exportsToCall || exportList;
Expand Down Expand Up @@ -425,13 +451,12 @@ function hashCombine(seed, value) {
task = tasks.pop();
} else {
// Pick a random task.
ordering = hashCombine(ordering, tasks.length);
let i = ordering % tasks.length;
let i = hashCombine(tasks.length) % tasks.length;
task = tasks.splice(i, 1)[0];
}

// Execute the task.
console.log('[fuzz-exec] calling ' + task.name);
console.log(`[fuzz-exec] calling ${task.name}${task.deferred ? ' (after defer)' : ''}`);
let result;
try {
result = task.func();
Expand All @@ -451,10 +476,7 @@ function hashCombine(seed, value) {
// depending on each other, ensuring certain orders of execution.
if (ordering !== undefined && !task.deferred && result &&
typeof result == 'object' && typeof result.then === 'function') {
// Hash with -1 here, just to get something different than the hashing a
// few lines above.
ordering = hashCombine(ordering, -1);
if (ordering & 1) {
if (randomBits() & 1) {
// Defer it for later. Reuse the existing task for simplicity.
console.log(`(jspi: defer ${task.name})`);
task.func = /* async */ () => {
Expand Down
72 changes: 72 additions & 0 deletions test/lit/d8/fuzz_shell_sleep.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
(module
(import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32)))

(func $func1 (export "func1") (result i32)
(call $sleep
(i32.const 0) ;; ms (d8 always sleeps 0 anyhow)
(i32.const 1) ;; id
)
)

(func $func2 (export "func2") (result i32)
(call $sleep
(i32.const 0)
(i32.const 2)
)
)

(func $func3 (export "func3") (result i32)
(call $sleep
(i32.const 0)
(i32.const 3)
)
)

(func $func4 (export "func4") (result i32)
(call $sleep
(i32.const 0)
(i32.const 4)
)
)

(func $func5 (export "func5") (result i32)
(call $sleep
(i32.const 0)
(i32.const 5)
)
)
)

;; See fuzz_shell_jspi.wast for how the following works.
;; RUN: echo "JSPI = 1;" > %t.0.js
;; RUN: cat %S/../../../scripts/fuzz_shell.js | node -e "process.stdout.write(require('fs').readFileSync(0, 'utf-8').replace(/[/][*] async [*][/]/g, 'async').replace(/[/][*] await [*][/]/g, 'await'))" >> %t.0.js

;; Replace the callExports() at the end with a call that has a random seed.
;; RUN: cat %t.0.js | node -e "process.stdout.write(require('fs').readFileSync(0, 'utf-8').replace('callExports()', 'callExports(66)'))" > %t.js

;; Run that JS shell with our wasm.
;; RUN: wasm-opt %s -o %t.wasm -q
;; RUN: v8 --wasm-staging %t.js -- %t.wasm | filecheck %s
;;
;; We should see a few cases that avoid sleeping: func2, func3, and func4 all
;; return a result immediately, showing they do not sleep. (Note though that
;; func2 is more because we do not have a toplevel await, see comment in
;; fuzz_shell_jspi.wast.)
;;
;; CHECK: [fuzz-exec] calling func2
;; CHECK: [fuzz-exec] note result: func2 => 2
;; CHECK: [fuzz-exec] calling func1
;; CHECK: (jspi: defer func1)
;; CHECK: [fuzz-exec] calling func3
;; CHECK: [fuzz-exec] note result: func3 => 3
;; CHECK: [fuzz-exec] calling func1 (after defer)
;; CHECK: (jspi: finish func1)
;; CHECK: [fuzz-exec] note result: func1 => 1
;; CHECK: [fuzz-exec] calling func5
;; CHECK: (jspi: defer func5)
;; CHECK: [fuzz-exec] calling func4
;; CHECK: [fuzz-exec] note result: func4 => 4
;; CHECK: [fuzz-exec] calling func5 (after defer)
;; CHECK: (jspi: finish func5)
;; CHECK: [fuzz-exec] note result: func5 => 5

Loading