Skip to content

Commit 7353da7

Browse files
authored
JSPI: Add a chance for sleep() to not actually sleep (WebAssembly#7244)
Rather than return a promise, it returns an integer immediately.
1 parent 3e85182 commit 7353da7

File tree

2 files changed

+109
-15
lines changed

2 files changed

+109
-15
lines changed

scripts/fuzz_shell.js

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,34 @@ function toAddressType(table, index) {
224224
return index;
225225
}
226226

227+
// Simple deterministic hashing, on an unsigned 32-bit seed. See e.g.
228+
// https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine
229+
var hashSeed;
230+
231+
function hasHashSeed() {
232+
return hashSeed !== undefined;
233+
}
234+
235+
function hashCombine(value) {
236+
// hashSeed must be set before we do anything.
237+
assert(hasHashSeed());
238+
239+
hashSeed ^= value + 0x9e3779b9 + (hashSeed << 6) + (hashSeed >>> 2);
240+
return hashSeed >>> 0;
241+
}
242+
243+
// Get a random 32-bit number. This is like hashCombine but does not take a
244+
// parameter.
245+
function randomBits() {
246+
return hashCombine(-1);
247+
}
248+
249+
// Return true with probability 1 in n. E.g. oneIn(3) returns false 2/3 of the
250+
// time, and true 1/3 of the time.
251+
function oneIn(n) {
252+
return (randomBits() % n) == 0;
253+
}
254+
227255
// Set up the imports.
228256
var tempRet0;
229257
var imports = {
@@ -274,7 +302,10 @@ var imports = {
274302

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

374-
// Simple deterministic hashing, on an unsigned 32-bit seed. See e.g.
375-
// https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine
376-
function hashCombine(seed, value) {
377-
seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >>> 2);
378-
return seed >>> 0;
379-
}
380-
381405
// Run the code by calling exports. The optional |ordering| parameter indicates
382406
// howe we should order the calls to the exports: if it is not provided, we call
383407
// them in the natural order, which allows our output to be compared to other
384408
// executions of the wasm (e.g. from wasm-opt --fuzz-exec). If |ordering| is
385409
// provided, it is a random seed we use to make deterministic choices on
386410
// the order of calls.
387411
/* async */ function callExports(ordering) {
412+
hashSeed = ordering;
413+
388414
// Call the exports we were told, or if we were not given an explicit list,
389415
// call them all.
390416
let relevantExports = exportsToCall || exportList;
@@ -425,13 +451,12 @@ function hashCombine(seed, value) {
425451
task = tasks.pop();
426452
} else {
427453
// Pick a random task.
428-
ordering = hashCombine(ordering, tasks.length);
429-
let i = ordering % tasks.length;
454+
let i = hashCombine(tasks.length) % tasks.length;
430455
task = tasks.splice(i, 1)[0];
431456
}
432457

433458
// Execute the task.
434-
console.log('[fuzz-exec] calling ' + task.name);
459+
console.log(`[fuzz-exec] calling ${task.name}${task.deferred ? ' (after defer)' : ''}`);
435460
let result;
436461
try {
437462
result = task.func();
@@ -451,10 +476,7 @@ function hashCombine(seed, value) {
451476
// depending on each other, ensuring certain orders of execution.
452477
if (ordering !== undefined && !task.deferred && result &&
453478
typeof result == 'object' && typeof result.then === 'function') {
454-
// Hash with -1 here, just to get something different than the hashing a
455-
// few lines above.
456-
ordering = hashCombine(ordering, -1);
457-
if (ordering & 1) {
479+
if (randomBits() & 1) {
458480
// Defer it for later. Reuse the existing task for simplicity.
459481
console.log(`(jspi: defer ${task.name})`);
460482
task.func = /* async */ () => {

test/lit/d8/fuzz_shell_sleep.wast

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
(module
2+
(import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32)))
3+
4+
(func $func1 (export "func1") (result i32)
5+
(call $sleep
6+
(i32.const 0) ;; ms (d8 always sleeps 0 anyhow)
7+
(i32.const 1) ;; id
8+
)
9+
)
10+
11+
(func $func2 (export "func2") (result i32)
12+
(call $sleep
13+
(i32.const 0)
14+
(i32.const 2)
15+
)
16+
)
17+
18+
(func $func3 (export "func3") (result i32)
19+
(call $sleep
20+
(i32.const 0)
21+
(i32.const 3)
22+
)
23+
)
24+
25+
(func $func4 (export "func4") (result i32)
26+
(call $sleep
27+
(i32.const 0)
28+
(i32.const 4)
29+
)
30+
)
31+
32+
(func $func5 (export "func5") (result i32)
33+
(call $sleep
34+
(i32.const 0)
35+
(i32.const 5)
36+
)
37+
)
38+
)
39+
40+
;; See fuzz_shell_jspi.wast for how the following works.
41+
;; RUN: echo "JSPI = 1;" > %t.0.js
42+
;; 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
43+
44+
;; Replace the callExports() at the end with a call that has a random seed.
45+
;; RUN: cat %t.0.js | node -e "process.stdout.write(require('fs').readFileSync(0, 'utf-8').replace('callExports()', 'callExports(66)'))" > %t.js
46+
47+
;; Run that JS shell with our wasm.
48+
;; RUN: wasm-opt %s -o %t.wasm -q
49+
;; RUN: v8 --wasm-staging %t.js -- %t.wasm | filecheck %s
50+
;;
51+
;; We should see a few cases that avoid sleeping: func2, func3, and func4 all
52+
;; return a result immediately, showing they do not sleep. (Note though that
53+
;; func2 is more because we do not have a toplevel await, see comment in
54+
;; fuzz_shell_jspi.wast.)
55+
;;
56+
;; CHECK: [fuzz-exec] calling func2
57+
;; CHECK: [fuzz-exec] note result: func2 => 2
58+
;; CHECK: [fuzz-exec] calling func1
59+
;; CHECK: (jspi: defer func1)
60+
;; CHECK: [fuzz-exec] calling func3
61+
;; CHECK: [fuzz-exec] note result: func3 => 3
62+
;; CHECK: [fuzz-exec] calling func1 (after defer)
63+
;; CHECK: (jspi: finish func1)
64+
;; CHECK: [fuzz-exec] note result: func1 => 1
65+
;; CHECK: [fuzz-exec] calling func5
66+
;; CHECK: (jspi: defer func5)
67+
;; CHECK: [fuzz-exec] calling func4
68+
;; CHECK: [fuzz-exec] note result: func4 => 4
69+
;; CHECK: [fuzz-exec] calling func5 (after defer)
70+
;; CHECK: (jspi: finish func5)
71+
;; CHECK: [fuzz-exec] note result: func5 => 5
72+

0 commit comments

Comments
 (0)