diff --git a/CMakeLists.txt b/CMakeLists.txt index bdf07383..47320a1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ option(SOCKET_SUPPORT "Socket plugin support" ON) option(ZLIB_SUPPORT "Zlib plugin support" ${DEFAULT_ZLIB_SUPPORT}) option(DIGEST_SUPPORT "Digest plugin support" ${DEFAULT_DIGEST_SUPPORT}) option(DIGEST_SUPPORT_EXTENDED "Enable additional hash algorithms" ${DEFAULT_DIGEST_SUPPORT}) +option(ASYNC_SUPPORT "Async plugin support" ON) set(LIB_SEARCH_PATH "${CMAKE_INSTALL_PREFIX}/lib/ucode/*.so:${CMAKE_INSTALL_PREFIX}/share/ucode/*.uc:./*.so:./*.uc" CACHE STRING "Default library search path") string(REPLACE ":" "\", \"" LIB_SEARCH_DEFINE "${LIB_SEARCH_PATH}") @@ -310,6 +311,13 @@ if(DIGEST_SUPPORT) target_link_libraries(digest_lib ${libmd}) endif() +if(ASYNC_SUPPORT) + set(LIBRARIES ${LIBRARIES} async_lib) + add_library(async_lib MODULE lib/async/manager.c lib/async/alien.c lib/async/promise.c lib/async/timer.c lib/async/callback.c lib/async/queuer.c) + set_target_properties(async_lib PROPERTIES OUTPUT_NAME async PREFIX "") + target_link_options(async_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) +endif() + if(UNIT_TESTING) enable_testing() add_definitions(-DUNIT_TESTING) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 12d32ba2..85989431 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,5 +3,5 @@ FOREACH(example ${examples}) GET_FILENAME_COMPONENT(example ${example} NAME_WE) SET(CLI_SOURCES main.c) ADD_EXECUTABLE(${example} ${example}.c) - TARGET_LINK_LIBRARIES(${example} libucode ${json}) + TARGET_LINK_LIBRARIES(${example} libucode ${json} pthread) ENDFOREACH(example) diff --git a/examples/async-alien.c b/examples/async-alien.c new file mode 100644 index 00000000..a850b19a --- /dev/null +++ b/examples/async-alien.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/******* + * This example demonstrates how to do 'alien' calls from outside threads + * to the script. + */ + +#include +#include + +#include +#include +#include +#include + + +#define MULTILINE_STRING(...) #__VA_ARGS__ + +static const char *program_code = MULTILINE_STRING( + {% + import * as async from 'async'; + + function CalledFromAlien( name, counter ) + { + print( `${async.uptime()} '${name}' ${counter}\n` ); + return counter + 7; + } + + init_alien( 'alienA', CalledFromAlien ); + init_alien( 'alienB', CalledFromAlien ); + init_alien( 'alienC', CalledFromAlien ); + %} +); + +static uc_parse_config_t config = { + .strict_declarations = false, + .lstrip_blocks = true, + .trim_blocks = true +}; + +static uc_value_t * +init_alien(uc_vm_t *vm, size_t nargs); + +int main(int argc, char **argv) +{ + int exit_code = 0; + + /* create a source buffer containing the program code */ + uc_source_t *src = uc_source_new_buffer("my program", strdup(program_code), strlen(program_code)); + + /* initialize default module search path */ + uc_search_path_init(&config.module_search_path); + uc_search_path_add(&config.module_search_path,"../" ); + + /* compile source buffer into function */ + char *syntax_error = NULL; + uc_program_t *program = uc_compile(&config, src, &syntax_error); + + /* release source buffer */ + uc_source_put(src); + + /* check if compilation failed */ + if (!program) { + fprintf(stderr, "Failed to compile program: %s\n", syntax_error); + /* free search module path vector */ + uc_search_path_free(&config.module_search_path); + return 1; + } + + /* initialize VM context */ + uc_vm_t vm = { 0 }; + uc_vm_init(&vm, &config); + + /* load standard library into global VM scope */ + uc_stdlib_load(uc_vm_scope_get(&vm)); + + /* register our native function as "init_alien" */ + uc_function_register(uc_vm_scope_get(&vm), "init_alien", init_alien ); + + /* execute program function */ + int return_code = uc_vm_execute(&vm, program, NULL); + + /* release program */ + uc_program_put(program); + + /* handle return status */ + if (return_code == ERROR_COMPILE || return_code == ERROR_RUNTIME) { + printf("An error occurred while running the program\n"); + exit_code = 1; + } + + /* free VM context */ + uc_vm_free(&vm); + + /* free search module path vector */ + uc_search_path_free(&config.module_search_path); + + return exit_code; +} + +struct alien_data +{ + char *name; + uc_value_t *callback; + int counter; + int return_value; + const uc_async_alient_t *alien_manager; + pthread_t tid; +}; + +static int alien_call( uc_vm_t *vm, void *p, int flags ) +{ + struct alien_data *worker = p; + if( flags & UC_ASYNC_CALLBACK_FLAG_EXECUTE ) + { + uc_value_push(ucv_get( worker->callback)); + uc_value_push(ucv_string_new( worker->name ) ); + uc_value_push(ucv_int64_new( worker->counter ) ); + + int ex = uc_call(2); + + if( EXCEPTION_NONE == ex ) + { + uc_value_t *ret = uc_value_pop(); + worker->return_value = ucv_int64_get( ret ); + ucv_put( ret ); + return 0; + } + return INT_MIN; + } + return INT_MIN; +} + +// Function runs in worker thread +static void *worker_thread( void *p ) +{ + struct alien_data *worker = p; + printf( "In worker thread %llu\n", (long long unsigned)worker->tid ); + + // Call the callback function 1000 times + for( int i=0; i<1000; i++ ) + { + worker->counter = i; + uc_async_alien_call( worker->alien_manager, alien_call, worker ); + printf( "outside script %s: func(%d) -> %d\n", worker->name, worker->counter, worker->return_value ); + // and wait for a msec + struct timespec wait; + wait.tv_sec = 0; + wait.tv_nsec = 1000000; + nanosleep(&wait, 0); + } + + // Cleanup + free( worker->name ); + uc_async_alien_free( &worker->alien_manager ); + free( worker ); + + // Alas we'll leak some thread data, as we can't thread_join() from here. + return 0; +} + + +static uc_value_t * +init_alien(uc_vm_t *vm, size_t nargs) +{ + char *name = ucv_to_string( vm, uc_fn_arg( 0 ) ); + uc_value_t *callback = uc_fn_arg( 1 ); + + // try to create an alien manager + const uc_async_alient_t *alien_manager = uc_async_alien_new( vm ); + if( !alien_manager ) + { + uc_vm_raise_exception( vm, EXCEPTION_RUNTIME, "Cannot create alien manager" ); + return 0; + } + + // setup worker struct + struct alien_data *worker = xalloc( sizeof( struct alien_data )); + worker->name = name; + worker->callback = ucv_get( callback ); + worker->alien_manager = alien_manager; + + // start thread + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_create( &worker->tid,&attr,worker_thread,worker); + pthread_attr_destroy(&attr); + + return 0; +} diff --git a/examples/async-worker.c b/examples/async-worker.c new file mode 100644 index 00000000..6debf069 --- /dev/null +++ b/examples/async-worker.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/******* + * This example demonstrates how to offload work to another thread to fullfill + * a promise. + */ + +#include +#include + +#include +#include +#include +#include + + +#define MULTILINE_STRING(...) #__VA_ARGS__ + +static const char *program_code = MULTILINE_STRING( + {% + import * as async from 'async'; + + let promises = [ worker( 1000 ), worker( 2000 ), worker( 3000 ) ]; + + async.PromiseAll( promises ) + .then( (res)=> + { + print( 'result:', res, '\n' ); + }); + %} +); + +static uc_parse_config_t config = { + .strict_declarations = false, + .lstrip_blocks = true, + .trim_blocks = true +}; + +static uc_value_t * +worker(uc_vm_t *vm, size_t nargs); + +int main(int argc, char **argv) +{ + int exit_code = 0; + + /* create a source buffer containing the program code */ + uc_source_t *src = uc_source_new_buffer("my program", strdup(program_code), strlen(program_code)); + + /* initialize default module search path */ + uc_search_path_init(&config.module_search_path); + uc_search_path_add(&config.module_search_path,"../" ); + + /* compile source buffer into function */ + char *syntax_error = NULL; + uc_program_t *program = uc_compile(&config, src, &syntax_error); + + /* release source buffer */ + uc_source_put(src); + + /* check if compilation failed */ + if (!program) { + fprintf(stderr, "Failed to compile program: %s\n", syntax_error); + /* free search module path vector */ + uc_search_path_free(&config.module_search_path); + return 1; + } + + /* initialize VM context */ + uc_vm_t vm = { 0 }; + uc_vm_init(&vm, &config); + + /* load standard library into global VM scope */ + uc_stdlib_load(uc_vm_scope_get(&vm)); + + /* register our native function as "worker" */ + uc_function_register(uc_vm_scope_get(&vm), "worker", worker); + + /* execute program function */ + int return_code = uc_vm_execute(&vm, program, NULL); + + /* release program */ + uc_program_put(program); + + /* handle return status */ + if (return_code == ERROR_COMPILE || return_code == ERROR_RUNTIME) { + printf("An error occurred while running the program\n"); + exit_code = 1; + } + + /* free VM context */ + uc_vm_free(&vm); + + /* free search module path vector */ + uc_search_path_free(&config.module_search_path); + + return exit_code; +} + +struct worker_data +{ + int value; + uc_async_promise_resolver_t *resolver; + const uc_async_callback_queuer_t *queuer; + pthread_t tid; +}; + +// function runs in script thread +static int worker_finish( uc_vm_t *vm, void *user, int flags ) +{ + struct worker_data *worker = user; + if( flags & UC_ASYNC_CALLBACK_FLAG_EXECUTE ) + { + printf( "Inside the callback for thread %llu\n", (long long unsigned)worker->tid ); + + // resolve the promise + uc_async_promise_resolve( vm, &worker->resolver, ucv_int64_new( worker->value ) ); + } + if( flags & UC_ASYNC_CALLBACK_FLAG_CLEANUP ) + { + uc_async_callback_queuer_free( &worker->queuer ); + void *res; + pthread_join( worker->tid, &res ); + free( worker ); + } + return EXCEPTION_NONE; +} + +// Function runs in worker thread +static void *worker_thread( void *p ) +{ + struct worker_data *worker = p; + printf( "In worker thread %llu\n", (long long unsigned)worker->tid ); + + // do some work (but make sure you don't get tired ;) + struct timespec wait; + wait.tv_sec = worker->value / 1000; + wait.tv_nsec = (worker->value % 1000) * 1000000; + nanosleep(&wait, 0); + + // and request a callback in the script thread. + printf( "About to request a callback from thread %llu\n", (long long unsigned)worker->tid ); + uc_async_request_callback( worker->queuer, worker_finish, worker ); + return 0; +} + + +static uc_value_t * +worker(uc_vm_t *vm, size_t nargs) +{ + int value = ucv_to_integer( uc_fn_arg(0) ); + + // try to create a promise + uc_async_promise_resolver_t *resolver = 0; + uc_value_t *promise = uc_async_promise_new( vm, &resolver ); + if( 0 == promise ) + { + uc_vm_raise_exception( vm, EXCEPTION_RUNTIME, "need the 'async' plugin to be loaded" ); + return 0; + } + + // setup worker struct + struct worker_data *worker = xalloc( sizeof( struct worker_data )); + worker->value = value; + worker->resolver = resolver; + worker->queuer = uc_async_callback_queuer_new( vm ); + + // start thread + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_create( &worker->tid,&attr,worker_thread,worker); + pthread_attr_destroy(&attr); + + // and return promise + return promise; +} diff --git a/include/ucode/async.h b/include/ucode/async.h new file mode 100644 index 00000000..1231e1d9 --- /dev/null +++ b/include/ucode/async.h @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * # Async function API + * + * The 'async' module provides asynchronous functionality. + * + * The c API of this module does not cause or need any linktime + * dependencies. All is handled via a struct uc_async_manager + * pointer in the uc_vm_t object, which is initialized in the + * async.so uc_module_init() function, and which contains function pointers. + * + * This file contains static inline wrapper functions. + * + * @module async + */ + + +#ifndef UCODE_ASYNC_H +#define UCODE_ASYNC_H +#include "types.h" +#include + + +/* This is the 'public' interface of the async module */ +typedef struct uc_async_promise_resolver{} uc_async_promise_resolver_t; + +typedef struct uc_async_timer {} uc_async_timer_t; + +typedef struct uc_async_callback_queuer +{ + void (*free)( struct uc_async_callback_queuer const ** ); + + bool (*request_callback)( struct uc_async_callback_queuer const *, + int (*)( uc_vm_t *, void *, int flags ), void * ); + + uc_async_timer_t *(*create_timer)( struct uc_async_callback_queuer const *, + int (*)( uc_vm_t *, void *, int flags ), void *, uint32_t msec, bool periodic ); + + void (*free_timer)( struct uc_async_callback_queuer const *, + uc_async_timer_t **, bool clear ); +} uc_async_callback_queuer_t; + +typedef struct uc_async_alien +{ + void (*free)( struct uc_async_alien const ** ); + int (*call)( const struct uc_async_alien *, int (*)( uc_vm_t *, void *, int flags ), void * ); + + // Internally used, but you are free to queue your own callbacks with it. + const uc_async_callback_queuer_t *queuer; +} uc_async_alient_t; + +struct uc_async_manager +{ + // pump timers, promises and callbacks. + int (*event_pump)( struct uc_async_manager *, unsigned max_wait, int flags ); + + // promise functions + struct uc_value *(*new_promise)( struct uc_async_manager *, struct uc_async_promise_resolver ** ); + void (*resolve_reject)( struct uc_async_manager *, struct uc_async_promise_resolver **, struct uc_value *, bool ); + + // callback queuer functions + uc_async_callback_queuer_t const *(*new_callback_queuer)( struct uc_async_manager * ); + + // alien call function + uc_async_alient_t const *(*new_alien)( struct uc_async_manager * ); +}; + +/*** + * Function returns a pointer to an uc_async_manager if the async plugin is active. + * It is not active when it's not loaded, or when it has already finished. + * */ +static inline struct uc_async_manager *uc_async_manager_get( uc_vm_t *vm ) +{ + if( !vm ) return 0; + return (struct uc_async_manager *)ucv_resource_data(uc_vm_registry_get(vm, "async.manager"), NULL); +} + +/** + * Flags which are passed to a callback or timer function + * When 'UC_ASYNC_CALLBACK_FLAG_EXECUTE' is set, + * the function should do it's job. + * When 'UC_ASYNC_CALLBACK_FLAG_CLEANUP' is set, + * the function should cleanup resources. + * The function will not be called again. + * + * The flags can be set both in the same call. It is possible that + * 'UC_ASYNC_CALLBACK_FLAG_EXECUTE' will never be set, when the target + * vm already ended. In that case the function will only be called once, + * with the 'UC_ASYNC_CALLBACK_FLAG_CLEANUP' flag set. + */ +enum{ + UC_ASYNC_CALLBACK_FLAG_EXECUTE = 1, + UC_ASYNC_CALLBACK_FLAG_CLEANUP = 2, +}; + +/** + * Flags to be passed to vm->async_manager->pump_event(). + * Normally you'll not have to use them, the wrapper functions + * will do. + */ +enum{ + UC_ASYNC_PUMP_PUMP = 1, + UC_ASYNC_PUMP_CYCLIC = 2, + UC_ASYNC_PUMP_CLEANUP = 4, +}; + +/** + * Do one event pump cycle (handle all currently pending timers, callbacks and + * resolved promises) and then wait max msec. The wait will be shorter + * if either a timer is due, or a callback is requested. + * + * So when the function returns before msec have elapsed, a timer or + * callback is waiting. +*/ +static inline int uc_async_pump_once( uc_vm_t *vm, unsigned timeout ) +{ + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return STATUS_OK; + return (*manager->event_pump)( manager, timeout, UC_ASYNC_PUMP_PUMP ); +} + +/** + * Pump events during msec. +*/ +static inline int uc_async_pump_cyclic( uc_vm_t *vm, unsigned timeout ) +{ + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return STATUS_OK; + return (*manager->event_pump)( manager, timeout, UC_ASYNC_PUMP_PUMP|UC_ASYNC_PUMP_CYCLIC ); +} + +/** + * Create an (async) callback queuer. This queuer can queue callbacks to be + * executed in the script thread. + * + * This function has to be called from within the script thread. + * + * The callback queuer can queue callbacks from any thread. + * + * You have to call uc_async_callback_queuer_free() to cleanup resources. + * This can be done from any thread. +*/ +static inline uc_async_callback_queuer_t const *uc_async_callback_queuer_new( uc_vm_t *vm ) +{ + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return 0; + return (*manager->new_callback_queuer)( manager ); +} + +/** + * Queue a callback to be executed in the script thread. + * It can be called from any thread, and will wake up the event pump. + * If the function returns false, the callback is not queued because the + * target vm already stopped. + * If the function returns true, the callback will be called. + * When 'flags' has UC_ASYNC_CALLBACK_EXECUTE set, the callback should be executed, + * when UC_ASYNC_CALLBACK_CLEANUP is set, you should free up resources in *user. + * When only UC_ASYNC_CALLBACK_CLEANUP is set, + * it is possible the script is no longer running, and you shouldn't access vm. +*/ +static inline bool +uc_async_request_callback( + uc_async_callback_queuer_t const *queuer, + int (*cb)( uc_vm_t *vm, void *user, int flags ), + void *user ) +{ + if( !queuer ) + return false; + return (*queuer->request_callback)( queuer, cb, user ); +} + +/** + * The counterpart of setTimeout() and setInterval(). + * For the function flags see above. + * + * The return value must eventually be free'd with uc_async_free_timer. +*/ +static inline uc_async_timer_t * +uc_async_create_timer( + uc_async_callback_queuer_t const *queuer, + int (*cb)( uc_vm_t *vm, void *user, int flags ), + void *user, uint32_t msec, bool periodic ) +{ + if( !queuer ) + return 0; + return (*queuer->create_timer)( queuer, cb, user, msec, periodic ); +} + +/** + * Free the timer object. + * If 'clear == true' this is the counterpart of clearTimeout(). + * If clear != true, the local resources are freed, but the timer + * is still active (if not expired yet). + * You will not have a way to stop the timer. The script will run until the + * timer expires, or forever if it's a periodic timer. +*/ +static inline void +uc_async_free_timer( + uc_async_callback_queuer_t const *queuer, + uc_async_timer_t **pptimer, + bool clear ) +{ + if( !queuer ) + return; + (*queuer->free_timer)( queuer, pptimer, clear ); +} + +/** + * Cleanup the callback queuer. This function can be called from any thread. +*/ +static inline void +uc_async_callback_queuer_free( + uc_async_callback_queuer_t const **queuer ) +{ + if( !queuer || !*queuer ) + return; + (*(*queuer)->free)( queuer ); +} + +/** + * Create a new promise. The return value is the promise, + * and the reolver pointer will have a resolver handle. + * + * This function has to be called from within the script thread. +*/ +static inline uc_value_t * +uc_async_promise_new( uc_vm_t *vm, uc_async_promise_resolver_t **resolver ) +{ + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return 0; + return (*manager->new_promise)( manager, resolver ); +} + +/** + * Resolve the promise. The function will cleanup the resolver and + * reset the pointer. + * You are not allowed to use the resolver again. + * Normally you will call this function from a callback which is + * queued by a callback queuer. +*/ +static inline void +uc_async_promise_resolve( uc_vm_t *vm, uc_async_promise_resolver_t **resolver, uc_value_t *value ) +{ + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return; + if( !resolver || !*resolver ) + return; + (*manager->resolve_reject)( manager, resolver, value, true ); +} + +/** + * Resolve the promise. The function will cleanup the resolver and + * reset the pointer. + * You are not allowed to use the resolver again. + * Normally you will call this function from a callback which is + * queued by a callback queuer. +*/ +static inline void +uc_async_promise_reject( uc_vm_t *vm, uc_async_promise_resolver_t **resolver, uc_value_t *value ) +{ + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return; + if( !resolver || !*resolver ) + return; + (*manager->resolve_reject)( manager, resolver, value, false ); +} + +/**** + * Create an 'alien' object, that is an object which can call script-native + * function from an external thread + * This function has to be called from the script thread. + */ +static inline const uc_async_alient_t * +uc_async_alien_new( uc_vm_t *vm ) +{ + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return 0; + + return (*manager->new_alien)( manager ); +} + +/**** + * Free the alien object. As long as an alien object is + * around, the script will not finish + */ +static inline void +uc_async_alien_free( const uc_async_alient_t **alien ) +{ + if( !alien || !*alien ) + return; + (*(*alien)->free)( alien ); +} + +/**** + * Call a script function from another thread. This call will block if the + * script is not in 'event pumping' mode. + * The return value is the return value of func. + * When the vm is already exited, or alien is NULL, the return value is INT_MIN + */ +static inline int +uc_async_alien_call( const uc_async_alient_t *alien, int (*func)( uc_vm_t *, void *, int flags ), void *user ) +{ + if( !alien ) + return INT_MIN; + return (*alien->call)( alien, func, user ); +} + +/* 'private' interface. Designed to be called from vm.c */ + +/** + * Pump events during msec, or until event loop is empty, + * (UINT_MAX is forever), + * and then clean up the async module. + * + * This function is designed to run in uc_vm_execute(), + * after the internal uc_vm_execute_chunk(), + * to keep the event pump active until all promises are resolved + * and no more timers are active. + * + * The return value is the new status. +*/ +static inline int uc_async_finish( uc_vm_t *vm, int status, unsigned timeout ) +{ + if( STATUS_OK != status ) + return status; + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return status; + return (*manager->event_pump)( manager, timeout, UC_ASYNC_PUMP_PUMP|UC_ASYNC_PUMP_CYCLIC|UC_ASYNC_PUMP_CLEANUP ); +} + +#endif // UCODE_ASYNC_H \ No newline at end of file diff --git a/include/ucode/types.h b/include/ucode/types.h index f149b98b..74c1e2c4 100644 --- a/include/ucode/types.h +++ b/include/ucode/types.h @@ -192,6 +192,17 @@ typedef struct { char name[]; } uc_cfunction_t; +// feedback will be called on destroy of uc_cfunction_ex_t +typedef void (*uc_cfn_feedback_t)( uc_value_t * ); + +typedef struct { + uc_value_t header; + uc_cfn_ptr_t cfn; + intptr_t magic; + uc_cfn_feedback_t feedback; + char name[]; +} uc_cfunction_ex_t; + typedef struct { const char *name; uc_value_t *proto; @@ -275,7 +286,26 @@ typedef struct { uc_list_t object_iterators; } uc_thread_context_t; -__hidden uc_thread_context_t *uc_thread_context_get(void); +/* Do not call uc_thread_context_helper() directly, use one of the helper functions below */ +uc_thread_context_t *uc_thread_context_helper( int cmd, void * ); + +static inline uc_thread_context_t * +uc_thread_context_get(void) +{ + return uc_thread_context_helper( 0, 0 ); +} + +static inline uc_thread_context_t * +uc_thread_context_peek(void) +{ + return uc_thread_context_helper( 1, 0 ); +} + +static inline uc_thread_context_t * +uc_thread_context_exchange( uc_thread_context_t *tc ) +{ + return uc_thread_context_helper( 2, tc ); +} /* VM definitions */ @@ -423,6 +453,58 @@ size_t ucv_object_length(uc_value_t *); uc_value_t *ucv_cfunction_new(const char *, uc_cfn_ptr_t); +// Do not call ucv_cfunction_ex_helper() directly, use one of the helper functions below +bool +ucv_cfunction_ex_helper( int, void * ); + +static inline uc_value_t * +ucv_cfunction_ex_new( const char *name, uc_cfn_ptr_t fn, uc_cfn_feedback_t fb, size_t extra_user_bytes ) +{ + void *args[ 4 ] = { (void *)name, fn, fb, (void *)extra_user_bytes }; + if( ucv_cfunction_ex_helper( 0, args ) ) + return args[ 0 ]; + return 0; +} + +static inline intptr_t +ucv_cfunction_ex_get_magic() +{ + intptr_t ret; + if( ucv_cfunction_ex_helper( 1, &ret ) ) + return ret; + return -1; +} + +static inline bool +ucv_is_cfunction_ex( uc_value_t *uv ) +{ + if( NULL == uv ) + return false; + if( ucv_type(uv) != UC_CFUNCTION ) + return false; + const char *magic = 0; + if( !ucv_cfunction_ex_helper( 2, &magic ) || NULL == magic ) + return false; + return 0 == strcmp( ((uc_cfunction_t *)uv)->name, magic ); +} + +static inline const char * +uvc_cfunction_get_name( uc_cfunction_t *uv ) +{ + if( ucv_is_cfunction_ex( &uv->header ) ) + return( ((uc_cfunction_ex_t *)uv)->name ); + return uv->name; +} + +static inline void * +ucv_cfunction_ex_get_user( uc_value_t *uv ) +{ + if( !ucv_is_cfunction_ex( uv ) ) + return 0; + uc_cfunction_ex_t *fn = (uc_cfunction_ex_t *)uv; + return (fn->name + ALIGN( strlen(fn->name)+1) ); +} + uc_value_t *ucv_closure_new(uc_vm_t *, uc_function_t *, bool); uc_resource_type_t *ucv_resource_type_add(uc_vm_t *, const char *, uc_value_t *, void (*)(void *)); diff --git a/lib/async/alien.c b/lib/async/alien.c new file mode 100644 index 00000000..96a42e3e --- /dev/null +++ b/lib/async/alien.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ +#include +#include +#include +#include + +#include "ucode/lib.h" +#include "ucode/vm.h" +#include "ucode/types.h" +#include "ucode/async.h" + +#include "manager.h" +#include "callback.h" +#include "alien.h" +#include "queuer.h" + +#ifdef ASYNC_HAS_ALIENS +#include +#include +#include + +static int +async_futex( _Atomic uint32_t *uaddr, int futex_op, uint32_t val, + const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) +{ + return syscall(SYS_futex, uaddr, futex_op, val, + timeout, uaddr2, val3); +} + +static void +async_alien_enter_futex( _Atomic uint32_t *futex_addr ) +{ + while (1) + { + uint32_t zero = 0; + if( atomic_compare_exchange_strong( futex_addr, &zero, 1 ) ) + return; + + int futex_rc = async_futex(futex_addr, FUTEX_WAIT, 0, NULL, NULL, 0); + if (futex_rc == -1) + { + if (errno != EAGAIN) + { + perror("futex wait"); + exit(1); + } + } + } + +} + +void +async_alien_enter( async_manager_t *manager ) +{ + if( !manager->alien ) + return; + async_alien_enter_futex( &manager->alien->the_futex ); +} + +static void +async_alien_release_futex( _Atomic uint32_t *futex_addr ) +{ + const uint32_t zero = 0; + atomic_store( futex_addr, zero ); + if( -1 == async_futex(futex_addr, FUTEX_WAKE, 0, NULL, NULL, 0) ) + { + perror( "futex wake" ); + exit( 1 ); + } +} + +void +async_alien_release( async_manager_t *manager ) +{ + if( !manager->alien ) + return; + async_alien_release_futex( &manager->alien->the_futex ); +} + +static async_alien_t * +async_alien_cast( const struct uc_async_alien *_a ) +{ + return (async_alien_t *)_a; +} + + +static void +_uc_async_alien_free( struct uc_async_alien const ** palien ) +{ + if( 0 == palien || 0 == *palien ) + return; + async_alien_t *alien = async_alien_cast( *palien ); + *palien = 0; + + async_alien_enter_futex( &alien->the_futex ); + + if( 0 == --alien->refcount ) + { + if( alien->manager ) + { + async_wakeup( alien->header.queuer ); + } + else + { + uc_async_callback_queuer_free( &alien->header.queuer ); + free( alien ); + return; + } + } + + async_alien_release_futex( &alien->the_futex ); +} + +static int +_uc_async_alien_call( const struct uc_async_alien *_alien, int (*func)( uc_vm_t *, void *, int flags ), void *user ) +{ + if( !_alien ) + return INT_MIN; + + async_alien_t *alien = async_alien_cast( _alien ); + + async_alien_enter_futex( &alien->the_futex ); + if( !alien->manager ) + { + // vm already stopped + async_alien_release_futex( &alien->the_futex ); + return INT_MIN; + } + + int todo_seq_before = alien->todo_seq; + uc_thread_context_t *push = uc_thread_context_exchange( alien->thread_context ); + uc_vm_t *other_vm = alien->thread_context->signal_handler_vm; + alien->thread_context->signal_handler_vm = push ? push->signal_handler_vm : 0; + + int ret = (func)( alien->manager->vm, user, UC_ASYNC_CALLBACK_FLAG_EXECUTE|UC_ASYNC_CALLBACK_FLAG_CLEANUP ); + + uc_thread_context_exchange( push ); + alien->thread_context->signal_handler_vm = other_vm; + + if( todo_seq_before != alien->todo_seq ) + { + // Something is added to the todolist. + // Wakeup script thread to let it reconsider it's sleep duration + async_wakeup( alien->header.queuer ); + } + + async_alien_release_futex( &alien->the_futex ); + return ret; +} + +static const uc_async_alient_t * +_uc_async_new_alien( struct uc_async_manager *_manager ) +{ + async_manager_t *manager = async_manager_cast( _manager ); + async_alien_t *alien = manager->alien; + if( 0 == alien ) + { + alien = manager->alien = xalloc( sizeof( struct async_alien ) ); + alien->thread_context = uc_thread_context_get(); + alien->the_futex = 1; + alien->manager = async_manager_link( manager ); + alien->header.queuer = manager->header.new_callback_queuer( &manager->header ); + alien->header.free = _uc_async_alien_free; + alien->header.call = _uc_async_alien_call; + } + alien->refcount++; + + return &alien->header; +} + +void +async_alien_free( async_manager_t *manager, struct async_alien *alien ) +{ + if( !alien ) + return; + + // The futex is locked, + async_manager_unlink( alien->manager ); + alien->manager = 0; + + if( 0 == alien->refcount ) + { + uc_async_callback_queuer_free( &alien->header.queuer ); + free( alien ); + return; + } + + // release it, there are still aliens around which will try to enter it. + async_alien_release_futex( &alien->the_futex ); +} + +#else // defined ASYNC_HAS_ALIENS + +static const uc_async_alient_t * +_uc_async_new_alien( struct uc_async_manager *manager ) +{ + return 0; +} + +#endif // nded ASYNC_HAS_ALIENS + +void +async_alien_init( async_manager_t *manager, uc_value_t *scope ) +{ + manager->header.new_alien = _uc_async_new_alien; +} + diff --git a/lib/async/alien.h b/lib/async/alien.h new file mode 100644 index 00000000..74e351df --- /dev/null +++ b/lib/async/alien.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ +#ifndef UC_ASYNC_ALIEN_H +#define UC_ASYNC_ALIEN_H + +#include "manager.h" + +#ifdef ASYNC_HAS_ALIENS +extern __hidden void +async_alien_enter( async_manager_t *manager ); + +extern __hidden void +async_alien_release( async_manager_t *manager ); + +typedef struct async_alien +{ + uc_async_alient_t header; + + // Counter which is incremented at each todo added, + // to know if an alien call should interrupt the sleep + int todo_seq; + + _Atomic uint32_t the_futex; + uint32_t refcount; + + // zero if vm ended + async_manager_t *manager; + + uc_thread_context_t *thread_context; +} async_alien_t; + +extern __hidden void +async_alien_free( async_manager_t *, struct async_alien * ); + +# define ASYNC_ALIEN_ENTER(manager) async_alien_enter( manager ) +# define ASYNC_ALIEN_LEAVE(manager) async_alien_release( manager ) +# define ASYNC_ALIEN_TODO_INCREMENT(manager) if( manager->alien ) manager->alien->todo_seq++ +# define IF_NO_MORE_ALIENS(manager) \ + if( 0 == manager->alien || 0 == manager->alien->refcount ) // no more aliens + + +#else // ASYNC_HAS_ALIENS + +# define ASYNC_ALIEN_ENTER(...) do{}while(0) +# define ASYNC_ALIEN_LEAVE(...) do{}while(0) +# define ASYNC_ALIEN_TODO_INCREMENT(...) do{}while(0) +# define IF_NO_MORE_ALIENS(...) +#endif // + +extern __hidden void +async_alien_init( async_manager_t *manager, uc_value_t *scope ); + +#endif //ndef UC_ASYNC_ALIEN_H + diff --git a/lib/async/callback.c b/lib/async/callback.c new file mode 100644 index 00000000..a28888e8 --- /dev/null +++ b/lib/async/callback.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ +#include "ucode/lib.h" +#include "ucode/vm.h" +#include "ucode/types.h" +#include "ucode/async.h" + +#include "manager.h" +#include "callback.h" + + +void +async_callback_destroy( uc_vm_t *vm, struct async_callback *pcb) +{ + switch ((async_callback_type_t)pcb->callback_type) + { + case callbackNone: + break; + case callbackUcode: + if (pcb->ucode.func) + ucv_put(pcb->ucode.func); + pcb->ucode.func = 0; + if (pcb->ucode.this) + ucv_put(pcb->ucode.this); + pcb->ucode.this = 0; + break; + case callbackC_int_user_flags: + if (pcb->c_int_user_flags.func) + { + (*pcb->c_int_user_flags.func)( vm, pcb->c_int_user_flags.user, UC_ASYNC_CALLBACK_FLAG_CLEANUP); + pcb->c_int_user_flags.func = 0; + pcb->c_int_user_flags.user = 0; + } + break; + case callbackC_int_user_args_flags: + if (pcb->c_int_user_args_flags.func) + { + (*pcb->c_int_user_args_flags.func)( vm, pcb->c_int_user_args_flags.user, 0, 0, UC_ASYNC_CALLBACK_FLAG_CLEANUP); + pcb->c_int_user_args_flags.func = 0; + pcb->c_int_user_args_flags.user = 0; + } + break; + } + for (size_t n = 0; n < pcb->nargs; n++) + { + ucv_put(pcb->args[n]); + pcb->args[n] = 0; + } + pcb->nargs = 0; +} + +uc_value_t * +async_callback_get_ucode_func( async_manager_t *manager, struct async_callback *cb ) +{ + switch ((async_callback_type_t)cb->callback_type) + { + case callbackNone: + return 0; + case callbackUcode: + return ucv_get(cb->ucode.func); + case callbackC_int_user_flags: + return 0; + case callbackC_int_user_args_flags: + return 0; + } + return 0; +} + +int +async_callback_call( async_manager_t *manager, struct async_callback *cb, uc_value_t **args, size_t nargs, uc_value_t **ret, bool cleanup) +{ + int flags = UC_ASYNC_CALLBACK_FLAG_EXECUTE | (cleanup ? UC_ASYNC_CALLBACK_FLAG_CLEANUP : 0); + switch ((async_callback_type_t)cb->callback_type) + { + case callbackNone: + return EXCEPTION_NONE; + case callbackUcode: + { + uc_vm_t *vm = manager->vm; + uc_vm_stack_push(vm, ucv_get(cb->ucode.func)); + for (size_t n = 0; n < nargs; n++) + uc_vm_stack_push(vm, ucv_get(args[n])); + for (size_t n = 0; n < cb->nargs; n++) + uc_vm_stack_push(vm, ucv_get(cb->args[n])); + int ex = uc_vm_call(vm, false, cb->nargs + nargs); + if (cleanup) + { + ucv_put(cb->ucode.func); + cb->ucode.func = 0; + ucv_put(cb->ucode.this); + cb->ucode.this = 0; + for (size_t n = 0; n < cb->nargs; n++) + { + ucv_put(cb->args[n]); + cb->args[n] = 0; + } + cb->nargs = 0; + } + if (ex != EXCEPTION_NONE) + return ex; + uc_value_t *pret = uc_vm_stack_pop(vm); + if (ret) + *ret = pret; + else + ucv_put(pret); + return ex; + } + case callbackC_int_user_flags: + { + if (!cb->c_int_user_flags.func) + return EXCEPTION_NONE; + int ex = (*cb->c_int_user_flags.func)(manager->vm, cb->c_int_user_flags.user, flags); + if (cleanup) + { + cb->c_int_user_flags.func = 0; + cb->c_int_user_flags.user = 0; + } + return ex; + } + case callbackC_int_user_args_flags: + { + if (!cb->c_int_user_flags.func) + return EXCEPTION_NONE; + uc_value_t *args2[nargs + cb->nargs]; + size_t m = 0; + for (size_t n = 0; n < nargs; n++) + args2[m++] = args[n]; + for (size_t n = 0; n < cb->nargs; n++) + args2[m++] = cb->args[n]; + int ex = (*cb->c_int_user_args_flags.func)(manager->vm, cb->c_int_user_args_flags.user, args2, m, flags); + if (cleanup) + { + cb->c_int_user_args_flags.func = 0; + cb->c_int_user_args_flags.user = 0; + } + return ex; + } + } + return EXCEPTION_NONE; +} diff --git a/lib/async/callback.h b/lib/async/callback.h new file mode 100644 index 00000000..75b18ed5 --- /dev/null +++ b/lib/async/callback.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ + +#ifndef UC_ASYNC_CALLBACK_H +#define UC_ASYNC_CALLBACK_H + +typedef enum +{ + callbackNone = 0, + callbackUcode, + callbackC_int_user_flags, + callbackC_int_user_args_flags +} async_callback_type_t; + +struct async_callback +{ + uint32_t callback_type : 4; + uint32_t type : 4; + uint32_t nargs : 8; + uint32_t still_available_bits : 16; + + union + { + struct + { + uc_value_t *func; + uc_value_t *this; // not used, so far + } ucode; + struct + { + int (*func)(uc_vm_t *, void *user, int flags); + void *user; + } c_int_user_flags; + struct + { + int (*func)(uc_vm_t *, void *user, uc_value_t **args, size_t nargs, int flags); + void *user; + } c_int_user_args_flags; + }; + + uc_value_t *args[]; +}; + +extern __hidden uc_value_t * +async_callback_get_ucode_func( async_manager_t *manager, struct async_callback *cb ); + +extern __hidden void +async_callback_destroy( uc_vm_t *vm, struct async_callback *pcb); + +extern __hidden int +async_callback_call( async_manager_t *manager, struct async_callback *cb, uc_value_t **args, size_t nargs, uc_value_t **ret, bool cleanup); + +#endif //ndef UC_ASYNC_CALLBACK_H diff --git a/lib/async/manager.c b/lib/async/manager.c new file mode 100644 index 00000000..45a38e9b --- /dev/null +++ b/lib/async/manager.c @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ + +/** + * # Async functions + * + * The `async` module provides asynchronous functionality. + * + * Functions can be individually imported and directly accessed using the + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import} + * syntax: + * + * ``` + * import { Promise, setTimeout } from 'async'; + * + * Promise( (resolve,reject)=> + * { + * setTimeout( ()=> + * { + * resolve( 'done' ); + * }, 1000 ) + * }).then( ( a )=> + * { + * print( a ); + * }); + * ``` + * + * Alternatively, the module namespace can be imported + * using a wildcard import statement: + * + * ``` + * import * as async from 'async'; + * + * async.Promise( (resolve,reject)=> + * { + * async.setTimeout( ()=> + * { + * resolve( 'done' ); + * }, 1000 ) + * }).then( ( a )=> + * { + * print( a ); + * }); + * ``` + * + * @module async + */ + +#include +#include +#include +#include +#include +#include + +#include "ucode/module.h" +#include "ucode/platform.h" +#include "ucode/async.h" + +#include "manager.h" +#include "promise.h" +#include "callback.h" +#include "timer.h" +#include "queuer.h" +#include "alien.h" + +#ifdef ASYNC_HAS_UPTIME +double +async_manager_uptime( async_manager_t *manager ) +{ + if (!manager) + return NAN; + int64_t now = async_timer_current_time() - manager->start_time; + return (double)now / 1000.0; +} +#endif + +/* Start an interruptable sleep */ +/*void async_sleep( async_manager_t *manager, int64_t msec) +{ + if (msec < 1) + return; + + struct timespec wait; + wait.tv_sec = msec / 1000; + wait.tv_nsec = (msec % 1000) * 1000000; + nanosleep(&wait, 0); +}*/ + +int +async_todo_unlink( async_manager_t *manager, async_todo_t *todo) +{ + if (!todo) + return EXCEPTION_NONE; + + if (0 != --todo->refcount) + return EXCEPTION_NONE; + + DEBUG_ASSERT( 0 == todo->in_todo_list ); + + int ret = EXCEPTION_NONE; + switch ((enum async_todo_type)todo->todo_type) + { + case todoClearedTimer: + case todoTimer: + async_timer_destroy( manager, todo ); + break; + case todoPromise: + ret = async_promise_destroy( manager, todo); + break; + } + + free(todo); + return ret; +} + +// When is the todo object due? +static inline int64_t +async_todo_due(async_todo_t *todo) +{ + switch ((enum async_todo_type)todo->todo_type) + { + case todoClearedTimer: + case todoPromise: + return 0; + case todoTimer: + return async_timer_cast(todo)->due; + } + return 0; +} + +void +async_todo_put_in_list( async_manager_t *manager, async_todo_t *todo) +{ + DEBUG_ASSERT( 0 == todo->in_todo_list ); + todo->in_todo_list = 1; + + ASYNC_ALIEN_TODO_INCREMENT( manager ); + + int64_t due = async_todo_due(todo); + if( due ) + { + DEBUG_PRINTF( "%-1.3lf Todo %p scheduled at %-1.3lf\n", async_manager_uptime(manager), todo, + (due - manager->start_time) / 1000.0 ); + + } + async_todo_t *previous = 0; + for (async_todo_t *it = manager->todo_list; it; it = it->next) + { + if (async_todo_due(it) > due) + { + todo->next = it; + if (previous) + previous->next = todo; + else + manager->todo_list = todo; + todo->refcount++; + return; + } + previous = it; + } + todo->next = 0; + if (previous) + previous->next = todo; + else + manager->todo_list = todo; + todo->refcount++; +} + +static int +async_handle_todo( async_manager_t *manager ) +{ + if (!manager->todo_list) + return EXCEPTION_NONE; + int64_t now = async_timer_current_time(); + async_todo_t *to_be_handled = 0; + + while (manager->todo_list) + { + bool end_of_todo_for_now = false; + switch ((enum async_todo_type)manager->todo_list->todo_type) + { + case todoClearedTimer: + break; + case todoTimer: + end_of_todo_for_now = async_timer_cast(manager->todo_list)->due > now; + break; + case todoPromise: + break; + } + if (end_of_todo_for_now) + break; + + async_todo_t *pop = manager->todo_list; + manager->todo_list = pop->next; + pop->in_todo_list = 0; + pop->next = 0; + + if (todoClearedTimer == pop->todo_type) + { + async_todo_unlink(manager, pop); + } + else + { + pop->next = to_be_handled; + to_be_handled = pop; + } + } + + if (!to_be_handled) + return EXCEPTION_NONE; + + reverse_stack(async_todo_t, to_be_handled); + + while (to_be_handled) + { + async_todo_t *pop = to_be_handled; + to_be_handled = pop->next; + pop->next = 0; + int ex = EXCEPTION_NONE; + + switch ((enum async_todo_type)pop->todo_type) + { + case todoClearedTimer: + { + // The timer can be cleared in one of the previous to_be_handled functions + break; + } + case todoTimer: + { + ex = async_timer_do(manager,pop); + break; + } + case todoPromise: + { + ex = async_promise_do(manager, pop); + break; + } + } + + { + int ex2 = async_todo_unlink(manager, pop); + if (EXCEPTION_NONE == ex) + ex = ex2; + } + + if (EXCEPTION_NONE != ex) + { + // put back all remaining todo's + reverse_stack(async_todo_t, to_be_handled); + while (to_be_handled) + { + async_todo_t *pop = to_be_handled; + to_be_handled = pop->next; + pop->next = manager->todo_list; + manager->todo_list = pop; + pop->in_todo_list = 1; + } + return ex; + } + } + return EXCEPTION_NONE; +} + +static int64_t +async_how_long_to_next_todo( async_manager_t *manager ) +{ + while (manager->todo_list) + { + switch ((enum async_todo_type)manager->todo_list->todo_type) + { + case todoClearedTimer: + { + // remove from list + async_todo_t *pop = manager->todo_list; + manager->todo_list = pop->next; + pop->next = 0; + pop->in_todo_list = 0; + async_todo_unlink(manager, pop); + continue; + } + case todoPromise: + return 0; + case todoTimer: + { + int64_t now = async_timer_current_time(); + int64_t due = async_timer_cast(manager->todo_list)->due; + if (due > now) + return due - now; + return 0; + } + } + } + + // Nothing in todo list + return -1; +} + +static void +async_manager_cleanup(uc_vm_t *vm, async_manager_t *manager) +{ + while (manager->todo_list) + { + async_todo_t *pop = manager->todo_list; + manager->todo_list = pop->next; + pop->next = 0; + async_todo_unlink( manager, pop); + } + + { + struct async_callback_queuer *queuer = manager->callback_queuer; + manager->callback_queuer = 0; + async_callback_queuer_free( manager, queuer ); + } +#ifdef ASYNC_HAS_ALIENS + { + async_alien_t *alien = manager->alien; + manager->alien = 0; + async_alien_free( manager, alien ); + } +#endif +} + +static int +async_event_pump( struct uc_async_manager *_man, unsigned max_wait, int flags) +{ + async_manager_t *manager = async_manager_cast( _man ); + + if (flags & UC_ASYNC_PUMP_PUMP) + { + int64_t until = 0; + if (UINT_MAX == max_wait) + { + until = INT64_MAX; + } + else if (max_wait) + { + until = async_timer_current_time() + max_wait; + } + + do + { + DEBUG_PRINTF("%-1.3lf Pump!\n", async_manager_uptime( manager )); + int ex = async_handle_todo( manager ); + if (EXCEPTION_NONE != ex) + { + if (EXCEPTION_EXIT == ex) + { + manager->silent = -1; + return STATUS_EXIT; + } + return ERROR_RUNTIME; + } + + ex = async_handle_queued_callbacks( manager ); + if (EXCEPTION_NONE != ex) + { + if (EXCEPTION_EXIT == ex) + { + manager->silent = -1; + return STATUS_EXIT; + } + return ERROR_RUNTIME; + } + + int64_t tosleep = async_how_long_to_next_todo( manager ); + + if (-1 == tosleep) // no todo list + { + if( 0 == manager->pending_promises_cnt ) // no pending promises + { + + IF_NO_MORE_ALIENS(manager) + { + // Nothing to do anymore + DEBUG_PRINTF("%-1.3lf Last!\n", async_manager_uptime( manager )); + break; // do {} while( ) + } + } + tosleep = INT64_MAX; + } + + if (max_wait && !async_any_queued_callbacks_waiting( manager )) + { + ASYNC_ALIEN_LEAVE(manager); + + if ((unsigned)tosleep > max_wait) + tosleep = max_wait; + if (tosleep > 0) + { +#ifdef DEBUG_PRINT + DEBUG_PRINTF("%-1.3lf Start wait\n", async_manager_uptime( manager )); + /* The printf could have eaten a signal. + So look if something was added to the async stack */ + if( !async_any_queued_callbacks_waiting( manager ) ) +#endif + async_sleep( manager, tosleep); + DEBUG_PRINTF("%-1.3lf End wait\n", async_manager_uptime( manager )); + } + + ASYNC_ALIEN_ENTER(manager); + } + else + { + ASYNC_ALIEN_LEAVE(manager); + ASYNC_ALIEN_ENTER(manager); + } + + } while ((flags & UC_ASYNC_PUMP_CYCLIC) && + (until > async_timer_current_time())); + } // if( flags & UC_ASYNC_PUMP_PUMP ) + + if (flags & UC_ASYNC_PUMP_CLEANUP) + { + uc_vm_t *vm = manager->vm; + manager->vm = 0; + async_manager_cleanup( vm, manager ); + uc_vm_registry_delete( vm, "async.manager" ); + } + + return STATUS_OK; +} + + +/** + * Event pump, in which the asynchronous functions are actually executed. + * + * You can call this inside your program regularly, or at the end. + * When omitted the async functions will be called after the script + * has 'ended', by the vm. + * + * @function module:async#PumpEvents + * + * @param {Number} [timespan=null] + * Timespan in msec. The function will keep pumping events until *timespan* + * msec has elapsed. When no timespan is provided, PumpEvents() will keep + * pumping until no timers are left and no active promises are around, + * or an exception occurs. + * + * @param {Boolean} [single=false] + * Flag to only pump once, and then 'sleep' *timespan* msec, or + * return at the moment the next event is due to be executed, + * whatever comes first. + * This is usable if you want to do something between each stroke of the + * event pump: + * ``` + * let promise = async.Promise( (resolve,reject)=>{ resolve( 1 ) } ); + * for( let i=0; i<5; i++ ) + * { + * promise = promise.then( (num)=>{ print(num); return ++num } ); + * } + * + * while( async.PumpEvents( 1000, true ) ) + * { + * print( ` *${async.async_manager_uptime()}* ` ); + * } + * // will output something like '*0.002* 1 *0.003* 2 *0.003* 3 *0.004* 4 *0.005* 5 *0.005*' + * // and then exit. + * ``` + * But also + * ``` + * let timer; + * timer = async.setInterval( ( cnt )=> + * { + * if( ++cnt.cnt == 5 ) + * async.clearTimeout( timer ); + * print( cnt.cnt ); + * }, 100, { cnt: 0 } ); + * + * while( async.PumpEvents( 1000, true ) ) + * { + * print( ` *${async.async_manager_uptime()}* ` ); + * } + * // will output something like '*0.101* 1 *0.201* 2 *0.301* 3 *0.401* 4 *0.501* 5' + * // and then exit. + * ``` + * + * @return {Boolean} + * True if more events are (or will be) available, False if no more events are to be expected. + * + * @example + * async.setTimeout( ()=>{}, 10000 ); + * + * let count = 0; + * while( async.PumpEvents( 1000 ) ) + * { + * print( `${++count} ` ); + * } + * // Will output '1 2 3 4 5 6 7 9' and then exit. + * // Maybe '10' is also printed, depending on the exact timing. + */ + +static uc_value_t * +PumpEvents(uc_vm_t *vm, size_t nargs) +{ + unsigned msec = UINT_MAX; + int flags = UC_ASYNC_PUMP_CYCLIC | UC_ASYNC_PUMP_PUMP; + uc_value_t *pmsec = uc_fn_arg(0); + if (pmsec) + { + int64_t v = ucv_int64_get(pmsec); + if (v > 0) + { + if (v < UINT_MAX) + msec = (unsigned)v; + else + msec = UINT_MAX - 1; + } + } + uc_value_t *psingle = uc_fn_arg(1); + if (psingle) + { + bool v = ucv_boolean_get(psingle); + if (v) + flags &= ~UC_ASYNC_PUMP_CYCLIC; + } + + async_manager_t *manager = async_manager_get( vm ); + if( !manager ) + return ucv_boolean_new(false); + + async_event_pump( &manager->header, msec, flags); + + if( manager->pending_promises_cnt || + manager->todo_list) + return ucv_boolean_new(true); + + return ucv_boolean_new(false); +} + + +#ifdef ASYNC_HAS_UPTIME +/** + * Returns the uptime of the script (since importing the async plugin), in seconds, + * with a milli seconds resolution. + * (Actually a debug helper, but I decided to leave it) + * + * @function module:async#uptime + * + * @returns {Number} + * Uptime in seconds. + * + * @example + * let timer + * timer = async.setInterval( (a)=> + * { + * if( async.async_manager_uptime() > 5 ) + * async.clearTimeout( timer ); + * print( `${async.async_manager_uptime()} ` ); + * }, 1000 ); + * + * while( async.PumpEvents() ); + * // Will output something like '0.003 1.003 2.003 3.003 4.003 5.003' and then exit. + */ + +static uc_value_t * +Uptime(uc_vm_t *vm, size_t args) +{ + async_manager_t *manager = async_manager_get( vm ); + return ucv_double_new(async_manager_uptime(manager)); +} +#endif + +static const uc_function_list_t local_async_fns[] = { + {"PumpEvents", PumpEvents}, +#ifdef ASYNC_HAS_UPTIME + {"uptime", Uptime}, +#endif +}; + +static void close_manager( void *ud ) +{ + async_manager_t *manager = ud; + DEBUG_PRINTF( "%-1.3lf close_manager( vm=%p )\n", async_manager_uptime( manager ), manager->vm ); + + // Don't expect the vm to be useable anymore. + manager->vm = 0; + async_manager_cleanup( 0, manager ); + async_manager_unlink( manager ); +} + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + if( async_manager_get( vm ) ) + // Initializing twice? + return; + + async_manager_t *manager = xalloc(sizeof(async_manager_t)); + const uc_function_list_t manager_type_fns[0]; + uc_resource_type_t *managertype = uc_type_declare(vm, "async.manager", manager_type_fns, close_manager); + uc_value_t *uv_manager = ucv_resource_new(managertype, async_manager_link( manager ) ); + uc_vm_registry_set(vm, "async.manager", uv_manager); + manager->vm = vm; + + manager->header.event_pump = async_event_pump; + + async_promise_init( manager, scope ); + async_timer_init( manager, scope ); + async_callback_queuer_init( manager, scope ); + async_alien_init( manager, scope ); + + uc_function_list_register(scope, local_async_fns); + +#ifdef ASYNC_HAS_UPTIME + manager->start_time = async_timer_current_time(); +#endif + DEBUG_PRINTF( "%-1.3lf uc_module_init( vm=%p )\n", async_manager_uptime( manager ), vm ); +} diff --git a/lib/async/manager.h b/lib/async/manager.h new file mode 100644 index 00000000..ac0d12ee --- /dev/null +++ b/lib/async/manager.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ + +#ifndef UC_ASYNC_MANAGER_H +#define UC_ASYNC_MANAGER_H + +#include "ucode/platform.h" +#include "ucode/async.h" + +// #define DEBUG_PRINT +#define ASYNC_HAS_UPTIME +#define ASYNC_HAS_ALIENS + +#ifdef __APPLE__ +// For now. Aliens needs Linux futex, which isn't available on MacOS. +# undef ASYNC_HAS_ALIENS +#endif + +#ifdef DEBUG_PRINT +# define DEBUG_PRINTF(...) printf(__VA_ARGS__) +# define ASYNC_HAS_UPTIME +#else +# define DEBUG_PRINTF(...) +#endif + +#ifdef NDEBUG +# define DEBUG_ASSERT(...) +#else +# define DEBUG_ASSERT(...) assert(__VA_ARGS__) +#endif + +struct async_todo; +struct async_promise; +struct async_callback_queuer; +struct async_alien; + +struct async_manager +{ + struct uc_async_manager header; + + uc_vm_t *vm; + + // Linked list of pending todo's + struct async_todo *todo_list; + // Points to the active promise which excecuting a then, catch or finally handler + // to be able to catch the arguments of 'throw()'. + struct async_promise *active_promise; + + // Number of pending promises + int pending_promises_cnt:31; + int silent:1; // exit is called, no more output + + // Pointer to linked list of async callback's + struct async_callback_queuer *callback_queuer; + +#ifdef ASYNC_HAS_UPTIME + // For uptime + int64_t start_time; +#endif +#ifdef ASYNC_HAS_ALIENS + struct async_alien *alien; +#endif + + uc_resource_type_t *promise_type; + uc_resource_type_t *timer_type; + + uint32_t refcount; +}; + +typedef struct async_manager async_manager_t; + +#ifdef ASYNC_HAS_UPTIME +extern __hidden double +async_manager_uptime( async_manager_t * ); +#endif + +static inline async_manager_t * +async_manager_cast( struct uc_async_manager *m ) +{ + return (async_manager_t *)m; +} + +static inline async_manager_t * +async_manager_get( uc_vm_t *vm ) +{ + if( !vm ) return 0; + struct uc_async_manager *manager = uc_async_manager_get( vm ); + if( !manager ) + return 0; + return async_manager_cast( manager ); +} + +static inline async_manager_t * +async_manager_link( async_manager_t *manager ) +{ + manager->refcount++; + return manager; +} + +static inline bool +async_manager_unlink( async_manager_t *manager ) +{ + if( 0 == --manager->refcount ) + { + free( manager ); + return true; + } + return false; +} + +enum async_todo_type +{ + todoPromise = 1, + todoTimer = 2, + todoClearedTimer = todoTimer | 1, +}; + +typedef struct async_todo +{ + struct uc_async_timer header; // empty struct + + uint32_t todo_type : 2; + + /* refcount can be max 4. + For promises: 1 for the ucode promise object, + 2 for the associated resolve,reject objects, + and 1 for being in the todo list. + For timers: 1 for the ucode timer object + and 1 for being in the todo list. + So 3 bits is plenty */ + uint32_t refcount : 3; + /* One bit to know if this object is in the todo list */ + uint32_t in_todo_list : 1; + + /* which leaves 26 bits for general purpose: */ + uint32_t promise_pending : 1; // is added to 'global' vm->pending_promises_cnt + uint32_t promise_state : 2; // pending, resolved, rejected + uint32_t promise_result_is_exception : 1; + /* still 22 bits left */ + + struct async_todo *next; +} async_todo_t; + +extern __hidden void +async_todo_put_in_list( async_manager_t *manager, async_todo_t *todo); + +extern __hidden int +async_todo_unlink( async_manager_t *manager, async_todo_t *todo); + +#define reverse_stack(type, stack) \ + do \ + { \ + type *walk = stack, *reversed = 0; \ + while (walk) \ + { \ + type *pop = walk; \ + walk = pop->next; \ + pop->next = reversed; \ + reversed = pop; \ + } \ + stack = reversed; \ + } while (0) + + +#endif // ndef UC_ASYNC_MANAGER_H diff --git a/lib/async/promise.c b/lib/async/promise.c new file mode 100644 index 00000000..8741e85c --- /dev/null +++ b/lib/async/promise.c @@ -0,0 +1,1465 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ + +#include "ucode/lib.h" +#include "ucode/vm.h" +#include "ucode/types.h" + +#include "promise.h" +#include "callback.h" +#include "timer.h" + +enum +{ + promisePending = 0, + + promiseThen, + promiseCatch, + promiseFinally, +}; + +struct async_promise_method +{ + struct async_promise_method *next; + struct async_callback callback; +}; + +typedef struct async_promise_method async_promise_method_t; + +static void +uc_promise_func_free( async_manager_t *manager, async_promise_method_t *func) +{ + if (!func) + return; + async_callback_destroy( manager->vm, &func->callback); + free(func); +} + +static void +async_exception_clear( async_manager_t *manager, uc_exception_t *exception) +{ + exception->type = EXCEPTION_NONE; + + ucv_put(exception->stacktrace); + exception->stacktrace = NULL; + + free(exception->message); + exception->message = NULL; +} + +static void +async_exception_free( async_manager_t *manager, uc_exception_t *exception) +{ + if (!exception) + return; + async_exception_clear( manager, exception); + free(exception); +} + +static void +async_exception_move( async_manager_t *manager, uc_exception_t *to, uc_exception_t *from) +{ + if (from && to) + { + to->type = from->type; + from->type = EXCEPTION_NONE; + to->stacktrace = from->stacktrace; + from->stacktrace = 0; + to->message = from->message; + from->message = 0; + } +} + +static uc_exception_t * +async_exception_new( async_manager_t *manager, uc_exception_t *exception) +{ + uc_exception_t *ret = xalloc(sizeof(uc_exception_t)); + if (exception) + { + async_exception_move( manager, ret, exception); + } + return ret; +} + +/** + * Represents a promise object as returned by + * {@link module:async#Promise|Promise()} or {@link module:async#PromiseAll|PromiseAll()}. + * + * @class module:async.promise + * @hideconstructor + * + * @see {@link module:async#Promise|Promise()} + * + * @implements then(), catch() and finally() + * + * @example + * + * const promise = async.Promise(…); + * + * promise.then( ()=>{} ); + * promise.catch( ()=>{} ); + * promise.finally( ()=>{} ); + */ + +async_promise_t * +uc_promise_new( async_manager_t *manager ) +{ + async_promise_t *p = xalloc(sizeof(async_promise_t)); + p->header.refcount = 1; + p->header.todo_type = todoPromise; + p->manager = async_manager_link( manager ); + p->manager->pending_promises_cnt++; + p->header.promise_pending = 1; + + DEBUG_PRINTF("%-1.3lf new promise %p %u\n", async_manager_uptime(manager), p, manager->pending_promises_cnt); + return p; +} + +static inline async_promise_t * +async_promise_cast(async_todo_t *todo) +{ + DEBUG_ASSERT(todoPromise == todo->todo_type); + return (async_promise_t *)todo; +} + +static void +async_promise_clear_result( async_manager_t *manager, async_promise_t *promise ) +{ + if( !promise ) + return; + ucv_put( promise->reject_caused_by ); + promise->reject_caused_by = 0; + + if (promise->header.promise_result_is_exception) + { + async_exception_free( manager, promise->result.exception); + promise->header.promise_result_is_exception = 0; + promise->result.exception = 0; + } + else + { + ucv_put( promise->result.value ); + promise->result.value = 0; + } +} + +static uc_chunk_t * +uc_vm_frame_chunk(uc_callframe_t *frame) +{ + return frame->closure ? &frame->closure->function->chunk : NULL; +} + +static bool +async_vm_raise_exception_caused_by( uc_vm_t *vm, uc_value_t *caused_by, int type, const char *err, intptr_t arg ) +{ + uc_callframe_t *frame = 0; + bool ret = false; + if( caused_by && UC_CLOSURE == ucv_type(caused_by) ) + { + uc_vector_grow(&vm->callframes); + + frame = &vm->callframes.entries[vm->callframes.count++]; + frame->closure = (uc_closure_t *)caused_by; + frame->cfunction = NULL; + frame->stackframe = vm->stack.count; + frame->ip = uc_vm_frame_chunk(frame)->entries; /* that would point to the first instruction of the closure so the error message would point there as well */ + frame->ctx = NULL; + frame->mcall = false; + } + if( vm->callframes.count ) + { + // No exceptions without framestack. It will crash +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + uc_vm_raise_exception(vm, type, err, arg ); +#pragma GCC diagnostic pop + ret = true; + } + if( frame ) + { + /* "pop" artifical callframe */ + vm->callframes.count--; + } + return ret; +} + +int +async_promise_destroy( async_manager_t *manager, async_todo_t *todo) +{ + async_promise_t *promise = async_promise_cast(todo); + uc_vm_t *vm = manager ? manager->vm : 0; + async_manager_t *vm_is_active = vm ? manager : 0; + + if (vm_is_active && promise->header.promise_pending) + { + vm_is_active->pending_promises_cnt--; + promise->header.promise_pending = 0; + } + + DEBUG_PRINTF("%-1.3lf delete promise %p %d\n", async_manager_uptime(vm_is_active), promise, + vm_is_active ? vm_is_active->pending_promises_cnt : -1 ); + + int ret = EXCEPTION_NONE; + bool uncaught = promiseCatch == promise->header.promise_state; + if (uncaught) + { + if (vm_is_active && promise->header.promise_result_is_exception) + { + // put back the original exception + async_exception_clear( vm_is_active, &vm->exception); + async_exception_move( vm_is_active, &vm->exception, promise->result.exception); + async_exception_free( vm_is_active, promise->result.exception); + promise->result.exception = 0; + promise->header.promise_result_is_exception = 0; + ret = vm->exception.type; + uncaught = false; + } + } + + uc_value_t *caused_by = 0; + if( uncaught ) + { + caused_by = promise->reject_caused_by; + promise->reject_caused_by = 0; + } + async_promise_clear_result( vm_is_active, promise ); + + async_promise_method_t *stack = promise->stack; + for (; stack;) + { + async_promise_method_t *pop = stack; + stack = pop->next; + + if (vm_is_active && !(uncaught) && promiseFinally == pop->callback.type) + async_callback_call( vm_is_active, &pop->callback, 0, 0, 0, false); + async_callback_destroy( vm, &pop->callback); + free(pop); + } + + if (uncaught) + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + + static const char *err = "Rejected promise %p without catch handler\n"; + if (vm_is_active ) + { + if( !async_vm_raise_exception_caused_by( vm, caused_by, ret = EXCEPTION_RUNTIME, err, (intptr_t)promise ) ) + { + fprintf( stderr, err, promise); + } + } + else + { + if( !manager || !manager->silent ) + fprintf( stderr, err, promise); + } +#pragma GCC diagnostic pop + } + + ucv_put( caused_by ); + return ret; +} + +static void async_promise_method_pushback( async_manager_t *manager, async_promise_t *promise, async_promise_method_t *func) +{ + async_promise_method_t *previous = 0; + for (async_promise_method_t *it = promise->stack; it; it = it->next) + { + previous = it; + } + if (previous) + previous->next = func; + else + promise->stack = func; + + if (0 == promise->header.promise_pending) + { + promise->header.promise_pending = 1; + manager->pending_promises_cnt++; + } + + if ((0 == promise->header.in_todo_list) && + (promisePending != promise->header.promise_state)) + { + async_todo_put_in_list( manager, &promise->header); + } +} + +static void +async_promise_add_ucode_func( async_manager_t *manager, async_promise_t *promise, uc_value_t *func, int type) +{ + if (!ucv_is_callable(func)) + { + uc_vm_raise_exception(manager->vm, EXCEPTION_TYPE, "arg1 needs to be callable"); + return; + } + + async_promise_method_t *pf = xalloc(sizeof(async_promise_method_t)); + pf->callback.type = type; + pf->callback.callback_type = callbackUcode; + pf->callback.ucode.func = ucv_get(func); + pf->next = 0; + + async_promise_method_pushback(manager, promise, pf); +} + +static void +async_promise_add_c_int_user_args_flags_func( async_manager_t *manager, + async_promise_t *promise, + int (*func)(uc_vm_t *, void *, uc_value_t **, size_t, int), void *user, int type) +{ + async_promise_method_t *pf = xalloc(sizeof(async_promise_method_t)); + pf->callback.type = type; + pf->callback.callback_type = callbackC_int_user_args_flags; + pf->callback.c_int_user_args_flags.func = func; + pf->callback.c_int_user_args_flags.user = user; + pf->next = 0; + + async_promise_method_pushback( manager, promise, pf); +} + +struct async_promise_resolver +{ + uc_async_promise_resolver_t header; // empty struct + uint32_t refcount : 16; + uint32_t type : 16; + async_promise_t *promise; + uc_value_t *callback; // The callback provided with the creator: async.Promise( callback ) +}; + +typedef struct async_promise_resolver async_promise_resolver_t; + +static inline async_promise_resolver_t * +async_promise_resolver_cast( struct uc_async_promise_resolver *p ) +{ + return (async_promise_resolver_t *)p; +} + +static async_promise_resolver_t * +async_promise_resolver_new( async_promise_t *promise ) +{ + async_promise_resolver_t *resolver = xalloc(sizeof(async_promise_resolver_t)); + resolver->refcount = 1; + promise->header.refcount++; + resolver->promise = promise; + promise->resolver = &resolver->header; + return resolver; +} + +static const char *_strPromise = "async.promise"; + +static uc_value_t * +async_promise_then(uc_vm_t *vm, size_t nargs) +{ + async_promise_t **ppromise = uc_fn_this(_strPromise); + if (!ppromise || !*ppromise) + return 0; + async_promise_add_ucode_func( (*ppromise)->manager, *ppromise, uc_fn_arg(0), promiseThen); + return ucv_get(_uc_fn_this_res(vm)); +} + +static uc_value_t * +async_promise_catch(uc_vm_t *vm, size_t nargs) +{ + async_promise_t **ppromise = uc_fn_this(_strPromise); + if (!ppromise || !*ppromise) + return 0; + async_promise_add_ucode_func( (*ppromise)->manager, *ppromise, uc_fn_arg(0), promiseCatch); + return ucv_get(_uc_fn_this_res(vm)); +} + +static uc_value_t * +async_promise_finally(uc_vm_t *vm, size_t nargs) +{ + async_promise_t **ppromise = uc_fn_this(_strPromise); + if (!ppromise || !*ppromise) + return 0; + async_promise_add_ucode_func( (*ppromise)->manager, *ppromise, uc_fn_arg(0), promiseFinally); + return ucv_get(_uc_fn_this_res(vm)); +} + +static const uc_function_list_t promise_type_fns[] = { + {"then", async_promise_then}, + {"catch", async_promise_catch}, + {"finally", async_promise_finally}, +}; + +static void +close_promise(void *ud) +{ + async_promise_t *promise = ud; + if (promise) + { + DEBUG_PRINTF("%-1.3lf close promise %p %u\n", async_manager_uptime(promise->manager), promise, promise->header.refcount); + if( async_manager_unlink( promise->manager ) ) + promise->manager = 0; + async_todo_unlink(promise->manager, &promise->header); + } +} + +int +async_promise_do( async_manager_t *manager, async_todo_t *todo) +{ + async_promise_t *promise = async_promise_cast(todo); + + int state = promise->header.promise_state; + + async_promise_method_t *next_to_be_handled = 0; + // walk the stack searching for a handler + for (; promise->stack;) + { + next_to_be_handled = promise->stack; + promise->stack = next_to_be_handled->next; + next_to_be_handled->next = 0; + + if (state == next_to_be_handled->callback.type + || promiseFinally == next_to_be_handled->callback.type) + { + break; + } + + uc_promise_func_free( manager, next_to_be_handled); + next_to_be_handled = 0; + } + + if( !next_to_be_handled ) + { + // mark as 'not pending' + if (promise->header.promise_pending) + { + promise->header.promise_pending = 0; + if( manager ) manager->pending_promises_cnt--; + } + return EXCEPTION_NONE; + } + + uc_value_t *out = 0; + int ex = EXCEPTION_NONE; + { + uc_value_t *in = 0; + if (promiseFinally != next_to_be_handled->callback.type) + { + if (promise->header.promise_result_is_exception) + { + in = ucv_string_new(promise->result.exception->message); + } + else + { + in = ucv_get( promise->result.value ); + } + + async_promise_clear_result( manager, promise ); + } + + // reset the state, so we know when throw() is called + promise->header.promise_state = promisePending; + + { + async_promise_t *push = manager->active_promise; + manager->active_promise = promise; + ex = async_callback_call( manager, &next_to_be_handled->callback, &in, 1, &out, true); + manager->active_promise = push; + } + + ucv_put(in); + } + + uc_value_t *caused_by = async_callback_get_ucode_func( manager, &next_to_be_handled->callback ); + int ittype = next_to_be_handled->callback.type; + uc_promise_func_free( manager, next_to_be_handled); + + if (EXCEPTION_NONE != ex) + { + if (EXCEPTION_EXIT == ex) + { + ucv_put( caused_by ); + return ex; + } + ucv_put(out); + ucv_put(promise->reject_caused_by); + promise->reject_caused_by = caused_by; + if( promiseCatch == promise->header.promise_state ) + { + // Caused by a throw() + async_exception_clear( manager, &manager->vm->exception ); + } + else + { + // Take over the exception + promise->result.exception = async_exception_new( manager, &manager->vm->exception); + promise->header.promise_result_is_exception = 1; + promise->header.promise_state = promiseCatch; + } + // reschedule + async_todo_put_in_list( manager, &promise->header ); + return EXCEPTION_NONE; + } + + ucv_put(caused_by); + caused_by = 0; + + if (promiseFinally == ittype) + { + // Return value of finally is ignored, if it's not an exception + ucv_put(out); + + // put state back + promise->header.promise_state = state; + // reschedule + async_todo_put_in_list( manager, &promise->header ); + return EXCEPTION_NONE; + } + + { // Is the result a promise? + async_promise_t **ppromise = (async_promise_t **)ucv_resource_dataptr(out, _strPromise); + if (ppromise && *ppromise) + { + async_promise_t *new_promise = *ppromise; + // We must push it's handler stack in front of ours, + // and adopt it's state and it's resolver + async_promise_method_t *previous = 0; + for (async_promise_method_t *it2 = new_promise->stack; it2; it2 = it2->next) + { + previous = it2; + } + if (previous) + previous->next = promise->stack; + else + new_promise->stack = promise->stack; + promise->stack = new_promise->stack; + new_promise->stack = 0; + + if (promise->resolver) + { + // Shouldn't be possible, but handle anyway + async_promise_resolver_cast( promise->resolver )->promise = 0; + promise->resolver = 0; + promise->header.refcount--; + } + + if (new_promise->resolver) + { + async_promise_resolver_cast( new_promise->resolver )->promise = promise; + promise->resolver = new_promise->resolver; + new_promise->resolver = 0; + new_promise->header.refcount--; + promise->header.refcount++; + } + + promise->result.value = new_promise->result.value; + new_promise->result.value = 0; + promise->header.promise_result_is_exception = new_promise->header.promise_result_is_exception; + new_promise->header.promise_result_is_exception = 0; + promise->header.promise_state = new_promise->header.promise_state; + + // destroys also new_promise + ucv_put(out); + + if( promisePending == promise->header.promise_state ) + { + // not reschedule. We must wait for the resolver to act + } + else + { + // reschedule + async_todo_put_in_list( manager, &promise->header ); + } + + return EXCEPTION_NONE; + } + } + + promise->header.promise_state = promiseThen; + promise->result.value = out; + promise->header.promise_result_is_exception = 0; + // reschedule + async_todo_put_in_list( manager, &promise->header ); + return EXCEPTION_NONE; +} + +static void +async_resolve_or_reject( async_manager_t *manager, async_promise_resolver_t *resolver, uc_value_t *res, int type) +{ + if (!resolver || !resolver->promise) + return; + + async_promise_t *promise = resolver->promise; + resolver->promise = 0; + promise->resolver = 0; + + if (promisePending != promise->header.promise_state) + { + async_todo_unlink( manager, &promise->header); + return; + } + + if (promiseThen == type) + promise->header.promise_state = promiseThen; + else if (promiseCatch == type) + promise->header.promise_state = promiseCatch; + + promise->result.value = ucv_get(res); + promise->header.promise_result_is_exception = 0; + + if( !promise->header.promise_pending ) + { + promise->header.promise_pending = 1; + manager->pending_promises_cnt++; + } + + if (!promise->header.in_todo_list) + { + async_todo_put_in_list( manager, &promise->header); + } + + async_todo_unlink( manager, &promise->header); +} + +static int +async_promise_resolver_unlink( async_manager_t *manager, async_promise_resolver_t *resolver ) +{ + if( 0 == resolver || 0 != --resolver->refcount ) + return EXCEPTION_NONE; + + if (resolver->promise) + { + async_promise_t *promise = resolver->promise; + resolver->promise = 0; + promise->resolver = 0; + + DEBUG_PRINTF("%-1.3lf promise abandoned %p\n", async_manager_uptime(promise->manager), promise); + promise->result.value = ucv_string_new("Promise abandoned"); + promise->header.promise_result_is_exception = 0; + promise->header.promise_state = promiseCatch; + promise->reject_caused_by = resolver->callback; + resolver->callback = 0; + if (promise->manager) + async_todo_put_in_list(promise->manager, &promise->header); + async_todo_unlink( promise->manager, &promise->header); + } + + if( resolver->callback ) + ucv_put( resolver->callback ); + free(resolver); + return EXCEPTION_NONE; +} + +static uc_value_t * +async_resolver_resolve_or_reject( uc_vm_t *vm, size_t nargs, int type ) +{ + uc_callframe_t *lastframe = uc_vector_last(&vm->callframes); + if( !lastframe ) + return 0; + uc_cfunction_t *callee = lastframe->cfunction; + async_promise_resolver_t **presolver = (async_promise_resolver_t**)ucv_cfunction_ex_get_user( (uc_value_t *)callee ); + if( presolver ) + { + async_promise_resolver_t *resolver = *presolver; + if( resolver ) + { + async_manager_t *manager = async_manager_get( vm ); + async_resolve_or_reject( manager, resolver, uc_fn_arg(0), type); + } + } + return 0; +} +static uc_value_t * +async_resolver_resolve(uc_vm_t *vm, size_t nargs) +{ + return async_resolver_resolve_or_reject( vm, nargs, promiseThen ); +} + +static uc_value_t * +async_resolver_reject(uc_vm_t *vm, size_t nargs) +{ + return async_resolver_resolve_or_reject( vm, nargs, promiseCatch ); +} + +static void +async_resolver_destroy( uc_value_t *uv ) +{ + async_promise_resolver_t **presolver = (async_promise_resolver_t**)ucv_cfunction_ex_get_user( uv ); + if( presolver ) + { + async_promise_resolver_t *resolver = *presolver; + *presolver = 0; + if( resolver ) + { + async_promise_resolver_unlink( 0, resolver ); + } + } +} + +static int +uc_resolver_immediate(uc_vm_t *vm, void *user, int flags) +{ + async_promise_resolver_t *resolver = user; + async_manager_t *manager = async_manager_get( vm ); + int ex = EXCEPTION_NONE; + if (flags & UC_ASYNC_CALLBACK_FLAG_EXECUTE) + { + uc_vm_stack_push(vm, ucv_get(resolver->callback)); + uc_value_t *resolve = ucv_cfunction_ex_new( "resolve", async_resolver_resolve, async_resolver_destroy, sizeof(resolver) ); + uc_value_t *reject = ucv_cfunction_ex_new( "reject", async_resolver_reject, async_resolver_destroy, sizeof(resolver) ); + + async_promise_resolver_t **presolver = (async_promise_resolver_t**)ucv_cfunction_ex_get_user( resolve ); + resolver->refcount++; + *presolver = resolver; + + presolver = (async_promise_resolver_t**)ucv_cfunction_ex_get_user( reject ); + resolver->refcount++; + *presolver = resolver; + + uc_vm_stack_push(vm, resolve ); + uc_vm_stack_push(vm, reject ); + + ex = uc_vm_call(vm, false, 2); + + if( EXCEPTION_NONE == ex ) + ucv_put(uc_vm_stack_pop(vm)); + } + if (flags & UC_ASYNC_CALLBACK_FLAG_CLEANUP) + { + int ex2 = async_promise_resolver_unlink( manager, resolver ); + if( EXCEPTION_NONE == ex ) + ex = ex2; + } + return ex; +} + +struct async_promise_array_result +{ + struct async_promise_array *promise_array; + uc_value_t *value; + int state; // only in use by ASYNC_PROMISE_ALLSETTLED +}; + +typedef enum { + ASYNC_PROMISE_ALL = 1, + ASYNC_PROMISE_ANY, + ASYNC_PROMISE_RACE, + ASYNC_PROMISE_ALLSETTLED, +} promise_array_type_t; + +struct async_promise_array +{ + uint32_t refcount:9; + uint32_t exec_refcount:9; + uint32_t numresults: 8; + uint32_t type : 4; // all, any, race, allsettled + async_promise_resolver_t *resolver; + + struct async_promise_array_result results[]; +}; + +static void async_promise_array_unlink(struct async_promise_array *promise_array) +{ + if (0 != --promise_array->refcount) + return; + for( uint32_t n=0; nnumresults; n++ ) + ucv_put( promise_array->results[ n ].value ); + free(promise_array); +} + +static int async_promise_array_resolve( async_manager_t *manager, struct async_promise_array *promise_array, uc_value_t **args, size_t nargs, int type) +{ + if( !promise_array->resolver ) + return EXCEPTION_NONE; + + if( promisePending == type ) + { + DEBUG_PRINTF("%-1.3lf %p will be pending forever\n", async_manager_uptime(manager), promise_array->resolver); + // to prevent it from keeping the script running forever, we'll remove it's 'promise pending' status + async_promise_t *promise = promise_array->resolver->promise; + if( promise ) + { + if( promise->header.promise_pending ) + { + manager->pending_promises_cnt--; + promise->header.promise_pending = 0; + } + // and cleanup + promise->resolver = 0; + promise_array->resolver->promise = 0; + async_todo_unlink( manager, &promise->header ); + } + } + else + { + DEBUG_PRINTF("%-1.3lf %p resolved\n", async_manager_uptime(manager), promise_array->resolver); + uc_value_t *value = 0; + int value_type = 0; + + switch( (promise_array_type_t)promise_array->type ) + { + case ASYNC_PROMISE_ALL: + if( promiseCatch == type ) + value_type = 1; + else if( promiseThen == type ) + value_type = 2; + break; + case ASYNC_PROMISE_ANY: + if( promiseCatch == type ) + value_type = 2; + else if( promiseThen == type ) + value_type = 1; + break; + case ASYNC_PROMISE_RACE: + value_type = 1; + break; + case ASYNC_PROMISE_ALLSETTLED: + value_type = 3; + + } + switch( value_type ) + { + case 1: // the provided argument in the current call + { + if( nargs > 0) + value = args[0]; + break; + } + case 2: // the array of stored values + { + value = ucv_array_new_length( manager->vm, promise_array->numresults ); + for( uint32_t n=0; nnumresults; n++ ) + { + uc_value_t *elem = promise_array->results[ n ].value; + promise_array->results[ n ].value = 0; + if( elem ) + ucv_array_set( value, n, elem ); + } + break; + } + case 3: // the array of stored values, as struct (for 'allsettled) + { + value = ucv_array_new_length( manager->vm, promise_array->numresults ); + uc_value_t *fullfilled = 0, *rejected = 0; + for( uint32_t n=0; nnumresults; n++ ) + { + struct async_promise_array_result *result = + &promise_array->results[ n ]; + uc_value_t *obj = ucv_object_new( manager->vm ); + ucv_get( obj ); + if( result->state == promiseCatch ) + { + if( 0 == rejected ) rejected = ucv_string_new( "rejected" ); + ucv_object_add( obj, "status", ucv_get( rejected ) ); + ucv_object_add( obj, "reason", result->value ); + result->value = 0; + } + if( result->state == promiseThen ) + { + if( 0 == fullfilled ) fullfilled = ucv_string_new( "fullfilled" ); + ucv_object_add( obj, "status", ucv_get( fullfilled ) ); + ucv_object_add( obj, "value", result->value ); + result->value = 0; + } + ucv_array_set( value, n, obj ); + } + ucv_put( fullfilled ); + ucv_put( rejected ); + break; + } + } + + async_resolve_or_reject(manager, promise_array->resolver, value, type); + } + async_promise_resolver_unlink( manager, promise_array->resolver); + promise_array->resolver = 0; + return EXCEPTION_NONE; +} + +static int async_promise_array_immediate(uc_vm_t *vm, void *user, int flags) +{ + /* When we come in this function, the array of promises didn't contain + any usable value. So what to do? */ + struct async_promise_array *promise_array = user; + int ex = EXCEPTION_NONE; + if (flags & UC_ASYNC_CALLBACK_FLAG_EXECUTE) + { + int type = 0; + switch( (promise_array_type_t)promise_array->type ) + { + case ASYNC_PROMISE_ALL: + type = promiseThen; + break; + case ASYNC_PROMISE_ANY: + type = promiseCatch; + break; + case ASYNC_PROMISE_RACE: + /* According to the spec the promise should stay pending forever */ + type = promisePending; + break; + case ASYNC_PROMISE_ALLSETTLED: + type = promiseThen; + break; + } + ex = async_promise_array_resolve( async_manager_get( vm ), promise_array, 0, 0, type); + } + if (flags & UC_ASYNC_CALLBACK_FLAG_CLEANUP) + async_promise_array_unlink(promise_array); + return ex; +} + +static int async_promise_array_then(uc_vm_t *vm, void *user, uc_value_t **args, size_t nargs, int flags) +{ + struct async_promise_array_result *result = user; + struct async_promise_array *promise_array = result->promise_array; + async_manager_t *manager = async_manager_get( vm ); + + int ex = EXCEPTION_NONE; + if (flags & UC_ASYNC_CALLBACK_FLAG_EXECUTE) + { + DEBUG_PRINTF("%-1.3lf promise_array_then()\n", async_manager_uptime(manager)); + result->state = promiseThen; + int cnt = --promise_array->exec_refcount; + switch( (promise_array_type_t)promise_array->type ) + { + case ASYNC_PROMISE_ALL: + if( nargs ) result->value = ucv_get( args[ 0 ] ); + break; + case ASYNC_PROMISE_ANY: + cnt = 0; + break; + case ASYNC_PROMISE_RACE: + cnt = 0; + break; + case ASYNC_PROMISE_ALLSETTLED: + if( nargs ) result->value = ucv_get( args[ 0 ] ); + break; + } + if( 0 == cnt ) + ex = async_promise_array_resolve( manager, promise_array, args, nargs, promiseThen); + } + if (flags & UC_ASYNC_CALLBACK_FLAG_CLEANUP) + { + async_promise_array_unlink(promise_array); + } + return ex; +} + +static int async_promise_array_catch(uc_vm_t *vm, void *user, uc_value_t **args, size_t nargs, int flags) +{ + struct async_promise_array_result *result = user; + struct async_promise_array *promise_array = result->promise_array; + async_manager_t *manager = async_manager_get( vm ); + int ex = EXCEPTION_NONE; + if (flags & UC_ASYNC_CALLBACK_FLAG_EXECUTE) + { + DEBUG_PRINTF("%-1.3lf promise_array_catch()\n", async_manager_uptime(manager)); + result->state = promiseCatch; + int cnt = --promise_array->exec_refcount; + switch( (promise_array_type_t)promise_array->type ) + { + case ASYNC_PROMISE_ALL: + cnt = 0; + break; + case ASYNC_PROMISE_ANY: + if( nargs ) result->value = ucv_get( args[ 0 ] ); + break; + case ASYNC_PROMISE_RACE: + cnt = 0; + break; + case ASYNC_PROMISE_ALLSETTLED: + if( nargs ) result->value = ucv_get( args[ 0 ] ); + break; + } + if( 0 == cnt ) + ex = async_promise_array_resolve(manager, promise_array, args, nargs, promiseCatch); + } + if (flags & UC_ASYNC_CALLBACK_FLAG_CLEANUP) + { + async_promise_array_unlink(promise_array); + } + return ex; +} + +static uc_value_t * +async_promise_array_new( uc_vm_t *vm, size_t nargs, int type ) +{ + uc_value_t *arr = uc_fn_arg(0); + if (arr && arr->type != UC_ARRAY) + { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "arg1 needs to be an array"); + return 0; + } + + size_t length = ucv_array_length(arr); + if( length > 255 ) + { + // promise_array->numresults has only 8 bits + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "arg1p[] may not exceed 255 elements"); + return 0; + } + + async_manager_t *manager = async_manager_get( vm ); + if( !manager ) + return 0; + + struct async_promise_array *promise_array = + xalloc(sizeof(struct async_promise_array) + length * sizeof(struct async_promise_array_result)); + + promise_array->type = type; + async_promise_t *promise = uc_promise_new( manager ); + promise_array->resolver = async_promise_resolver_new(promise); + + for (size_t n = 0; n < length; n++) + { + uc_value_t *elem = ucv_array_get(arr, n); + async_promise_t **ppromise = (async_promise_t **)ucv_resource_dataptr(elem, _strPromise); + if (ppromise && *ppromise) + { + if( ASYNC_PROMISE_RACE == type && + ((*ppromise)->header.promise_state != promisePending) && + (promisePending == promise->header.promise_state) ) + { + // this one should fullfill the promise + if( (*ppromise)->header.promise_result_is_exception ) + { + promise->result.exception = (*ppromise)->result.exception; + (*ppromise)->result.exception = 0; + (*ppromise)->header.promise_result_is_exception = 0; + } + else + { + promise->result.value = (*ppromise)->result.value; + (*ppromise)->result.value = 0; + } + promise->header.promise_state = (*ppromise)->header.promise_state; + promise_array->resolver->promise = 0; + promise->resolver = 0; + async_todo_unlink( manager, &promise->header ); + // Lets continue normally to keep the code simple + } + struct async_promise_array_result *slot = + &promise_array->results[ promise_array->numresults++ ]; + slot->promise_array = promise_array; + promise_array->exec_refcount++; + promise_array->refcount++; + async_promise_add_c_int_user_args_flags_func( manager, *ppromise, async_promise_array_then, slot, promiseThen); + promise_array->refcount++; + async_promise_add_c_int_user_args_flags_func( manager, *ppromise, async_promise_array_catch, slot, promiseCatch); + } + else if( ASYNC_PROMISE_RACE == type ) + { + if( promisePending == promise->header.promise_state ) + { + // Promise should be resolved by this value + promise->result.value = ucv_get( elem ); + promise->header.promise_state = promiseThen; + promise_array->resolver->promise = 0; + promise->resolver = 0; + async_todo_unlink( manager, &promise->header ); + } + } + else if( ASYNC_PROMISE_ALLSETTLED == type ) + { + // This value should simply show up as fullfilled: + struct async_promise_array_result *slot = + &promise_array->results[ promise_array->numresults++ ]; + slot->promise_array = promise_array; + slot->state = promiseThen; + slot->value = ucv_get( elem ); + } + } + + if (0 == promise_array->exec_refcount && 0 != promise_array->resolver->promise ) + { + // Array didn't contain any promises. We will resolve in a 'setImmediate'. + promise_array->refcount++; + async_timer_t *timer = async_timer_c_int_user_flags_new( manager, async_promise_array_immediate, promise_array); + async_todo_put_in_list( manager, &timer->header); + timer->header.refcount--; + } + + return uc_resource_new( manager->promise_type, promise); +} + +static struct uc_value * +_uc_async_new_promise( struct uc_async_manager *_man, uc_async_promise_resolver_t **resolver) +{ + if( !resolver ) + return 0; + async_manager_t *manager = async_manager_cast( _man ); + async_promise_t *promise = uc_promise_new( manager ); + *resolver = &async_promise_resolver_new(promise)->header; + return uc_resource_new( manager->promise_type, promise); +} + +static void +_uc_async_resolve_reject( struct uc_async_manager *_man, uc_async_promise_resolver_t **resolver, uc_value_t *res, bool resolve) +{ + if (!resolver || !*resolver) + return; + async_manager_t *manager = async_manager_cast( _man ); + async_promise_resolver_t *res2 = async_promise_resolver_cast(*resolver); + *resolver = 0; + async_resolve_or_reject( manager, res2, res, resolve ? promiseThen : promiseCatch); + async_promise_resolver_unlink( manager, res2); +} + +static char *uc_cast_string(uc_vm_t *vm, uc_value_t **v, bool *freeable) { + if (ucv_type(*v) == UC_STRING) { + *freeable = false; + + return _ucv_string_get(v); + } + + *freeable = true; + + return ucv_to_string(vm, *v); +} + +/** + * Creates and returns a promise. The provided resolver function will be called + * asynchronously. + * + * @function module:async#Promise + * + * @param {Function} callback + * The callback used to deliver the {?module:async.resolver} object. + * + * @returns {?module:async.promise} + * + * @example + * // Create a promise + * async Promise( (resolve,reject)=> + * { + * resolve( 'world' ); + * print( 'hello ' ); + * }).then( (a)=> + * { + * print( a ); + * }); + * // will output 'hello world' + */ + +static uc_value_t * +Promise(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *func = uc_fn_arg(0); + if (!ucv_is_callable(func)) + { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "arg1 needs to be callable"); + return 0; + } + + async_manager_t *manager = async_manager_get( vm ); + + async_promise_t *promise = uc_promise_new( manager ); + async_promise_resolver_t *resolver = async_promise_resolver_new(promise); + resolver->callback = ucv_get(func); + + async_timer_t *timer = async_timer_c_int_user_flags_new( manager, uc_resolver_immediate, resolver); + async_todo_put_in_list( manager, &timer->header); + timer->header.refcount--; + + return uc_resource_new( manager->promise_type, promise ); +} + +/** + * Takes an array of {@link module:async.promise|promises}, and returns a single promise. + * + * When one of the promises is rejected, the new promise is rejected immediately + * with the reject value. + * When all of the promises are resolved, the new promise is resolved with an array + * of the resolve values. + * + * @function module:async#PromiseAll + * + * @param {Array} promises + * Array of {@link module:async.promise|promises}. Elements in the array which are no promise are ignored. + * An empty array will cause the returned promise to be resolved immediately. + * + * @returns {?module:async.promise} + * + * @example + * function NewPromise( interval, value, do_reject ) + * { + * return async.Promise( (resolve,reject)=> + * { + * async.setTimeout( ()=> + * { + * if( do_reject ) reject( value ); + * resolve( value ); + * }, interval ); + * } ); + * } + * + * // Create an array of promises: + * let promises = [ NewPromise(300,'A',false), + * NewPromise(200,'B',false), + * 'hello', + * NewPromise(400,'C',true) ]; + * + * async.PromiseAll( promises ).then( (a)=> + * { + * print( 'fullfilled ', a, '\n' ); + * }).catch( (a)=> + * { + * print( 'rejected ', a, '\n' ); + * }); + * // will output 'rejected C', however, if you change the boolean for 'C' in false, + * // it will output 'fullfilled [ "A", "B", "C" ]' + */ + +static uc_value_t * +PromiseAll(uc_vm_t *vm, size_t nargs) +{ + return async_promise_array_new( vm, nargs, ASYNC_PROMISE_ALL ); +} + +/** + * Takes an array of {@link module:async.promise|promises}, and returns a single promise. + * + * The first promise which resolves, resolves the new promise. + * When none of the promises is resolved, the new promise will be rejected with + * an array of reject objects. + * + * @function module:async#PromiseAny + * + * @param {Array} promises + * Array of {@link module:async.promise|promises}. Elements in the array which are no promise are ignored. + * An empty array will cause the returned promise to be rejected immediately. + * + * @returns {?module:async.promise} + * + * @example + * function NewPromise( interval, value, do_reject ) + * { + * return async.Promise( (resolve,reject)=> + * { + * async.setTimeout( ()=> + * { + * if( do_reject ) reject( value ); + * resolve( value ); + * }, interval ); + * } ); + * } + * + * // Create an array of promises: + * let promises = [ NewPromise(300,'A',false), + * NewPromise(200,'B',false), + * 'hello', + * NewPromise(400,'C',true) ]; + * + * async.PromiseAny( promises ).then( (a)=> + * { + * print( 'fullfilled ', a, '\n' ); + * }).catch( (a)=> + * { + * print( 'rejected ', a, '\n' ); + * }); + * // will output 'fullfilled B', however, if you change the booleans for 'A' and 'B' + * // in true, it will output 'rejected [ "A", "B", "C" ]' + */ + +static uc_value_t * +PromiseAny(uc_vm_t *vm, size_t nargs) +{ + return async_promise_array_new( vm, nargs, ASYNC_PROMISE_ANY ); +} + +/** + * Takes an array of {@link module:async.promise|promises}, and returns a single promise. + * + * The first promise which settles, settles the new promise. + * When the array of promises contains promises which are already settled, + * or non-promise values, the new promise will settle with the first one of those + * in the array. + * + * @function module:async#PromiseRace + * + * @param {Array} promises + * Array of {@link module:async.promise|promises}. An empty array will cause the returned + * promise to be pending forever. + * + * @returns {?module:async.promise} + * + * @example + * function NewPromise( interval, value, do_reject ) + * { + * return async.Promise( (resolve,reject)=> + * { + * async.setTimeout( ()=> + * { + * if( do_reject ) reject( value ); + * resolve( value ); + * }, interval ); + * } ); + * } + * + * // Create an array of promises: + * let promises = [ NewPromise(300,'A',false), + * NewPromise(200,'B',false), + * 'hello', + * NewPromise(50,'C',true) ]; + * + * async.PromiseRace( promises ).then( (a)=> + * { + * print( 'fullfilled ', a, '\n' ); + * }).catch( (a)=> + * { + * print( 'rejected ', a, '\n' ); + * }); + * // will output 'fullfilled hello', however, if you remove the 'hello' value, + * // it will output 'rejected C' + */ + +static uc_value_t * +PromiseRace(uc_vm_t *vm, size_t nargs) +{ + return async_promise_array_new( vm, nargs, ASYNC_PROMISE_RACE ); +} + +/** + * Takes an array of {@link module:async.promise|promises}, and returns a single promise. + * + * When all promises are settled, the new promise is fullfilled with an array of objects. + * ``` + * { + * status: 'fullfilled', // or 'rejected' + * reason: 'whatever', // only set in case of 'rejected' + * value: 'whatever' // only set in case of 'fullfilled' + * } + * ``` + * A non-promise value in the promise array will be 'fullfilled' in the result array. + * + * @function module:async#PromiseAllSettled + * + * @param {Array} promises + * Array of {@link module:async.promise|promises}. + * + * @returns {?module:async.promise} + * + * @example + * function NewPromise( interval, value, do_reject ) + * { + * return async.Promise( (resolve,reject)=> + * { + * async.setTimeout( ()=> + * { + * if( do_reject ) reject( value ); + * resolve( value ); + * }, interval ); + * } ); + * } + * + * // Create an array of promises: + * let promises = [ NewPromise(300,'A',false), + * NewPromise(200,'B',false), + * 'hello', + * NewPromise(50,'C',true) ]; + * + * async.PromiseAllSettled( promises ).then( (a)=> + * { + * print( 'fullfilled ', a, '\n' ); + * }).catch( (a)=> + * { + * print( 'rejected ', a, '\n' ); + * }); + * // will output 'fullfilled [ { "status": "fullfilled", "value": "A" }, { "status": "fullfilled", "value": "B" }, { "status": "fullfilled", "value": "hello" }, { "status": "rejected", "reason": "C" } ]' + */ + +static uc_value_t * +PromiseAllSettled(uc_vm_t *vm, size_t nargs) +{ + return async_promise_array_new( vm, nargs, ASYNC_PROMISE_ALLSETTLED ); +} + +/** + * Helper function to make it possible to throw an object from a promise method. + * + * When calling this function from within a promise method, the promise is rejected, and + * the provided argument is delivered in the next catch handler. + * + * When called outside a promise method, async.throw( a ) is more or less a synonym of + * die( \`${a}\` ); + * + * @function module:async#throw + * + * @param {Any} error + * Object or value to be delivered in the next catch handler + * + * @throws {Error} + * + * @example + * async.Promise( (resolve,reject)=>{resolve('hello ')}).then((a)=> + * print( a ); + * async.throw( 'world' ); + * print( 'this will never be printed' ) + * }).then( (a)=> + * { + * print( 'this will also never be printed' ); + * }).catch( (a)=> + * print( a ) ); + * }); + * // Will output 'hello world'. + */ + +static uc_value_t * +Throw( uc_vm_t *vm, size_t nargs ) +{ + async_manager_t *manager = async_manager_get( vm ); + async_promise_t *promise = (0 == manager) ? 0 : manager->active_promise; + + if( promise ) + { + // Throw being called from inside a promise + promise->header.promise_state = promiseCatch; + promise->result.value = ucv_get( uc_fn_arg(0) ); + promise->header.promise_result_is_exception = 0; + + // create a 'lightweight' exception, to prevent further code execution + vm->exception.type = EXCEPTION_USER; + return ucv_boolean_new( true ); + } + + // Create a 'fullblown' exception + uc_value_t *v = uc_fn_arg(0); + bool freeable = false; + char *casted = uc_cast_string( vm, &v, &freeable ); + uc_vm_raise_exception( vm, EXCEPTION_USER, "%s", casted ); + if( freeable ) + free( casted ); + return ucv_boolean_new( false ); +} + +static const uc_function_list_t local_async_fns[] = { + {"Promise", Promise}, + {"PromiseAll", PromiseAll}, + {"PromiseAny", PromiseAny}, + {"PromiseRace", PromiseRace}, + {"PromiseAllSettled",PromiseAllSettled}, + {"throw", Throw} +}; + +void async_promise_init( async_manager_t *manager, uc_value_t *scope ) +{ + manager->header.new_promise = _uc_async_new_promise; + manager->header.resolve_reject = _uc_async_resolve_reject; + + /* promise initializing */ + manager->promise_type = uc_type_declare(manager->vm, _strPromise, promise_type_fns, close_promise); + + uc_function_list_register(scope, local_async_fns); +} diff --git a/lib/async/promise.h b/lib/async/promise.h new file mode 100644 index 00000000..5d1d9705 --- /dev/null +++ b/lib/async/promise.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ + +#ifndef UC_ASYNC_PROMISE_H +#define UC_ASYNC_PROMISE_H + +#include "manager.h" + +extern __hidden int +async_promise_destroy( async_manager_t *, async_todo_t *); + +extern __hidden int +async_promise_do( async_manager_t *, async_todo_t *); + +typedef struct async_promise +{ + async_todo_t header; + async_manager_t *manager; + struct uc_async_promise_resolver *resolver; + union + { + uc_value_t *value; + uc_exception_t *exception; + } result; + /* Contains the ucode function which caused the reject. + To be used for the user feedback when no catch handler is found */ + uc_value_t *reject_caused_by; + + struct async_promise_method *stack; + +} async_promise_t; + +extern __hidden async_promise_t * +uc_promise_new( async_manager_t *manager ); + +extern __hidden void +async_promise_init( async_manager_t *manager, uc_value_t *scope ); + +#endif //ndef UC_ASYNC_PROMISE_H diff --git a/lib/async/queuer.c b/lib/async/queuer.c new file mode 100644 index 00000000..7b40872e --- /dev/null +++ b/lib/async/queuer.c @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ +#include +#include +#include + +#include "ucode/lib.h" +#include "ucode/vm.h" +#include "ucode/platform.h" +#include "ucode/async.h" + +#include "manager.h" +#include "timer.h" +#include "queuer.h" + + +/******* + * The part of the code which is responsible for multithreaded asynchronity + **/ +#define SIGNEWCALLBACK SIGUSR1 // Signal used for async callbacks +/* +static uc_value_t * +async_callback_signal_handler( uc_vm_t *vm, size_t nargs ) +{ + // Do nothing. We only want to interrupt the async_sleep function +#ifdef DEBUG_PRINT + async_manager_t *manager = async_manager_get( vm ); + DEBUG_PRINTF( "%-1.3lf Signal handler vm=%p\n", manager ? async_manager_uptime(manager) : NAN, vm ); +#endif + return 0; +} +*/ +static async_manager_t * +async_callback_is_synchron( const async_callback_queuer_t *queuer ) +{ + if (queuer->thread == pthread_self()) + return queuer->manager; + return 0; +} + +/* Wakeup the sleeping script engine */ +static void +async_callback_queuer_wakeup( const async_callback_queuer_t *queuer ) +{ + if (async_callback_is_synchron( queuer ) ) + // running in the script thread + return; + + DEBUG_PRINTF( "%-1.3lf Wakeup script vm=%p\n", async_manager_uptime( queuer->manager ), queuer->manager->vm ); + // send a signal to the script thread; + pthread_kill( queuer->thread, SIGNEWCALLBACK ); +} + +void async_sleep( async_manager_t *manager, int64_t msec ) +{ + sigset_t waitset; + struct timespec timeout; + + sigemptyset( &waitset ); + sigaddset( &waitset, SIGNEWCALLBACK ); + + timeout.tv_sec = msec / 1000; + timeout.tv_nsec = (msec % 1000) * 1000000; + + sigtimedwait( &waitset, 0, &timeout ); +} + + +static int +async_callback_queuer_addref( const async_callback_queuer_t *cqueuer, bool add ) +{ + async_callback_queuer_t *queuer = (async_callback_queuer_t *)cqueuer; + if( add ) + return __atomic_add_fetch(&queuer->refcount, 1, __ATOMIC_RELAXED); + else + return __atomic_add_fetch(&queuer->refcount, -1, __ATOMIC_RELAXED); +} + +static struct async_callback_queuer_chain * +uc_async_queuer_lock_stack( const async_callback_queuer_t *cqueuer) +{ + async_callback_queuer_t *queuer = (async_callback_queuer_t *)cqueuer; + struct async_callback_queuer_chain **pstack = &queuer->stack; + /* + The stack is locked as the least significant bit is 1. + So we try to set it, which only succeeds if it is not set now. + */ + while (true) + { + struct async_callback_queuer_chain *oldstack = *pstack; + oldstack = (void *)(((intptr_t)oldstack) & ~(intptr_t)1); + struct async_callback_queuer_chain *newstack = oldstack; + newstack = (void *)(((intptr_t)newstack) | (intptr_t)1); + if (__atomic_compare_exchange_n( pstack, &oldstack, newstack, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) + { + return oldstack; + } + } +} + +static void +uc_async_queuer_unlock_stack( const async_callback_queuer_t *cqueuer, struct async_callback_queuer_chain *func) +{ + async_callback_queuer_t *queuer = (async_callback_queuer_t *)cqueuer; + struct async_callback_queuer_chain **pstack = &queuer->stack; + /* + Unlock the stack by writing a 'clean' pointer, without bit 0 set. + */ + __atomic_store_n( pstack, func, __ATOMIC_RELAXED); +} + +/******* + * End of multithreaded functions + */ + +struct async_callback_queuer_chain +{ + struct async_callback_queuer_chain *next; + struct async_callback callback; +}; + +/* +static inline async_callback_queuer_t * +async_callback_queuer_cast(struct uc_async_callback_queuer *handler) +{ + return (async_callback_queuer_t *)handler; +} +*/ + +static inline async_callback_queuer_t const * +async_callback_queuer_cast_const(struct uc_async_callback_queuer const *handler) +{ + return (async_callback_queuer_t const *)handler; +} + +int +async_handle_queued_callbacks( async_manager_t *manager ) +{ + if( 0 == manager || 0 == manager->callback_queuer ) + return EXCEPTION_NONE; + async_callback_queuer_t *queuer = manager->callback_queuer; + struct async_callback_queuer_chain *stack = uc_async_queuer_lock_stack(queuer); + uc_async_queuer_unlock_stack(queuer, 0); + + if (0 == stack) + return EXCEPTION_NONE; + + reverse_stack(struct async_callback_queuer_chain, stack); + + while (stack) + { + struct async_callback_queuer_chain *pop = stack; + stack = pop->next; + int ex = async_callback_call( manager, &pop->callback, 0, 0, 0, true); + free( pop ); + if (EXCEPTION_NONE == ex) + continue; + if (stack) + { + // put remaining stack back + struct async_callback_queuer_chain *last = stack; + reverse_stack(struct async_callback_queuer_chain, stack); + last->next = uc_async_queuer_lock_stack(queuer); + uc_async_queuer_unlock_stack(queuer, stack); + } + return ex; + } + return EXCEPTION_NONE; +} + +bool +async_any_queued_callbacks_waiting( async_manager_t *manager ) +{ + if (0 == manager || 0 == manager->callback_queuer) + return false; + async_callback_queuer_t *queuer = manager->callback_queuer; + if ((intptr_t)queuer->stack & ~(intptr_t)3) + return true; + return false; +} + +static bool +_uc_async_request_callback(struct uc_async_callback_queuer const *_queuer, + int (*func)(struct uc_vm *, void *, int), void *user) +{ + struct async_callback_queuer_chain *pfunc = xalloc(sizeof(struct async_callback_queuer_chain)); + pfunc->callback.callback_type = callbackC_int_user_flags; + pfunc->callback.c_int_user_flags.func = func; + pfunc->callback.c_int_user_flags.user = user; + + const async_callback_queuer_t *queuer = async_callback_queuer_cast_const(_queuer); + + struct async_callback_queuer_chain *stack = uc_async_queuer_lock_stack(queuer); + + if (stack == (struct async_callback_queuer_chain *)queuer ) + { + // vm doesn't exist anymore + uc_async_queuer_unlock_stack( queuer, stack); + free(pfunc); + return false; + } + + pfunc->next = stack; + uc_async_queuer_unlock_stack( queuer, pfunc); + + async_callback_queuer_wakeup( queuer ); + return true; +} + +void +async_wakeup( const uc_async_callback_queuer_t *_queuer ) +{ + if( !_queuer ) + return; + const async_callback_queuer_t *queuer = async_callback_queuer_cast_const( _queuer ); + async_callback_queuer_wakeup( queuer ); +} + + +static void +_uc_async_callback_queuer_free(struct uc_async_callback_queuer const **pqueuer) +{ + if (0 == pqueuer || 0 == *pqueuer) + return; + async_callback_queuer_t const *queuer = async_callback_queuer_cast_const(*pqueuer); + *pqueuer = 0; + + if( 0 == async_callback_queuer_addref( queuer, false ) ) + { + if( 0 == queuer->manager ) + free( (void *)queuer ); + } +} + +static int +_async_put_todo_in_list( uc_vm_t *vm, void *user, int flags) +{ + async_todo_t *todo = user; + async_manager_t *manager = async_manager_get( vm ); + + if (flags & UC_ASYNC_CALLBACK_FLAG_EXECUTE) + { + async_todo_put_in_list( manager, todo ); + } + if (flags & UC_ASYNC_CALLBACK_FLAG_CLEANUP) + { + async_todo_unlink( manager, todo ); + } + return EXCEPTION_NONE; +} + +static uc_async_timer_t * +_uc_async_create_timer(struct uc_async_callback_queuer const *_queuer, + int (*cb)(uc_vm_t *, void *, int), void *user, uint32_t msec, bool periodic) +{ + async_timer_t *timer = async_timer_c_int_user_flags_new(0, cb, user); + timer->due = async_timer_current_time() + msec; + if (periodic) + timer->periodic = msec; + timer->header.refcount++; + + async_callback_queuer_t const *queuer = async_callback_queuer_cast_const(_queuer); + + // are we synchron? + async_manager_t *manager = async_callback_is_synchron( queuer ); + if (manager) + { + _async_put_todo_in_list( manager->vm, &timer->header, UC_ASYNC_CALLBACK_FLAG_EXECUTE | UC_ASYNC_CALLBACK_FLAG_CLEANUP); + } + else + { + _uc_async_request_callback(_queuer, _async_put_todo_in_list, &timer->header ); + } + + return &timer->header.header; +} + +static int +_async_free_timer(uc_vm_t *vm, void *user, int flags) +{ + uintptr_t v = (uintptr_t)user; + bool clear = v & 1; + v = v & ~((uintptr_t)1); + async_timer_t *timer = (async_timer_t *)v; + async_manager_t *manager = async_manager_get(vm); + if (flags & UC_ASYNC_CALLBACK_FLAG_EXECUTE) + { + if (clear) + async_timer_destroy( manager, &timer->header ); + } + if (flags & UC_ASYNC_CALLBACK_FLAG_CLEANUP) + { + async_todo_unlink( async_manager_get(vm), &timer->header); + } + return EXCEPTION_NONE; +} + + +static void +_uc_async_free_timer(struct uc_async_callback_queuer const *_queuer, + uc_async_timer_t **_pptimer, bool clear) +{ + async_callback_queuer_t const *queuer = async_callback_queuer_cast_const(_queuer); + async_timer_t **pptimer = (async_timer_t **)_pptimer; + if (!pptimer || !*pptimer) + return; + async_timer_t *timer = *pptimer; + *_pptimer = 0; + + // use bit 0 to store the clear flag + if (clear) + { + timer = (async_timer_t *)(((uintptr_t)timer) | 1); + } + + // are we synchron? + async_manager_t *manager = async_callback_is_synchron( queuer ); + if (manager) + { + _async_free_timer( manager->vm, timer, UC_ASYNC_CALLBACK_FLAG_EXECUTE | UC_ASYNC_CALLBACK_FLAG_CLEANUP); + } + else + { + _uc_async_request_callback(_queuer, _async_free_timer, timer); + } +} + +static struct uc_async_callback_queuer const * +_uc_async_new_callback_queuer( struct uc_async_manager *_man ) +{ + async_manager_t *manager = async_manager_cast( _man ); + if( 0 == manager ) + return 0; + + async_callback_queuer_t *queuer = manager->callback_queuer; + if( 0 == queuer ) + { + manager->callback_queuer = queuer = xalloc(sizeof(async_callback_queuer_t)); + + sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGNEWCALLBACK ); + if( 0 != pthread_sigmask( SIG_BLOCK, &set, &queuer->oldmask ) ) + { + perror( "pthread_sigmask" ); + exit( 1 ); + } + +/* + pthread_sigmask( ) + // Setup signal handler + uc_cfn_ptr_t ucsignal = uc_stdlib_function("signal"); + uc_value_t *func = ucv_cfunction_new("async", async_callback_signal_handler); + + uc_vm_stack_push( manager->vm, ucv_uint64_new( SIGNEWCALLBACK )); + uc_vm_stack_push( manager->vm, func); + + if (ucsignal(manager->vm, 2) != func) + fprintf(stderr, "Unable to install async_callback_signal_handler\n"); + else + { + DEBUG_PRINTF("%-1.3lf vm=%p signal handler installed\n", async_manager_uptime(manager), manager->vm ); + + } + + ucv_put(uc_vm_stack_pop( manager->vm)); + ucv_put(uc_vm_stack_pop( manager->vm)); + ucv_put( func ); +*/ + + // Remember the thread ID + queuer->thread = pthread_self(); + // And the vm + queuer->manager = async_manager_link( manager ); + + queuer->header.free = _uc_async_callback_queuer_free; + queuer->header.request_callback = _uc_async_request_callback; + queuer->header.create_timer = _uc_async_create_timer; + queuer->header.free_timer = _uc_async_free_timer; + + queuer->refcount = 1; + } + + async_callback_queuer_addref( queuer, true ); + return &queuer->header; +} + +void +async_callback_queuer_free( async_manager_t *manager, async_callback_queuer_t *queuer) +{ + if (0 == queuer) + return; + + struct async_callback_queuer_chain *stack = uc_async_queuer_lock_stack( queuer ); + // write sentinel value meaning that callbacks are disabled forever + uc_async_queuer_unlock_stack( queuer, (void *)queuer ); + + async_manager_unlink( queuer->manager ); + queuer->manager = 0; + + struct uc_async_callback_queuer const *pconsth = &queuer->header; + _uc_async_callback_queuer_free(&pconsth); + + // TODO: Shouldn't we release the signal handler? + + // call all function on stack with exec=false to make them able to free up resources + while (stack) + { + struct async_callback_queuer_chain *pop = stack; + stack = pop->next; + async_callback_destroy( manager->vm, &pop->callback); + } +} + +void +async_callback_queuer_init( async_manager_t *manager, uc_value_t *scope ) +{ + manager->header.new_callback_queuer = _uc_async_new_callback_queuer; +} + diff --git a/lib/async/queuer.h b/lib/async/queuer.h new file mode 100644 index 00000000..676ef7d4 --- /dev/null +++ b/lib/async/queuer.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ +#ifndef ASYNC_QUEUER_H +#define ASYNC_QUEUER_H + +#include +#include "ucode/async.h" +#include "manager.h" + +struct async_callback_queuer_chain; + +typedef struct async_callback_queuer +{ + struct uc_async_callback_queuer header; + + // linked list of callbacks to be executed + struct async_callback_queuer_chain *stack; + int refcount; + + // Thread of the script + pthread_t thread; + // VM in which we live. + async_manager_t *manager; + // Stored sigmask + sigset_t oldmask; +} async_callback_queuer_t; + +extern __hidden void +async_callback_queuer_init( async_manager_t *manager, uc_value_t *scope ); + + +extern __hidden void +async_callback_queuer_free( async_manager_t *, async_callback_queuer_t * ); + +extern __hidden int +async_handle_queued_callbacks( async_manager_t *manager ); + +extern __hidden bool +async_any_queued_callbacks_waiting( async_manager_t *manager ); + +extern __hidden void +async_wakeup( const uc_async_callback_queuer_t *queuer ); + +extern __hidden void +async_sleep( async_manager_t *manager, int64_t msec ); + + +#endif // ndef ASYNC_QUEUER_H \ No newline at end of file diff --git a/lib/async/timer.c b/lib/async/timer.c new file mode 100644 index 00000000..1fb15e71 --- /dev/null +++ b/lib/async/timer.c @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ + +#include "ucode/lib.h" +#include "ucode/vm.h" +#include "ucode/types.h" + +#include "manager.h" +#include "callback.h" +#include "timer.h" + +/** + * Represents a timer object as returned by + * {@link module:async#setTimeout|setTimeout()}, {@link module:async#setInterval|setInterval()} or {@link module:async#setImmediate|setImmediate()}. + * + * This class has no methods. The only sane usage is to pass it to {@link module:async#clearTimeout|clearTimeout()} + * + * @class module:async.timer + * @hideconstructor + * + */ +// async_timer_t + +int64_t +async_timer_current_time(); + +static async_timer_t * +uc_timer_ucode_new( async_manager_t *manager, size_t nargs, uc_value_t *cb, size_t startarg) +{ + size_t n_args = nargs - startarg; + async_timer_t *timer = xalloc(sizeof(async_timer_t) + n_args * sizeof(uc_value_t *)); + timer->header.refcount = 1; + timer->header.todo_type = todoTimer; + timer->callback.callback_type = callbackUcode; + timer->callback.nargs = n_args; + timer->callback.ucode.func = ucv_get(cb); + uc_vm_t *vm = manager->vm; + + for (size_t n1 = startarg, n2 = 0; n1 < nargs; n1++, n2++) + timer->callback.args[n2] = ucv_get(uc_fn_arg(n1)); + + return timer; +} + +async_timer_t * +async_timer_c_int_user_flags_new( async_manager_t *manager, int (*func)(uc_vm_t *, void *, int), void *user) +{ + async_timer_t *timer = xalloc(sizeof(async_timer_t)); + timer->header.refcount = 1; + timer->header.todo_type = todoTimer; + timer->callback.callback_type = callbackC_int_user_flags; + timer->callback.c_int_user_flags.func = func; + timer->callback.c_int_user_flags.user = user; + return timer; +} + +void +async_timer_destroy( async_manager_t *manager, async_todo_t *todo ) +{ + if( !todo ) + return; + async_timer_t *timer = async_timer_cast( todo ); + timer->header.todo_type = todoClearedTimer; + timer->due = 0; + timer->periodic = 0; + + async_callback_destroy( manager ? manager->vm : 0, &timer->callback ); +} + +int +async_timer_do( async_manager_t *manager, async_todo_t *todo ) +{ + async_timer_t *timer = async_timer_cast(todo); + int ex = async_callback_call(manager, &timer->callback, 0, 0, 0, false); + if (0 == timer->periodic || + // the timer can be cleared in the callback itself + todoClearedTimer == timer->header.todo_type) + return ex; + timer->due += timer->periodic; + async_todo_put_in_list(manager, &timer->header); + return ex; +} + +int64_t +async_timer_current_time() +{ + struct timespec monotime; + clock_gettime(CLOCK_MONOTONIC, &monotime); + return ((int64_t)monotime.tv_sec) * 1000 + (monotime.tv_nsec / 1000000); +} + + +enum +{ + timerTimeout = 0, + timerPeriodic, + timerImmediate, +}; + +static const char *_strTimer = "async.timer"; + +static void +close_timer(void *p) +{ + async_timer_t *timer = p; + if (timer) + { + async_todo_unlink(0, &timer->header); + } +} + +static uc_value_t * +createTimer(uc_vm_t *vm, size_t nargs, int type) +{ + uc_value_t *cb = uc_fn_arg(0); + if (!ucv_is_callable(cb)) + { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "arg1 needs to be callable"); + return 0; + } + int64_t timeout = 0; + if (nargs > 1 && timerImmediate != type) + { + uc_value_t *arg2 = uc_fn_arg(1); + timeout = ucv_int64_get(arg2); + } + else if (timerPeriodic == type) + { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "arg2 needs to be a number"); + return 0; + } + + if (timerPeriodic == type && timeout < 1) + timeout = 1; + + size_t startarg = 2; + if (timerImmediate == type) + startarg = 1; + + async_manager_t *manager = async_manager_get( vm ); + + async_timer_t *timer = uc_timer_ucode_new( manager, nargs, cb, startarg); + + timer->periodic = (timerPeriodic == type) ? timeout : 0; + + if (timerImmediate == type) + { + timer->due = 0; + } + else + { + timer->due = async_timer_current_time() + timeout; + } + + async_todo_put_in_list(manager, &timer->header); + timer->header.refcount--; + + if (0 == manager->timer_type) + { + static const uc_function_list_t timer_fns[] = {}; + manager->timer_type = uc_type_declare(vm, _strTimer, timer_fns, close_timer); + } + + timer->header.refcount++; + return uc_resource_new(manager->timer_type, timer); +} + +/** + * Start a timer, to execute it's callback after a delay. + * + * The timer can be stopped before the callback is executed using clearTimeout() + * + * @function module:async#setTimeout + * + * @param {Function} callback + * + * @param {Number} [interval = 0] + * Optional time to be waited (in msec) before the callback is called. + * + * @param {Any} ...args + * Optional Argument(s) to be passed to the callback function. + * + * @returns {?module:async.timer} + * + * @example + * async.setTimeout( (a)=> + * { + * print( a ); + * }, 10000, 'hello world' ); + * + * while( async.PumpEvents() ); + * // Will output 'hello world' after 10 seconds, and then exit. + */ + +static uc_value_t * +setTimeout(uc_vm_t *vm, size_t nargs) +{ + return createTimer(vm, nargs, timerTimeout); +} + +/** + * Start a periodic timer, to execute it's callback at each interval. + * + * The timer can be stopped using clearTimeout() + * + * @function module:async#setInterval + * + * @param {Function} callback + * + * @param {Number} interval + * Interval time in millisec. + * + * @param {Any} ...args + * Optional Argument(s) to be passed to the callback function. + * + * @returns {?module:async.timer} + * + * @example + * async.setInterval( (a)=> + * { + * print( `${++a.count}\n` ); + * }, 1000, { count: 0 } ); + * + * while( async.PumpEvents() ); + * // Will output '1\n2\n3\n...' forever. + */ + +static uc_value_t * +setInterval(uc_vm_t *vm, size_t nargs) +{ + return createTimer(vm, nargs, timerPeriodic); +} + +/** + * Let callback be executed in the next event pump stroke. + * + * In theory it can be stopped using clearTimeout() + * + * A *setImmediate()* is executed before *setTimeout( ()=>{}, 0 )*. + * Background: the setTimeout() is scheduled at *now + 0 msec*, + * while 'setImmediate()' is scheduled at *start of time*, + * which has already passed. + * + * @function module:async#setImmediate + * + * @param {Function} callback + * + * @param {Any} ...args + * Optional Argument(s) to be passed to the callback function. + * + * @returns {?module:async.timer} + * + * @example + * async.setTimeout( (a)=> + * { + * print( a ); + * }, 0, 'world' ); + * async.setImmediate( (a)=> + * { + * print( a ); + * }, 'hello ' ); + * + * while( async.PumpEvents() ); + * // Will output 'hello world', and exit. + */ + +static uc_value_t * +setImmediate(uc_vm_t *vm, size_t nargs) +{ + return createTimer(vm, nargs, timerImmediate); +} + +/** + * Clears a timer. It's safe to call it more than once on the same timer object. + * + * @function module:async#clearTimeout + * + * @param {?module:async.timer} timer + * + * @returns {Boolean} + * True if the timer is a valid timer object, and false if it isn't. + * + * @example + * let timer = async.setTimeout( (a)=> + * { + * print( 'hello world' ); + * }, 1000 ); + * async.clearTimeout( timer ); + * while( async.PumpEvents() ); + * // Will output nothing, and exit immediately. + */ + +static uc_value_t * +clearTimeout(uc_vm_t *vm, size_t nargs) +{ + async_timer_t **ptimer = (async_timer_t **)ucv_resource_dataptr(uc_fn_arg(0), _strTimer); + if (ptimer && *ptimer) + { + async_timer_t *timer = *ptimer; + async_manager_t *manager = async_manager_get( vm ); + async_timer_destroy( manager, &timer->header ); + return ucv_boolean_new(true); + } + return ucv_boolean_new(false); +} + +static const uc_function_list_t local_async_fns[] = { + {"setTimeout", setTimeout}, + {"setInterval", setInterval}, + {"setImmediate", setImmediate}, + {"clearTimeout", clearTimeout}, +}; + +void +async_timer_init( async_manager_t *manager, uc_value_t *scope ) +{ + uc_function_list_register(scope, local_async_fns); +} diff --git a/lib/async/timer.h b/lib/async/timer.h new file mode 100644 index 00000000..2977f697 --- /dev/null +++ b/lib/async/timer.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2025 Isaac de Wolff + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This file is part of the async plugin for ucode +*/ + +#ifndef UC_ASYNC_TIMER_H +#define UC_ASYNC_TIMER_H + +#include "manager.h" +#include "callback.h" + +extern __hidden int64_t +async_timer_current_time(); + +typedef struct async_timer +{ + async_todo_t header; + int64_t due; + uint32_t periodic; + struct async_callback callback; +} async_timer_t; + +// Safety. Let the compiler error out when the wrong type is casted. +static inline struct async_timer * +async_timer_cast(async_todo_t *p) +{ + DEBUG_ASSERT(p->todo_type & todoTimer); + return (struct async_timer *)p; +} + +extern __hidden void +async_timer_destroy( async_manager_t *, async_todo_t * ); + +extern __hidden int +async_timer_do( async_manager_t *, async_todo_t * ); + +extern __hidden struct async_timer * +async_timer_c_int_user_flags_new( async_manager_t *manager, int (*func)(uc_vm_t *, void *, int), void *user); + +extern __hidden void +async_timer_init( async_manager_t *manager, uc_value_t *scope ); + +#endif //ndef UC_ASYNC_TIMER_H diff --git a/lib/debug.c b/lib/debug.c index 0b227d9f..bd6c0c14 100644 --- a/lib/debug.c +++ b/lib/debug.c @@ -419,7 +419,7 @@ print_memdump(uc_vm_t *vm, FILE *out) print_function_srcpos(ctx.out, frame->closure); else fprintf(ctx.out, " @ [C function \"%s\"]", - frame->cfunction->name); + uvc_cfunction_get_name( frame->cfunction ) ); } else if (i == frame->stackframe) { fprintf(ctx.out, " (callee)"); @@ -428,7 +428,7 @@ print_memdump(uc_vm_t *vm, FILE *out) print_function_srcpos(ctx.out, frame->closure); else fprintf(ctx.out, " @ [C function \"%s\"]", - frame->cfunction->name); + uvc_cfunction_get_name( frame->cfunction ) ); } else if (i > frame->stackframe) { fprintf(ctx.out, " (argument #%zu)", @@ -438,7 +438,7 @@ print_memdump(uc_vm_t *vm, FILE *out) print_function_srcpos(ctx.out, frame->closure); else fprintf(ctx.out, " @ [C function \"%s\"]", - frame->cfunction->name); + uvc_cfunction_get_name( frame->cfunction ) ); } } diff --git a/types.c b/types.c index 5dbf6a86..dd69fcbc 100644 --- a/types.c +++ b/types.c @@ -288,6 +288,15 @@ ucv_free(uc_value_t *uv, bool retain) ucv_put_value(upval->value, retain); break; + case UC_CFUNCTION: + if( ucv_is_cfunction_ex( uv ) ) + { + uc_cfunction_ex_t *cfn = (uc_cfunction_ex_t *)uv; + if( cfn->feedback ) + cfn->feedback( uv ); + } + break; + case UC_PROGRAM: program = (uc_program_t *)uv; @@ -1150,6 +1159,57 @@ ucv_cfunction_new(const char *name, uc_cfn_ptr_t fptr) return &cfn->header; } +bool +ucv_cfunction_ex_helper( int cmd, void *args ) +{ + _Static_assert( offsetof(uc_cfunction_t,name) == offsetof(uc_cfunction_ex_t,magic), + "Problem with alignment of uc_cfunction_ex_t" ); + + static const char *strmagic = +#if INTPTR_MAX == INT64_MAX + "\xFF" "FEEDBA"; // 8 bytes, including closing zero +#else + "\xFF" "FB"; // 4 bytes, including closing zero +#endif + + if( 0 == cmd ) // ucv_cfunction_ex_new() + { + void **pargs = args; + uc_cfunction_ex_t *cfn; + + size_t namelen = 0; + const char *name = (const char *)pargs[ 0 ]; + + if (name) + namelen = strlen(name); + + cfn = xalloc(sizeof(*cfn) + ALIGN( namelen + 1 ) + (size_t)pargs[3] ); + cfn->header.type = UC_CFUNCTION; + cfn->header.refcount = 1; + cfn->cfn = pargs[ 1 ]; + cfn->magic = *(intptr_t *)strmagic; + cfn->feedback = pargs[ 2 ]; + + if( name ) + strcpy( cfn->name, name ); + + pargs[ 0 ] = cfn; + return true; + } + if( 1 == cmd ) // ucv_cfunction_ex_get_magic() + { + intptr_t *ret = args; + *ret = *(intptr_t *)strmagic; + return true; + } + if( 2 == cmd ) // ucv_cfunction_ex_get_magic string + { + const char **ret = args; + *ret = strmagic; + return true; + } + return false; +} uc_value_t * ucv_closure_new(uc_vm_t *vm, uc_function_t *function, bool arrow_fn) @@ -1851,15 +1911,18 @@ ucv_to_stringbuf_formatted(uc_vm_t *vm, uc_stringbuf_t *pb, uc_value_t *uv, size break; case UC_CFUNCTION: + { cfunction = (uc_cfunction_t *)uv; + const char *name = uvc_cfunction_get_name( cfunction ); ucv_stringbuf_printf(pb, "%sfunction%s%s(...) { [native code] }%s", json ? "\"" : "", - cfunction->name[0] ? " " : "", - cfunction->name[0] ? cfunction->name : "", + name[0] ? " " : "", + name[0] ? name : "", json ? "\"" : ""); break; + } case UC_RESOURCE: resource = (uc_resource_t *)uv; @@ -2481,13 +2544,26 @@ uc_search_path_init(uc_search_path_t *search_path) static __thread uc_thread_context_t *tls_ctx; uc_thread_context_t * -uc_thread_context_get(void) +uc_thread_context_helper( int cmd, void *args ) { - if (tls_ctx == NULL) { - tls_ctx = xalloc(sizeof(*tls_ctx)); - tls_ctx->object_iterators.prev = &tls_ctx->object_iterators; - tls_ctx->object_iterators.next = &tls_ctx->object_iterators; + if( 0 == cmd ) // uc_thread_context_get() + { + if (tls_ctx == NULL) { + tls_ctx = xalloc(sizeof(*tls_ctx)); + tls_ctx->object_iterators.prev = &tls_ctx->object_iterators; + tls_ctx->object_iterators.next = &tls_ctx->object_iterators; + } + return tls_ctx; } - - return tls_ctx; + if( 1 == cmd ) // uc_thread_context_peek() + { + return tls_ctx; + } + if( 2 == cmd ) // uc_thread_context_exchange() + { + uc_thread_context_t *cur = tls_ctx; + tls_ctx = args; + return cur; + } + return 0; } diff --git a/vm.c b/vm.c index fc322c5d..2abb49a8 100644 --- a/vm.c +++ b/vm.c @@ -29,6 +29,7 @@ #include "ucode/program.h" #include "ucode/lib.h" /* uc_error_context_format() */ #include "ucode/platform.h" +#include "ucode/async.h" #undef __insn #define __insn(_name) #_name, @@ -949,7 +950,7 @@ uc_vm_capture_stacktrace(uc_vm_t *vm, size_t i) name = "[anonymous function]"; } else { - name = frame->cfunction->name; + name = (char *)uvc_cfunction_get_name( frame->cfunction ); } ucv_object_add(entry, "function", ucv_string_new(name)); @@ -3087,6 +3088,8 @@ uc_vm_execute(uc_vm_t *vm, uc_program_t *program, uc_value_t **retval) status = uc_vm_execute_chunk(vm); + status = uc_async_finish( vm, status, UINT_MAX ); + switch (status) { case STATUS_OK: val = uc_vm_stack_pop(vm);