Path: blob/main/system/lib/pthread/proxying_legacy.c
6174 views
/*1* Copyright 2023 The Emscripten Authors. All rights reserved.2* Emscripten is available under two separate licenses, the MIT license and the3* University of Illinois/NCSA Open Source License. Both these licenses can be4* found in the LICENSE file.5*/6#include <assert.h>7#include <math.h>8#include <stdatomic.h>910#include <emscripten/threading.h>11#include <emscripten/console.h>1213#include "../internal/pthread_impl.h"1415#include "threading_internal.h"16#include "emscripten_internal.h"1718typedef union em_variant_val {19int i;20int64_t j;21float f;22double d;23void *vp;24char *cp;25} em_variant_val;2627typedef struct em_queued_call {28int functionEnum;29void *functionPtr;30_Atomic uint32_t operationDone;31em_variant_val args[EM_QUEUED_JS_CALL_MAX_ARGS];32em_variant_val returnValue;3334// Sets the PThread.currentProxiedOperationCallerThread global for the35// duration of the proxied call.36pthread_t callingThread;3738// An optional pointer to a secondary data block that should be free()d when39// this queued call is freed.40void *satelliteData;4142// If true, the caller has "detached" itself from this call object and the43// Emscripten main runtime thread should free up this em_queued_call object44// after it has been executed. If false, the caller is in control of the45// memory.46int calleeDelete;47} em_queued_call;4849// Proxied C/C++ functions support at most this many arguments. Dispatch is50// static/strongly typed by signature.51#define EM_QUEUED_CALL_MAX_ARGS 115253typedef void (*em_func_v)(void);54typedef void (*em_func_vi)(int);55typedef void (*em_func_vf)(float);56typedef void (*em_func_vii)(int, int);57typedef void (*em_func_vif)(int, float);58typedef void (*em_func_vff)(float, float);59typedef void (*em_func_viii)(int, int, int);60typedef void (*em_func_viij)(int, int, uint64_t);61typedef void (*em_func_viif)(int, int, float);62typedef void (*em_func_viff)(int, float, float);63typedef void (*em_func_vfff)(float, float, float);64typedef void (*em_func_viiii)(int, int, int, int);65typedef void (*em_func_viifi)(int, int, float, int);66typedef void (*em_func_vifff)(int, float, float, float);67typedef void (*em_func_vffff)(float, float, float, float);68typedef void (*em_func_viiiii)(int, int, int, int, int);69typedef void (*em_func_viffff)(int, float, float, float, float);70typedef void (*em_func_viiiiii)(int, int, int, int, int, int);71typedef void (*em_func_viiiiiii)(int, int, int, int, int, int, int);72typedef void (*em_func_viiiiiiii)(int, int, int, int, int, int, int, int);73typedef void (*em_func_viiiiiiiii)(int, int, int, int, int, int, int, int, int);74typedef void (*em_func_viiiiiiiiii)(int, int, int, int, int, int, int, int, int, int);75typedef void (*em_func_viiiiiiiiiii)(int, int, int, int, int, int, int, int, int, int, int);76typedef int (*em_func_i)(void);77typedef int (*em_func_ii)(int);78typedef int (*em_func_iii)(int, int);79typedef int (*em_func_iiij)(int, int, uint64_t);80typedef int (*em_func_iiii)(int, int, int);81typedef int (*em_func_iiiii)(int, int, int, int);82typedef int (*em_func_iiiiii)(int, int, int, int, int);83typedef int (*em_func_iiiiiii)(int, int, int, int, int, int);84typedef int (*em_func_iiiiiiii)(int, int, int, int, int, int, int);85typedef int (*em_func_iiiiiiiii)(int, int, int, int, int, int, int, int);86typedef int (*em_func_iiiiiiiiii)(int, int, int, int, int, int, int, int, int);8788#ifdef __wasm64__89typedef int (*em_func_ij)(uint64_t);90typedef uint64_t (*em_func_ji)(int);91typedef int (*em_func_ijj)(uint64_t, uint64_t);92typedef int (*em_func_iij)(int, uint64_t);93typedef int (*em_func_iijj)(int, uint64_t, uint64_t);94typedef uint64_t (*em_func_jjj)(uint64_t, uint64_t);95typedef void (*em_func_vij)(int, uint64_t);96typedef void (*em_func_viji)(int, uint64_t, int);97typedef void (*em_func_vijji)(int, uint64_t, uint64_t, int);98typedef void (*em_func_viijj)(int, int, uint64_t, uint64_t);99typedef void (*em_func_viiij)(int, int, int, uint64_t);100typedef void (*em_func_viiiiij)(int, int, int, int, int, uint64_t);101typedef void (*em_func_viiiiiij)(int, int, int, int, int, int, uint64_t);102typedef void (*em_func_viiiiiiiij)(int, int, int, int, int, int, int, int, uint64_t);103#endif104105// Allocator and deallocator for em_queued_call objects.106static em_queued_call* em_queued_call_malloc() {107em_queued_call* call = (em_queued_call*)malloc(sizeof(em_queued_call));108assert(call); // Not a programming error, but use assert() in debug builds to catch OOM scenarios.109if (call) {110call->operationDone = 0;111call->functionPtr = 0;112call->satelliteData = 0;113}114return call;115}116117static void em_queued_call_free(em_queued_call* call) {118if (call)119free(call->satelliteData);120free(call);121}122123static void init_em_queued_call_args(em_queued_call* q,124EM_FUNC_SIGNATURE sig,125va_list args) {126EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK;127int numArguments = EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(sig);128for (int i = 0; i < numArguments; ++i) {129switch ((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) {130case EM_FUNC_SIG_PARAM_I:131q->args[i].i = va_arg(args, int);132break;133case EM_FUNC_SIG_PARAM_J:134q->args[i].j = va_arg(args, int64_t);135break;136case EM_FUNC_SIG_PARAM_F:137q->args[i].f = (float)va_arg(args, double);138break;139case EM_FUNC_SIG_PARAM_D:140q->args[i].d = va_arg(args, double);141break;142default:143assert(false && "unknown proxy param type");144}145argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT;146}147}148149static em_queued_call* em_queued_call_create(EM_FUNC_SIGNATURE sig,150void* func,151void* satellite,152va_list args) {153em_queued_call* call = em_queued_call_malloc();154if (call) {155call->functionEnum = sig;156call->functionPtr = func;157call->satelliteData = satellite;158init_em_queued_call_args(call, sig, args);159}160return call;161}162163void emscripten_async_waitable_close(em_queued_call* call) {164assert(call->operationDone);165em_queued_call_free(call);166}167168static void _do_call(void* arg) {169em_queued_call* q = (em_queued_call*)arg;170// C function pointer171assert(EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(q->functionEnum) <= EM_QUEUED_CALL_MAX_ARGS);172switch (q->functionEnum) {173case EM_FUNC_SIG_V:174((em_func_v)q->functionPtr)();175break;176case EM_FUNC_SIG_VI:177((em_func_vi)q->functionPtr)(q->args[0].i);178break;179case EM_FUNC_SIG_VF:180((em_func_vf)q->functionPtr)(q->args[0].f);181break;182case EM_FUNC_SIG_VII:183((em_func_vii)q->functionPtr)(q->args[0].i, q->args[1].i);184break;185case EM_FUNC_SIG_VIF:186((em_func_vif)q->functionPtr)(q->args[0].i, q->args[1].f);187break;188case EM_FUNC_SIG_VFF:189((em_func_vff)q->functionPtr)(q->args[0].f, q->args[1].f);190break;191case EM_FUNC_SIG_VIII:192((em_func_viii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i);193break;194case EM_FUNC_SIG_VIIJ:195((em_func_viij)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].j);196break;197case EM_FUNC_SIG_VIIF:198((em_func_viif)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].f);199break;200case EM_FUNC_SIG_VIFF:201((em_func_viff)q->functionPtr)(q->args[0].i, q->args[1].f, q->args[2].f);202break;203case EM_FUNC_SIG_VFFF:204((em_func_vfff)q->functionPtr)(q->args[0].f, q->args[1].f, q->args[2].f);205break;206case EM_FUNC_SIG_VIIII:207((em_func_viiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i);208break;209case EM_FUNC_SIG_VIIFI:210((em_func_viifi)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].f, q->args[3].i);211break;212case EM_FUNC_SIG_VIFFF:213((em_func_vifff)q->functionPtr)(q->args[0].i, q->args[1].f, q->args[2].f, q->args[3].f);214break;215case EM_FUNC_SIG_VFFFF:216((em_func_vffff)q->functionPtr)(q->args[0].f, q->args[1].f, q->args[2].f, q->args[3].f);217break;218case EM_FUNC_SIG_VIIIII:219((em_func_viiiii)q->functionPtr)(220q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i);221break;222case EM_FUNC_SIG_VIFFFF:223((em_func_viffff)q->functionPtr)(224q->args[0].i, q->args[1].f, q->args[2].f, q->args[3].f, q->args[4].f);225break;226case EM_FUNC_SIG_VIIIIII:227((em_func_viiiiii)q->functionPtr)(228q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i);229break;230case EM_FUNC_SIG_VIIIIIII:231((em_func_viiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i,232q->args[4].i, q->args[5].i, q->args[6].i);233break;234case EM_FUNC_SIG_VIIIIIIII:235((em_func_viiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i,236q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i);237break;238case EM_FUNC_SIG_VIIIIIIIII:239((em_func_viiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i,240q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i);241break;242case EM_FUNC_SIG_VIIIIIIIIII:243((em_func_viiiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i,244q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i,245q->args[9].i);246break;247case EM_FUNC_SIG_VIIIIIIIIIII:248((em_func_viiiiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i,249q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i,250q->args[9].i, q->args[10].i);251break;252#ifdef __wasm64__253case EM_FUNC_SIG_IP:254q->returnValue.i = ((em_func_ij)q->functionPtr)(q->args[0].j);255break;256case EM_FUNC_SIG_IIPP:257q->returnValue.i = ((em_func_iijj)q->functionPtr)(q->args[0].i, q->args[1].j, q->args[2].j);258break;259case EM_FUNC_SIG_PI:260q->returnValue.j = ((em_func_ji)q->functionPtr)(q->args[0].i);261break;262case EM_FUNC_SIG_IIP:263q->returnValue.i = ((em_func_iij)q->functionPtr)(q->args[0].i, q->args[1].j);264break;265case EM_FUNC_SIG_PPP:266q->returnValue.j = ((em_func_jjj)q->functionPtr)(q->args[0].j, q->args[1].j);267break;268case EM_FUNC_SIG_VIP:269((em_func_vij)q->functionPtr)(q->args[0].i, q->args[1].j);270break;271case EM_FUNC_SIG_VIIPP:272((em_func_viijj)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].j, q->args[3].j);273break;274case EM_FUNC_SIG_VIIIP:275((em_func_viiij)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].j);276break;277case EM_FUNC_SIG_VIPPI:278((em_func_vijji)q->functionPtr)(q->args[0].i, q->args[1].j, q->args[2].j, q->args[3].i);279break;280case EM_FUNC_SIG_VIPI:281((em_func_viji)q->functionPtr)(q->args[0].i, q->args[1].j, q->args[3].i);282break;283case EM_FUNC_SIG_VIIIIIP:284((em_func_viiiiij)q->functionPtr)(285q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].j);286break;287case EM_FUNC_SIG_VIIIIIIP:288((em_func_viiiiiij)q->functionPtr)(289q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].j);290break;291case EM_FUNC_SIG_VIIIIIIIIP:292((em_func_viiiiiiiij)q->functionPtr)(293q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].j);294break;295#endif296case EM_FUNC_SIG_I:297q->returnValue.i = ((em_func_i)q->functionPtr)();298break;299case EM_FUNC_SIG_II:300q->returnValue.i = ((em_func_ii)q->functionPtr)(q->args[0].i);301break;302case EM_FUNC_SIG_III:303q->returnValue.i = ((em_func_iii)q->functionPtr)(q->args[0].i, q->args[1].i);304break;305case EM_FUNC_SIG_IIII:306q->returnValue.i = ((em_func_iiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i);307break;308case EM_FUNC_SIG_IIIJ:309q->returnValue.i = ((em_func_iiij)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].j);310break;311case EM_FUNC_SIG_IIIII:312q->returnValue.i =313((em_func_iiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i);314break;315case EM_FUNC_SIG_IIIIII:316q->returnValue.i = ((em_func_iiiiii)q->functionPtr)(317q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i);318break;319case EM_FUNC_SIG_IIIIIII:320q->returnValue.i = ((em_func_iiiiiii)q->functionPtr)(321q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i);322break;323case EM_FUNC_SIG_IIIIIIII:324q->returnValue.i = ((em_func_iiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i,325q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i);326break;327case EM_FUNC_SIG_IIIIIIIII:328q->returnValue.i = ((em_func_iiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i,329q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i);330break;331case EM_FUNC_SIG_IIIIIIIIII:332q->returnValue.i =333((em_func_iiiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i,334q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i);335break;336default:337assert(0 && "Invalid Emscripten pthread _do_call opcode!");338}339340// If the caller is detached from this operation, it is the main thread's responsibility to free341// up the call object.342if (q->calleeDelete) {343em_queued_call_free(q);344// No need to wake a listener, nothing is listening to this since the call object is detached.345} else {346// The caller owns this call object, it is listening to it and will free it up.347q->operationDone = 1;348emscripten_futex_wake(&q->operationDone, INT_MAX);349}350}351352static pthread_t normalize_thread(pthread_t target_thread) {353assert(target_thread);354if (target_thread == EM_CALLBACK_THREAD_CONTEXT_MAIN_RUNTIME_THREAD) {355return emscripten_main_runtime_thread_id();356}357if (target_thread == EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD) {358return pthread_self();359}360return target_thread;361}362363// Execute `call` and return 1 only if already on the `target_thread`. Otherwise364// return 0.365static int maybe_call_on_current_thread(pthread_t target_thread,366em_queued_call* call) {367if (pthread_equal(target_thread, pthread_self())) {368_do_call(call);369return 1;370}371return 0;372}373374// Execute or proxy `call`. Return 1 if the work was executed or otherwise375// return 0.376static int do_dispatch_to_thread(pthread_t target_thread,377em_queued_call* call) {378target_thread = normalize_thread(target_thread);379if (maybe_call_on_current_thread(target_thread, call)) {380return 1;381}382emscripten_proxy_async(383emscripten_proxy_get_system_queue(), target_thread, _do_call, call);384return 0;385}386387int emscripten_dispatch_to_thread_args(pthread_t target_thread,388EM_FUNC_SIGNATURE sig,389void* func_ptr,390void* satellite,391va_list args) {392em_queued_call* q = em_queued_call_create(sig, func_ptr, satellite, args);393assert(q);394// TODO: handle errors in a better way, this pattern appears in several places395// in this file. The current behavior makes the calling thread hang as396// it waits (for synchronous calls).397// If we failed to allocate, return 0 which means we did not execute anything398// (we also never will in that case).399if (!q)400return 0;401402// `q` will not be used after it is called, so let the call clean it up.403q->calleeDelete = 1;404return do_dispatch_to_thread(target_thread, q);405}406407void emscripten_async_run_in_main_thread(em_queued_call* call) {408do_dispatch_to_thread(emscripten_main_runtime_thread_id(), call);409}410411int emscripten_dispatch_to_thread_(pthread_t target_thread,412EM_FUNC_SIGNATURE sig,413void* func_ptr,414void* satellite,415...) {416va_list args;417va_start(args, satellite);418int ret = emscripten_dispatch_to_thread_args(419target_thread, sig, func_ptr, satellite, args);420va_end(args);421return ret;422}423424int emscripten_dispatch_to_thread_async_args(pthread_t target_thread,425EM_FUNC_SIGNATURE sig,426void* func_ptr,427void* satellite,428va_list args) {429// Check if we are already on the target thread.430if (pthread_equal(target_thread, pthread_self())) {431// Setup is the same as in emscripten_dispatch_to_thread_args.432em_queued_call* q = em_queued_call_create(sig, func_ptr, satellite, args);433assert(q);434if (!q)435return 0;436q->calleeDelete = 1;437438// Schedule the call to run later on this thread.439emscripten_set_timeout(_do_call, 0, q);440return 0;441}442443// Otherwise, dispatch as usual.444return emscripten_dispatch_to_thread_args(445target_thread, sig, func_ptr, satellite, args);446}447448int emscripten_dispatch_to_thread_async_(pthread_t target_thread,449EM_FUNC_SIGNATURE sig,450void* func_ptr,451void* satellite,452...) {453va_list args;454va_start(args, satellite);455int ret = emscripten_dispatch_to_thread_async_args(456target_thread, sig, func_ptr, satellite, args);457va_end(args);458return ret;459}460461static void sync_run_in_main_thread(em_queued_call* call) {462emscripten_async_run_in_main_thread(call);463464// Enter to wait for the operation to complete.465emscripten_wait_for_call_v(call, INFINITY);466}467468int emscripten_sync_run_in_main_runtime_thread_(EM_FUNC_SIGNATURE sig, void* func_ptr, ...) {469em_queued_call q = {sig, func_ptr};470471va_list args;472va_start(args, func_ptr);473init_em_queued_call_args(&q, sig, args);474va_end(args);475sync_run_in_main_thread(&q);476return q.returnValue.i;477}478479#ifdef __wasm64__480void* emscripten_sync_run_in_main_runtime_thread_ptr_(EM_FUNC_SIGNATURE sig, void* func_ptr, ...) {481em_queued_call q = {sig, func_ptr};482483va_list args;484va_start(args, func_ptr);485init_em_queued_call_args(&q, sig, args);486va_end(args);487sync_run_in_main_thread(&q);488emscripten_outf("sync_run_in_main_ptr: %p\n", q.returnValue.vp);489return q.returnValue.vp;490}491#endif492493void emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIGNATURE sig, void* func_ptr, ...) {494em_queued_call* q = em_queued_call_malloc();495if (!q)496return;497q->functionEnum = sig;498q->functionPtr = func_ptr;499500va_list args;501va_start(args, func_ptr);502init_em_queued_call_args(q, sig, args);503va_end(args);504// 'async' runs are fire and forget, where the caller detaches itself from the call object after505// returning here, and it is the callee's responsibility to free up the memory after the call has506// been performed.507q->calleeDelete = 1;508emscripten_async_run_in_main_thread(q);509}510511em_queued_call* emscripten_async_waitable_run_in_main_runtime_thread_(512EM_FUNC_SIGNATURE sig, void* func_ptr, ...) {513em_queued_call* q = em_queued_call_malloc();514if (!q)515return NULL;516q->functionEnum = sig;517q->functionPtr = func_ptr;518519va_list args;520va_start(args, func_ptr);521init_em_queued_call_args(q, sig, args);522va_end(args);523// 'async waitable' runs are waited on by the caller, so the call object needs to remain alive for524// the caller to access it after the operation is done. The caller is responsible in cleaning up525// the object after done.526q->calleeDelete = 0;527emscripten_async_run_in_main_thread(q);528return q;529}530531EMSCRIPTEN_RESULT emscripten_wait_for_call_v(em_queued_call* call, double timeoutMSecs) {532int r;533534int done = atomic_load(&call->operationDone);535if (!done) {536double now = emscripten_get_now();537double waitEndTime = now + timeoutMSecs;538emscripten_set_current_thread_status(EM_THREAD_STATUS_WAITPROXY);539while (!done && now < waitEndTime) {540r = emscripten_futex_wait(&call->operationDone, 0, waitEndTime - now);541done = atomic_load(&call->operationDone);542now = emscripten_get_now();543}544emscripten_set_current_thread_status(EM_THREAD_STATUS_RUNNING);545}546if (done)547return EMSCRIPTEN_RESULT_SUCCESS;548else549return EMSCRIPTEN_RESULT_TIMED_OUT;550}551552EMSCRIPTEN_RESULT emscripten_wait_for_call_i(553em_queued_call* call, double timeoutMSecs, int* outResult) {554EMSCRIPTEN_RESULT res = emscripten_wait_for_call_v(call, timeoutMSecs);555if (res == EMSCRIPTEN_RESULT_SUCCESS && outResult)556*outResult = call->returnValue.i;557return res;558}559560561