diff --git a/CMakeLists.txt b/CMakeLists.txt index f2eb540..1eb5a2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ if(MSVC) xcheck_add_c_compiler_flag(-Wno-reserved-macro-identifier) xcheck_add_c_compiler_flag(-Wno-reserved-identifier) xcheck_add_c_compiler_flag(-Wdeprecated-declarations) - add_compile_definitions(WIN32_LEAN_AND_MEAN) + xcheck_add_c_compiler_flag(/experimental:c11atomics) endif() if(CMAKE_SYSTEM_NAME STREQUAL "WASI") @@ -153,10 +153,16 @@ if(BUILD_QJS_LIBC) list(APPEND qjs_sources quickjs-libc.c) endif() list(APPEND qjs_defines _GNU_SOURCE) +if(WIN32) + list(APPEND qjs_defines WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0602) +endif() list(APPEND qjs_libs qjs ${CMAKE_DL_LIBS}) +find_package(Threads) +if(NOT CMAKE_SYSTEM_NAME STREQUAL "WASI") + list(APPEND qjs_libs ${CMAKE_THREAD_LIBS_INIT}) +endif() if(NOT MSVC) - find_package(Threads) - list(APPEND qjs_libs ${CMAKE_THREAD_LIBS_INIT} m) + list(APPEND qjs_libs m) endif() add_library(qjs ${qjs_sources}) diff --git a/cutils.c b/cutils.c index 088e75e..0b21d47 100644 --- a/cutils.c +++ b/cutils.c @@ -34,6 +34,9 @@ #include "cutils.h" +#undef NANOSEC +#define NANOSEC ((uint64_t) 1e9) + #pragma GCC visibility push(default) void pstrcpy(char *buf, int buf_size, const char *str) @@ -649,7 +652,7 @@ uint64_t js__hrtime_ns(void) { * performance counter interval, integer math could cause this computation * to overflow. Therefore we resort to floating point math. */ - scaled_freq = (double) frequency.QuadPart / 1e9; + scaled_freq = (double) frequency.QuadPart / NANOSEC; result = (double) counter.QuadPart / scaled_freq; return (uint64_t) result; } @@ -660,7 +663,7 @@ uint64_t js__hrtime_ns(void) { if (clock_gettime(CLOCK_MONOTONIC, &t)) abort(); - return t.tv_sec * (uint64_t) 1e9 + t.tv_nsec; + return t.tv_sec * NANOSEC + t.tv_nsec; } #endif @@ -674,4 +677,219 @@ int64_t js__gettimeofday_us(void) { return ((int64_t)tv.tv_sec * 1000000) + tv.tv_usec; } +/* Cross-platform threading APIs. */ + +#if !defined(EMSCRIPTEN) && !defined(__wasi__) + +#if defined(_WIN32) +typedef void (*js__once_cb)(void); + +typedef struct { + js__once_cb callback; +} js__once_data_t; + +static BOOL WINAPI js__once_inner(INIT_ONCE *once, void *param, void **context) { + js__once_data_t *data = param; + + data->callback(); + + return TRUE; +} + +void js_once(js_once_t *guard, js__once_cb callback) { + js__once_data_t data = { .callback = callback }; + InitOnceExecuteOnce(guard, js__once_inner, (void*) &data, NULL); +} + +void js_mutex_init(js_mutex_t *mutex) { + InitializeCriticalSection(mutex); +} + +void js_mutex_destroy(js_mutex_t *mutex) { + DeleteCriticalSection(mutex); +} + +void js_mutex_lock(js_mutex_t *mutex) { + EnterCriticalSection(mutex); +} + +void js_mutex_unlock(js_mutex_t *mutex) { + LeaveCriticalSection(mutex); +} + +void js_cond_init(js_cond_t *cond) { + InitializeConditionVariable(cond); +} + +void js_cond_destroy(js_cond_t *cond) { + /* nothing to do */ + (void) cond; +} + +void js_cond_signal(js_cond_t *cond) { + WakeConditionVariable(cond); +} + +void js_cond_broadcast(js_cond_t *cond) { + WakeAllConditionVariable(cond); +} + +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { + if (!SleepConditionVariableCS(cond, mutex, INFINITE)) + abort(); +} + +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { + if (SleepConditionVariableCS(cond, mutex, (DWORD)(timeout / 1e6))) + return 0; + if (GetLastError() != ERROR_TIMEOUT) + abort(); + return -1; +} + +#else /* !defined(_WIN32) */ + +void js_once(js_once_t *guard, void (*callback)(void)) { + if (pthread_once(guard, callback)) + abort(); +} + +void js_mutex_init(js_mutex_t *mutex) { + if (pthread_mutex_init(mutex, NULL)) + abort(); +} + +void js_mutex_destroy(js_mutex_t *mutex) { + if (pthread_mutex_destroy(mutex)) + abort(); +} + +void js_mutex_lock(js_mutex_t *mutex) { + if (pthread_mutex_lock(mutex)) + abort(); +} + +void js_mutex_unlock(js_mutex_t *mutex) { + if (pthread_mutex_unlock(mutex)) + abort(); +} + +void js_cond_init(js_cond_t *cond) { +#if defined(__APPLE__) && defined(__MACH__) + if (pthread_cond_init(cond, NULL)) + abort(); +#else + pthread_condattr_t attr; + int err; + + if (pthread_condattr_init(&attr)) + abort(); + + if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) + abort(); + + if (pthread_cond_init(cond, &attr)) + abort(); + + if (pthread_condattr_destroy(&attr)) + abort(); +#endif +} + +void js_cond_destroy(js_cond_t *cond) { +#if defined(__APPLE__) && defined(__MACH__) + /* It has been reported that destroying condition variables that have been + * signalled but not waited on can sometimes result in application crashes. + * See https://codereview.chromium.org/1323293005. + */ + pthread_mutex_t mutex; + struct timespec ts; + int err; + + if (pthread_mutex_init(&mutex, NULL)) + abort(); + + if (pthread_mutex_lock(&mutex)) + abort(); + + ts.tv_sec = 0; + ts.tv_nsec = 1; + + err = pthread_cond_timedwait_relative_np(cond, &mutex, &ts); + if (err != 0 && err != ETIMEDOUT) + abort(); + + if (pthread_mutex_unlock(&mutex)) + abort(); + + if (pthread_mutex_destroy(&mutex)) + abort(); +#endif /* defined(__APPLE__) && defined(__MACH__) */ + + if (pthread_cond_destroy(cond)) + abort(); +} + +void js_cond_signal(js_cond_t *cond) { + if (pthread_cond_signal(cond)) + abort(); +} + +void js_cond_broadcast(js_cond_t *cond) { + if (pthread_cond_broadcast(cond)) + abort(); +} + +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { +#if defined(__APPLE__) && defined(__MACH__) + int r; + + errno = 0; + r = pthread_cond_wait(cond, mutex); + + /* Workaround for a bug in OS X at least up to 13.6 + * See https://github.com/libuv/libuv/issues/4165 + */ + if (r == EINVAL && errno == EBUSY) + return; + if (r) + abort(); +#else + if (pthread_cond_wait(cond, mutex)) + abort(); +#endif +} + +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { + int r; + struct timespec ts; + +#if !defined(__APPLE__) + timeout += js__hrtime_ns(); +#endif + + ts.tv_sec = timeout / NANOSEC; + ts.tv_nsec = timeout % NANOSEC; +#if defined(__APPLE__) && defined(__MACH__) + r = pthread_cond_timedwait_relative_np(cond, mutex, &ts); +#else + r = pthread_cond_timedwait(cond, mutex, &ts); +#endif + + if (r == 0) + return 0; + + if (r == ETIMEDOUT) + return -1; + + abort(); + + /* Pacify some compilers. */ + return -1; +} + +#endif + +#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */ + #pragma GCC visibility pop diff --git a/cutils.h b/cutils.h index 447a72a..f731eb9 100644 --- a/cutils.h +++ b/cutils.h @@ -29,8 +29,10 @@ #include #include -#if defined(_MSC_VER) +#if defined(_WIN32) #include +#endif +#if defined(_MSC_VER) #include #include #define alloca _alloca @@ -43,7 +45,10 @@ #elif defined(__FreeBSD__) #include #endif - +#if !defined(_WIN32) +#include +#include +#endif #if defined(_MSC_VER) && !defined(__clang__) # define likely(x) (x) @@ -426,4 +431,36 @@ static inline size_t js__malloc_usable_size(const void *ptr) #endif } +/* Cross-platform threading APIs. */ + +#if !defined(EMSCRIPTEN) && !defined(__wasi__) + +#if defined(_WIN32) +#define JS_ONCE_INIT INIT_ONCE_STATIC_INIT +typedef INIT_ONCE js_once_t; +typedef CRITICAL_SECTION js_mutex_t; +typedef CONDITION_VARIABLE js_cond_t; +#else +#define JS_ONCE_INIT PTHREAD_ONCE_INIT +typedef pthread_once_t js_once_t; +typedef pthread_mutex_t js_mutex_t; +typedef pthread_cond_t js_cond_t; +#endif + +void js_once(js_once_t *guard, void (*callback)(void)); + +void js_mutex_init(js_mutex_t *mutex); +void js_mutex_destroy(js_mutex_t *mutex); +void js_mutex_lock(js_mutex_t *mutex); +void js_mutex_unlock(js_mutex_t *mutex); + +void js_cond_init(js_cond_t *cond); +void js_cond_destroy(js_cond_t *cond); +void js_cond_signal(js_cond_t *cond); +void js_cond_broadcast(js_cond_t *cond); +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex); +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout); + +#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */ + #endif /* CUTILS_H */ diff --git a/gen/function_source.c b/gen/function_source.c index 7f4c013..2a6ce3e 100644 Binary files a/gen/function_source.c and b/gen/function_source.c differ diff --git a/gen/hello.c b/gen/hello.c index 3c0ed68..24ef01d 100644 Binary files a/gen/hello.c and b/gen/hello.c differ diff --git a/gen/hello_module.c b/gen/hello_module.c index 91c962c..a656f05 100644 Binary files a/gen/hello_module.c and b/gen/hello_module.c differ diff --git a/gen/repl.c b/gen/repl.c index 8fd7b24..2097e64 100644 Binary files a/gen/repl.c and b/gen/repl.c differ diff --git a/gen/test_fib.c b/gen/test_fib.c index ae31939..24ed66a 100644 Binary files a/gen/test_fib.c and b/gen/test_fib.c differ diff --git a/quickjs-atom.h b/quickjs-atom.h index c00b23d..01d1910 100644 --- a/quickjs-atom.h +++ b/quickjs-atom.h @@ -170,11 +170,9 @@ DEF(status, "status") DEF(reason, "reason") DEF(globalThis, "globalThis") DEF(bigint, "bigint") -#ifdef CONFIG_ATOMICS DEF(not_equal, "not-equal") DEF(timed_out, "timed-out") DEF(ok, "ok") -#endif DEF(toJSON, "toJSON") /* class names */ DEF(Object, "Object") diff --git a/quickjs.c b/quickjs.c index 8698067..601afc8 100644 --- a/quickjs.c +++ b/quickjs.c @@ -67,6 +67,11 @@ #define NO_TM_GMTOFF #endif +#if !defined(EMSCRIPTEN) && !defined(__wasi__) +#include "quickjs-c-atomics.h" +#define CONFIG_ATOMICS +#endif + /* dump object free */ //#define DUMP_FREE //#define DUMP_CLOSURE @@ -99,12 +104,6 @@ /* test the GC by forcing it before each object allocation */ //#define FORCE_GC_AT_MALLOC -#ifdef CONFIG_ATOMICS -#include -#include "quickjs-c-atomics.h" -#include -#endif - #define STRINGIFY_(x) #x #define STRINGIFY(x) STRINGIFY_(x) @@ -32561,7 +32560,7 @@ typedef enum BCTagEnum { BC_TAG_OBJECT_REFERENCE, } BCTagEnum; -#define BC_VERSION 9 +#define BC_VERSION 10 typedef struct BCWriterState { JSContext *ctx; @@ -51497,11 +51496,12 @@ static JSValue js_atomics_isLockFree(JSContext *ctx, typedef struct JSAtomicsWaiter { struct list_head link; BOOL linked; - pthread_cond_t cond; + js_cond_t cond; int32_t *ptr; } JSAtomicsWaiter; -static pthread_mutex_t js_atomics_mutex = PTHREAD_MUTEX_INITIALIZER; +static js_once_t js_atomics_once = JS_ONCE_INIT; +static js_mutex_t js_atomics_mutex; static struct list_head js_atomics_waiter_list = LIST_HEAD_INIT(js_atomics_waiter_list); @@ -51543,44 +51543,34 @@ static JSValue js_atomics_wait(JSContext *ctx, /* XXX: inefficient if large number of waiters, should hash on 'ptr' value */ - /* XXX: use Linux futexes when available ? */ - pthread_mutex_lock(&js_atomics_mutex); + js_mutex_lock(&js_atomics_mutex); if (size_log2 == 3) { res = *(int64_t *)ptr != v; } else { res = *(int32_t *)ptr != v; } if (res) { - pthread_mutex_unlock(&js_atomics_mutex); + js_mutex_unlock(&js_atomics_mutex); return JS_AtomToString(ctx, JS_ATOM_not_equal); } waiter = &waiter_s; waiter->ptr = ptr; - pthread_cond_init(&waiter->cond, NULL); + js_cond_init(&waiter->cond); waiter->linked = TRUE; list_add_tail(&waiter->link, &js_atomics_waiter_list); if (timeout == INT64_MAX) { - pthread_cond_wait(&waiter->cond, &js_atomics_mutex); + js_cond_wait(&waiter->cond, &js_atomics_mutex); ret = 0; } else { - /* XXX: use clock monotonic */ - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += timeout / 1000; - ts.tv_nsec += (timeout % 1000) * 1000000; - if (ts.tv_nsec >= 1000000000) { - ts.tv_nsec -= 1000000000; - ts.tv_sec++; - } - ret = pthread_cond_timedwait(&waiter->cond, &js_atomics_mutex, - &ts); + ret = js_cond_timedwait(&waiter->cond, &js_atomics_mutex, timeout * 1e6 /* to ns */); } if (waiter->linked) list_del(&waiter->link); - pthread_mutex_unlock(&js_atomics_mutex); - pthread_cond_destroy(&waiter->cond); - if (ret == ETIMEDOUT) { + js_mutex_unlock(&js_atomics_mutex); + js_cond_destroy(&waiter->cond); + if (ret == -1) { return JS_AtomToString(ctx, JS_ATOM_timed_out); } else { return JS_AtomToString(ctx, JS_ATOM_ok); @@ -51612,7 +51602,7 @@ static JSValue js_atomics_notify(JSContext *ctx, n = 0; if (abuf->shared && count > 0) { - pthread_mutex_lock(&js_atomics_mutex); + js_mutex_lock(&js_atomics_mutex); init_list_head(&waiter_list); list_for_each_safe(el, el1, &js_atomics_waiter_list) { waiter = list_entry(el, JSAtomicsWaiter, link); @@ -51627,9 +51617,9 @@ static JSValue js_atomics_notify(JSContext *ctx, } list_for_each(el, &waiter_list) { waiter = list_entry(el, JSAtomicsWaiter, link); - pthread_cond_signal(&waiter->cond); + js_cond_signal(&waiter->cond); } - pthread_mutex_unlock(&js_atomics_mutex); + js_mutex_unlock(&js_atomics_mutex); } return js_int32(n); } @@ -51654,8 +51644,15 @@ static const JSCFunctionListEntry js_atomics_obj[] = { JS_OBJECT_DEF("Atomics", js_atomics_funcs, countof(js_atomics_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; +static void js__atomics_init(void) { + js_mutex_init(&js_atomics_mutex); +} + +/* TODO(saghul) make this public and not dependent on typed arrays? */ void JS_AddIntrinsicAtomics(JSContext *ctx) { + js_once(&js_atomics_once, js__atomics_init); + /* add Atomics as autoinit object */ JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_atomics_obj, countof(js_atomics_obj)); } diff --git a/test262.conf b/test262.conf index 1763269..c004bd8 100644 --- a/test262.conf +++ b/test262.conf @@ -69,7 +69,7 @@ arraybuffer-transfer arrow-function async-functions async-iteration -Atomics=skip # disabled because of Windows <-> pthreads +Atomics Atomics.waitAsync=skip BigInt caller