Skip to content

Commit

Permalink
WIP: await promise code from sync c
Browse files Browse the repository at this point in the history
  • Loading branch information
hedwigz committed Dec 1, 2024
1 parent 06cebfc commit 0a58ce7
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/jsifier.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ function(${args}) {
return `
function(${args}) {
if (ENVIRONMENT_IS_PTHREAD)
return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}${args ? ', ' : ''}${args});
return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}, 0${args ? ', ' : ''}${args});
${body}
}\n`;
});
Expand Down
13 changes: 8 additions & 5 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -1631,7 +1631,7 @@ addToLibrary({
'$proxyToMainThread'
#endif
],
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync) => {
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync, promise) => {
var args = readEmAsmArgs(sigPtr, argbuf);
#if PTHREADS
if (ENVIRONMENT_IS_PTHREAD) {
Expand All @@ -1644,7 +1644,7 @@ addToLibrary({
// of using __proxy. (And dor simplicity, do the same in the sync
// case as well, even though it's not strictly necessary, to keep the two
// code paths as similar as possible on both sides.)
return proxyToMainThread(0, emAsmAddr, sync, ...args);
return proxyToMainThread(0, emAsmAddr, sync, promise, ...args);
}
#endif
#if ASSERTIONS
Expand All @@ -1653,14 +1653,17 @@ addToLibrary({
return ASM_CONSTS[emAsmAddr](...args);
},
emscripten_asm_const_int_sync_on_main_thread__deps: ['$runMainThreadEmAsm'],
emscripten_asm_const_int_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1),
emscripten_asm_const_int_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1, 0),

emscripten_asm_const_int_await_promise_on_main_thread__deps: ['$runMainThreadEmAsm'],
emscripten_asm_const_int_await_promise_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1, 1),

emscripten_asm_const_ptr_sync_on_main_thread__deps: ['$runMainThreadEmAsm'],
emscripten_asm_const_ptr_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1),
emscripten_asm_const_ptr_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1, 0),

emscripten_asm_const_double_sync_on_main_thread: 'emscripten_asm_const_int_sync_on_main_thread',
emscripten_asm_const_async_on_main_thread__deps: ['$runMainThreadEmAsm'],
emscripten_asm_const_async_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 0),
emscripten_asm_const_async_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 0, 0),
#endif

#if !DECLARE_ASM_MODULE_EXPORTS
Expand Down
27 changes: 24 additions & 3 deletions src/library_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ var LibraryPThread = {
$proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_on_main_thread_js', ...i53ConversionDeps],
$proxyToMainThread__docs: '/** @type{function(number, (number|boolean), ...number)} */',
$proxyToMainThread: (funcIndex, emAsmAddr, sync, ...callArgs) => {
$proxyToMainThread: (funcIndex, emAsmAddr, sync, promise, ...callArgs) => {
// EM_ASM proxying is done by passing a pointer to the address of the EM_ASM
// content as `emAsmAddr`. JS library proxying is done by passing an index
// into `proxiedJSCallArgs` as `funcIndex`. If `emAsmAddr` is non-zero then
Expand Down Expand Up @@ -933,7 +933,7 @@ var LibraryPThread = {
HEAPF64[b + i] = arg;
#endif
}
var rtn = __emscripten_run_on_main_thread_js(funcIndex, emAsmAddr, serializedNumCallArgs, args, sync);
var rtn = __emscripten_run_on_main_thread_js(funcIndex, emAsmAddr, serializedNumCallArgs, args, sync, promise);
stackRestore(sp);
return rtn;
},
Expand All @@ -944,7 +944,7 @@ var LibraryPThread = {
_emscripten_receive_on_main_thread_js__deps: [
'$proxyToMainThread',
'$proxiedJSCallArgs'],
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, numCallArgs, args) => {
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, numCallArgs, args, promiseCtx) => {
// Sometimes we need to backproxy events to the calling thread (e.g.
// HTML5 DOM events handlers such as
// emscripten_set_mousemove_callback()), so keep track in a globally
Expand Down Expand Up @@ -983,6 +983,27 @@ var LibraryPThread = {
PThread.currentProxiedOperationCallerThread = callingThread;
var rtn = func(...proxiedJSCallArgs);
PThread.currentProxiedOperationCallerThread = 0;
if (promiseCtx) {
#if ASSERTIONS
assert(!!rtn.then, 'Return value of proxied function expected to be a Promise but got' + rtn);
#endif
if (!rtn.then) {
throw new Error('Return value of proxied function expected to be a Promise but got' + rtn);
}
rtn.then(res => {
#if MEMORY64
// In memory64 mode some proxied functions return bigint/pointer but
// our return type is i53/double.
if (typeof res == "bigint") {
res = bigintToI53Checked(res);
}
#endif
__emscripten_proxy_promise_finish(res, promiseCtx);
}).catch(err => {
__emscripten_proxy_promise_finish(err, promiseCtx);
});
return;
}
#if MEMORY64
// In memory64 mode some proxied functions return bigint/pointer but
// our return type is i53/double.
Expand Down
3 changes: 2 additions & 1 deletion src/library_sigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ sigs = {
_emscripten_notify_mailbox_postmessage__sig: 'vpp',
_emscripten_push_main_loop_blocker__sig: 'vppp',
_emscripten_push_uncounted_main_loop_blocker__sig: 'vppp',
_emscripten_receive_on_main_thread_js__sig: 'dippip',
_emscripten_receive_on_main_thread_js__sig: 'dippipp',
_emscripten_runtime_keepalive_clear__sig: 'v',
_emscripten_set_offscreencanvas_size__sig: 'ipii',
_emscripten_system__sig: 'ip',
Expand Down Expand Up @@ -570,6 +570,7 @@ sigs = {
emscripten_asm_const_double_sync_on_main_thread__sig: 'dppp',
emscripten_asm_const_int__sig: 'ippp',
emscripten_asm_const_int_sync_on_main_thread__sig: 'ippp',
emscripten_asm_const_int_await_promise_on_main_thread__sig: 'ippp',
emscripten_asm_const_ptr__sig: 'pppp',
emscripten_asm_const_ptr_sync_on_main_thread__sig: 'pppp',
emscripten_async_call__sig: 'vppi',
Expand Down
6 changes: 6 additions & 0 deletions system/include/emscripten/em_asm.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ __attribute__((nothrow))
int emscripten_asm_const_int_sync_on_main_thread(
const char* code, const char* arg_sigs, ...);
__attribute__((nothrow))
int emscripten_asm_const_int_await_promise_on_main_thread(
const char* code, const char* arg_sigs, ...);
__attribute__((nothrow))
void* emscripten_asm_const_ptr_sync_on_main_thread(
const char* code, const char* arg_sigs, ...);
__attribute__((nothrow))
Expand All @@ -51,6 +54,7 @@ void emscripten_asm_const_async_on_main_thread(
#define EM_ASM_PTR(...) EM_ASM_ERROR
#define EM_ASM_DOUBLE(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_PROMISE_AWAIT(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_INT(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_PTR(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_DOUBLE(...) EM_ASM_ERROR
Expand Down Expand Up @@ -250,6 +254,8 @@ const char __em_asm_sig_builder<__em_asm_type_tuple<Args...> >::buffer[] = { __e
// functions.
#define MAIN_THREAD_EM_ASM(code, ...) ((void)emscripten_asm_const_int_sync_on_main_thread(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__)))

#define MAIN_THREAD_EM_ASM_PROMISE_AWAIT(code, ...) ((void)emscripten_asm_const_int_await_promise_on_main_thread(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__)))

// Runs the given JavaScript code synchronously on the main browser thread, and
// returns an integer back.
// The same considerations apply as with MAIN_THREAD_EM_ASM().
Expand Down
82 changes: 69 additions & 13 deletions system/lib/pthread/proxying.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "em_task_queue.h"
#include "thread_mailbox.h"
Expand All @@ -35,6 +36,17 @@ static em_proxying_queue system_proxying_queue = {
.capacity = 0,
};

typedef struct proxied_js_func_t {
int funcIndex;
void* emAsmAddr;
pthread_t callingThread;
int numArgs;
double* argBuffer;
double result;
bool owned;
em_proxying_ctx * ctx;
} proxied_js_func_t;

em_proxying_queue* emscripten_proxy_get_system_queue(void) {
return &system_proxying_queue;
}
Expand Down Expand Up @@ -390,13 +402,41 @@ int emscripten_proxy_sync_with_ctx(em_proxying_queue* q,
return ret;
}

int emscripten_proxy_promise_await_with_ctx(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(em_proxying_ctx*, void*),
void* arg, proxied_js_func_t* f) {
assert(!pthread_equal(target_thread, pthread_self()) &&
"Cannot synchronously wait for work proxied to the current thread");
em_proxying_ctx ctx;
em_proxying_ctx_init_sync(&ctx, func, arg);
f->ctx = &ctx;
if (!do_proxy(q, target_thread, (task){call_with_ctx, cancel_ctx, &ctx})) {
em_proxying_ctx_deinit(&ctx);
return 0;
}
pthread_mutex_lock(&ctx.sync.mutex);
while (ctx.sync.state == PENDING) {
pthread_cond_wait(&ctx.sync.cond, &ctx.sync.mutex);
}
pthread_mutex_unlock(&ctx.sync.mutex);
int ret = ctx.sync.state == DONE;
em_proxying_ctx_deinit(&ctx);
return ret;
}

// Helper for signaling the end of the task after the user function returns.
static void call_then_finish_task(em_proxying_ctx* ctx, void* arg) {
task* t = arg;
t->func(t->arg);
emscripten_proxy_finish(ctx);
}

static void call_task(em_proxying_ctx* ctx, void* arg) {
task* t = arg;
t->func(t->arg);
}

int emscripten_proxy_sync(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(void*),
Expand All @@ -406,6 +446,15 @@ int emscripten_proxy_sync(em_proxying_queue* q,
q, target_thread, call_then_finish_task, &t);
}

int emscripten_proxy_promise_await(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(void*),
proxied_js_func_t* f) {
task t = {.func = func, .arg = (void*)f};
return emscripten_proxy_promise_await_with_ctx(
q, target_thread, call_task, &t, f);
}

static int do_proxy_callback(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(em_proxying_ctx* ctx, void*),
Expand Down Expand Up @@ -583,31 +632,32 @@ em_promise_t emscripten_proxy_promise(em_proxying_queue* q,
&block->promise_ctx);
}

typedef struct proxied_js_func_t {
int funcIndex;
void* emAsmAddr;
pthread_t callingThread;
int numArgs;
double* argBuffer;
double result;
bool owned;
} proxied_js_func_t;

static void run_js_func(void* arg) {
proxied_js_func_t* f = (proxied_js_func_t*)arg;
f->result = _emscripten_receive_on_main_thread_js(
f->funcIndex, f->emAsmAddr, f->callingThread, f->numArgs, f->argBuffer);
f->funcIndex, f->emAsmAddr, f->callingThread, f->numArgs, f->argBuffer, NULL);
if (f->owned) {
free(f->argBuffer);
free(f);
}
}

static void run_js_promise_await(void* arg) {
proxied_js_func_t* f = (proxied_js_func_t*)arg;
f->result = _emscripten_receive_on_main_thread_js(
f->funcIndex, f->emAsmAddr, f->callingThread, f->numArgs, f->argBuffer, (void*)f->ctx);
}

void _emscripten_proxy_promise_finish(void* res, em_proxying_ctx* ctx) {
emscripten_proxy_finish(ctx);
}

double _emscripten_run_on_main_thread_js(int func_index,
void* em_asm_addr,
int num_args,
double* buffer,
int sync) {
int sync,
int promise) {
proxied_js_func_t f = {
.funcIndex = func_index,
.emAsmAddr = em_asm_addr,
Expand All @@ -620,12 +670,18 @@ double _emscripten_run_on_main_thread_js(int func_index,
em_proxying_queue* q = emscripten_proxy_get_system_queue();
pthread_t target = emscripten_main_runtime_thread_id();

if (sync) {
if (sync && !promise) {
if (!emscripten_proxy_sync(q, target, run_js_func, &f)) {
assert(false && "emscripten_proxy_sync failed");
return 0;
}
return f.result;
} else if (sync && promise) {
if (!emscripten_proxy_promise_await(q, target, run_js_promise_await, &f)) {
assert(false && "emscripten_proxy_promise failed");
return 0;
}
return f.result;
}

// Make a heap-heap allocated copy of the proxied_js_func_t
Expand Down
2 changes: 1 addition & 1 deletion system/lib/pthread/threading_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ int __pthread_create_js(struct __pthread *thread, const pthread_attr_t *attr, vo
int _emscripten_default_pthread_stack_size();
void __set_thread_state(pthread_t ptr, int is_main, int is_runtime, int can_block);

double _emscripten_receive_on_main_thread_js(int funcIndex, void* emAsmAddr, pthread_t callingThread, int numCallArgs, double* args);
double _emscripten_receive_on_main_thread_js(int funcIndex, void* emAsmAddr, pthread_t callingThread, int numCallArgs, double* args, void *ctx);

// Return non-zero if the calling thread supports Atomic.wait (For example
// if called from the main browser thread, this function will return zero
Expand Down
24 changes: 24 additions & 0 deletions test/core/test_main_thread_async_em_asm_promise_await.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2017 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.

#include <emscripten.h>
#include <stdio.h>

int main()
{
printf("Before MAIN_THREAD_EM_ASM_PROMISE_AWAIT\n");
MAIN_THREAD_EM_ASM_PROMISE_AWAIT({
out('Inside MAIN_THREAD_EM_ASM_PROMISE_AWAIT: ' + $0 + ' ' + $1);
const asyncOp = new Promise((resolve,reject) => {
setTimeout(() => {
out('Inside asyncOp');
resolve();
}, 1000);
});
return asyncOp;
}, 42, 3.5);
printf("After MAIN_THREAD_EM_ASM_PROMISE_AWAIT\n");
return 0;
}
4 changes: 4 additions & 0 deletions test/core/test_main_thread_async_em_asm_promise_await.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Before MAIN_THREAD_EM_ASM_PROMISE_AWAIT
Inside MAIN_THREAD_EM_ASM_PROMISE_AWAIT: 42 3.5
Inside asyncOp
After MAIN_THREAD_EM_ASM_PROMISE_AWAIT
7 changes: 7 additions & 0 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,13 @@ def test_main_thread_em_asm(self, args):
def test_main_thread_async_em_asm(self, args, force_c=False):
self.do_core_test('test_main_thread_async_em_asm.cpp', emcc_args=args, force_c=force_c)

@needs_dylink
@parameterized({
'pthreads': (['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'], False),
})
def test_main_thread_async_em_asm_promise_await(self, args, force_c=False):
self.do_core_test('test_main_thread_async_em_asm_promise_await.cpp', emcc_args=args, force_c=force_c)

# Tests MAIN_THREAD_EM_ASM_INT() function call with different signatures.
def test_main_thread_em_asm_signatures(self):
self.do_core_test('test_em_asm_signatures.cpp', assert_returncode=NON_ZERO)
Expand Down
1 change: 1 addition & 0 deletions tools/emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,7 @@ def create_pointer_conversion_wrappers(metadata):
'_wasmfs_read_file': 'pp',
'__dl_seterr': '_pp',
'_emscripten_run_on_main_thread_js': '__p_p_',
'_emscripten_proxy_promise_finish': 'pp',
'_emscripten_proxy_execute_task_queue': '_p',
'_emscripten_thread_exit': '_p',
'_emscripten_thread_init': '_p_____',
Expand Down
1 change: 1 addition & 0 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ def setup_pthreads():
'emscripten_main_runtime_thread_id',
'emscripten_main_thread_process_queued_calls',
'_emscripten_run_on_main_thread_js',
'_emscripten_proxy_promise_finish',
'emscripten_stack_set_limits',
]

Expand Down

0 comments on commit 0a58ce7

Please sign in to comment.