Skip to content

Conversation

brendandahl
Copy link
Collaborator

The _emval_await library function is marked _emval_await__async: true, but the js function is not async. With memory64 enabled we auto convert to bigint and look for the async keyword (which is missing) to apply the await before creating the BigInt. With my changes __async will require an async js function, which signals the function is used with JSPI and the appropriate awaits are then inserted.

Fixes #25468

The `_emval_await` library function is marked `_emval_await__async: true`,
but the js function is not async. With memory64 enabled we auto convert to
bigint and look for the async keyword (which is missing) to apply the await
before creating the BigInt. With my changes __async will require an
async js function, which signals the function is used with JSPI and the
appropriate awaits are then inserted.

import assert from 'node:assert';
import * as fs from 'node:fs/promises';
import { isAsyncFunction } from 'node:util/types';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh.. interesting. TIL about this.

});
},
#endif
#if ASYNCIFY == 2
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this just be #else Since it can only be either 1 or 2 right? Or elif would work too.

_emval_await: async (promise) => {
var value = await Emval.toValue(promise);
return Emval.toHandle(value);
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't really get why this functions needs to looks so different in ASYNCIFY 1 vs 2.

In other places in this PR we have async (...) => Asyncify.handleSleep(..... but not here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unfortunately, handleAsync currently works slightly differently on old asyncify and JSPI. On JSPI, it returns the promise and on asyncify it pipes the return value magically through the wakeup. Adding the async keyword here for asyncify=1 causes the test to fail.

I'd really like to unify all this code and have it so if you mark a library function with __async you don't have to use handleSleep or handleAsync, but that's a bigger change.

Copy link
Collaborator

Choose a reason for hiding this comment

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

SGTM

SDL_Delay__deps: ['emscripten_sleep'],
SDL_Delay__async: true,
SDL_Delay: (delay) => _emscripten_sleep(delay),
SDL_Delay: async (delay) => _emscripten_sleep(delay),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I kind of which #if ASYNCIFY case came first here, but that unrelated to this PR

'dyncall': [['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB']],
'wasm64': (['-sMEMORY64'],),
})
@requires_jspi
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about also_with_wasm64 so that we get all 4 cases in wasm64 mode?

}
''')
err = self.expect_fail([EMCC, 'main.c', '-o', 'out.js', '-sJSPI', '--js-library=lib.js', '-Wno-experimental',])
self.assertContained('error: foo is marked with the __async decorator but is not an async JS function.', err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe move this function alongside all the other test_jslib_xx functions? Or maybe its more suited to being grouped with JSPI? Not sure.

emscripten_sleep__deps: ['$safeSetTimeout'],
emscripten_sleep__async: true,
emscripten_sleep: (ms) => Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)),
emscripten_sleep: async (ms) => Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)),
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does flagging this async gain here? (it costs code size)

I understand the rationale for async is that one can await for the return value, but for functions that return void, isn't the async keyword dead code?

Though Asyncify and JSPI build modes itself do not need the functions to be flagged async do they? (in WebGPU JSPI support I went with this)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It really doesn't add anything here, but consistency. We have a few spots where we auto modify the library js (e.g. memory 64). It's much easier in those wrapper functions to assume we're in a async function and emit await code (also the await code is usually shorter).

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.

Embind + MEMORY64: Cannot convert Promise to a BigInt
3 participants