Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSPI Fuzzing: Interleave executions #7226

Merged
merged 55 commits into from
Jan 24, 2025

Conversation

kripken
Copy link
Member

@kripken kripken commented Jan 17, 2025

Rather than always do

await export()

now we might stash the Promise on the side, and execute it later, after
other stacks are executed and perhaps also saved.

To do this, rewrite the logic for calling the exports in a more flexible
manner. (That required altering the random seed in fuzz_shell_orders.wast,
to preserve the current order it was emitting.)

We do not fuzz with top-level await, so the output here looks a bit out
of order, but it does still end up with interleaved executions, which I think
is useful for fuzzing. @brendandahl maybe there is a better idea here though?

if (ordering & 1) {
// Defer it for later. Reuse the existing task for simplicity.
console.log(`(jspi: defer ${task.name})`);
task.func = /* async */ () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is all in if (JSPI), do the async and await need to be comments?

Copy link
Member Author

@kripken kripken Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My (perhaps overly paranoid) approach was that when JSPI is off, we don't want the JS VM to see any async stuff, which could affect codegen. Like maybe they lay out the stack or basic blocks differently in async functions?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would hope these isolated awaits/asyncs would not affect other code. Anway, I'm fine with leaving them since it's consistent with all the other uses.

console.log(`(jspi: defer ${task.name})`);
task.func = /* async */ () => {
console.log(`(jspi: finish ${task.name})`);
return /* await */ result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this await be in a try/catch as well? If the suspended Wasm ends up throwing an error, that error would propagate out here IIUC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the try-catch on 468 is enough? This code ends up run in that try-catch, so yes, an exception would propagate, but just to that try-catch, where it is caught and handled. (Note that we can't really add any handling for it here - all we could do is log and rethrow?)

Comment on lines 445 to 446
// promises unresolved until later, which lets us interleave them. Note we
// never defer a task more than once (which would be pointless), and we
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferring a task more than once would be interesting if it were deferred via chaining to another promise. And it would be even more interesting if it were "chained" to another promise by being returned from a future JSPI import call.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, interesting, what do you mean by "chaining" here? Is there an API for that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var newPromise = oldPromise.then((x) => { return x; }); or similar ways of wrapping the contents of the old promise in a new promise that will only be resolved after the old promise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think that would test anything on the wasm side? I'd guess that just defers any wasm work further? I don't really know the VM side though.

As for chaining to a future JSPI call, that does happen already, if there is this in the wasm:

sleep();
sleep();
sleep();

Each one sleeps, then we continue, then we sleep again etc., which I think achieves the same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, maybe chaining in pure JS wouldn't add much. I think chaining through future JSPI calls might still be interesting because it would mix up the temporal order of tasks. The common case is that a task will not be resumed until a subsequently started task is completed, as in the sequence of sleeps above, but it is also interesting to test tasks that will not be resumed until prior tasks are completed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a todo.

;; RUN: echo "JSPI = 1;" > %t.js

;; Use |echo| here to avoid quoting issues on windows paths.
;; RUN: echo %S | node -e "process.stdout.write(require('fs').readFileSync(path.join(require('fs').readFileSync(0, 'utf-8'), '..', '..', '..', 'scripts', 'fuzz_shell.js'), 'utf-8').replace(/[/][*] async [*][/]/g, 'async').replace(/[/][*] await [*][/]/g, 'await'))" >> %t.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this go in a helper script of some kind? At the very least, could we split the logic across multiple RUN lines?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heh, yeah, this is a pretty horrible line. It was a hard-fought battle to even get it to work across all platforms!

I did some more work to simplify it now, it turns out lit has cat support (at least on linux... we'll see on CI for windows). If that works, I don't think we can split any more.

Copy link
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM % a possible TODO about testing more chaining interactions.

@kripken
Copy link
Member Author

kripken commented Jan 23, 2025

@brendandahl any more thoughts/ideas here?

@brendandahl
Copy link
Collaborator

@brendandahl any more thoughts/ideas here?

It's not really related to this PR, but the only other thing I can think of is having an import that sometimes return a value synchronously (i.e. not even through async function) and sometimes a promise. This really shouldn't behave differently now that v8 always converts the return value to a promise, so I'm not sure it's worth testing.

@kripken
Copy link
Member Author

kripken commented Jan 24, 2025

Thanks @brendandahl , good idea, I'll look into that in a later PR.

@kripken kripken merged commit bb876a5 into WebAssembly:main Jan 24, 2025
13 checks passed
@kripken kripken deleted the fuzz.interleave.jspi branch January 24, 2025 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants