From 90b201578c9974c98902a887ede42f85f77e61ee Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 3 Nov 2023 11:57:56 +0100 Subject: [PATCH 1/2] Replace pthreads with C11 threading support This should make it feasible to build quickjs on Windows with VS 2022. Fixes: https://github.com/quickjs-ng/quickjs/issues/1 --- .github/workflows/ci.yml | 15 +++++++++ Makefile | 4 +-- qjsc.c | 1 - quickjs-libc.c | 33 +++++++++--------- quickjs.c | 45 ++++++++++++++++--------- release.sh | 4 --- run-test262.c | 72 ++++++++++++++++++++++++++-------------- 7 files changed, 108 insertions(+), 66 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2e79fd40..0e779141b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,3 +79,18 @@ jobs: - name: test run: | make -j$(getconf _NPROCESSORS_ONLN) CONFIG_WERROR=y CONFIG_UBSAN=y test + + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: build + run: | + make CONFIG_WIN32=y CONFIG_WERROR=y + - name: stats + run: | + make CONFIG_WIN32=y CONFIG_WERROR=y qjs + ./qjs.exe -qd + - name: test + run: | + make CONFIG_WIN32=y CONFIG_WERROR=y test diff --git a/Makefile b/Makefile index cafd3fd60..770c2697d 100644 --- a/Makefile +++ b/Makefile @@ -189,10 +189,10 @@ QJS_LIB_OBJS+=$(OBJDIR)/libbf.o QJS_OBJS+=$(OBJDIR)/qjscalc.o endif -HOST_LIBS=-lm -ldl -lpthread +HOST_LIBS=-lm -ldl LIBS=-lm ifndef CONFIG_WIN32 -LIBS+=-ldl -lpthread +LIBS+=-ldl endif LIBS+=$(EXTRA_LIBS) diff --git a/qjsc.c b/qjsc.c index b9f1e4cd5..4ab6bcc2c 100644 --- a/qjsc.c +++ b/qjsc.c @@ -451,7 +451,6 @@ static int output_executable(const char *out_filename, const char *cfilename, *arg++ = libjsname; *arg++ = "-lm"; *arg++ = "-ldl"; - *arg++ = "-lpthread"; *arg = NULL; if (verbose) { diff --git a/quickjs-libc.c b/quickjs-libc.c index 12dc798ee..eff16c5c8 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -63,7 +63,7 @@ typedef sig_t sighandler_t; #endif #ifdef USE_WORKER -#include +#include #include #endif @@ -106,7 +106,7 @@ typedef struct { typedef struct { int ref_count; #ifdef USE_WORKER - pthread_mutex_t mutex; + mtx_t mutex; #endif struct list_head msg_queue; /* list of JSWorkerMessage.link */ int read_fd; @@ -2166,7 +2166,7 @@ static int handle_posted_message(JSRuntime *rt, JSContext *ctx, JSWorkerMessage *msg; JSValue obj, data_obj, func, retval; - pthread_mutex_lock(&ps->mutex); + mtx_lock(&ps->mutex); if (!list_empty(&ps->msg_queue)) { el = ps->msg_queue.next; msg = list_entry(el, JSWorkerMessage, link); @@ -2186,7 +2186,7 @@ static int handle_posted_message(JSRuntime *rt, JSContext *ctx, } } - pthread_mutex_unlock(&ps->mutex); + mtx_unlock(&ps->mutex); data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); @@ -2216,7 +2216,7 @@ static int handle_posted_message(JSRuntime *rt, JSContext *ctx, } ret = 1; } else { - pthread_mutex_unlock(&ps->mutex); + mtx_unlock(&ps->mutex); ret = 0; } return ret; @@ -3195,7 +3195,7 @@ static JSWorkerMessagePipe *js_new_message_pipe(void) } ps->ref_count = 1; init_list_head(&ps->msg_queue); - pthread_mutex_init(&ps->mutex, NULL); + mtx_init(&ps->mutex, mtx_timed); ps->read_fd = pipe_fds[0]; ps->write_fd = pipe_fds[1]; return ps; @@ -3235,7 +3235,7 @@ static void js_free_message_pipe(JSWorkerMessagePipe *ps) msg = list_entry(el, JSWorkerMessage, link); js_free_message(msg); } - pthread_mutex_destroy(&ps->mutex); + mtx_destroy(&ps->mutex); close(ps->read_fd); close(ps->write_fd); free(ps); @@ -3268,7 +3268,7 @@ static JSClassDef js_worker_class = { .finalizer = js_worker_finalizer, }; -static void *worker_func(void *opaque) +static int worker_func(void *opaque) { WorkerFuncArgs *args = opaque; JSRuntime *rt; @@ -3311,7 +3311,7 @@ static void *worker_func(void *opaque) JS_FreeContext(ctx); js_std_free_handlers(rt); JS_FreeRuntime(rt); - return NULL; + return 0; } static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, @@ -3351,8 +3351,7 @@ static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, { JSRuntime *rt = JS_GetRuntime(ctx); WorkerFuncArgs *args = NULL; - pthread_t tid; - pthread_attr_t attr; + thrd_t tid; JSValue obj = JS_UNDEFINED; int ret; const char *filename = NULL, *basename; @@ -3399,15 +3398,13 @@ static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, if (JS_IsException(obj)) goto fail; - pthread_attr_init(&attr); /* no join at the end */ - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - ret = pthread_create(&tid, &attr, worker_func, args); - pthread_attr_destroy(&attr); - if (ret != 0) { + ret = thrd_create(&tid, worker_func, args); + if (ret != thrd_success) { JS_ThrowTypeError(ctx, "could not create worker"); goto fail; } + thrd_detach(tid); JS_FreeCString(ctx, basename); JS_FreeCString(ctx, filename); return obj; @@ -3475,7 +3472,7 @@ static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, } ps = worker->send_pipe; - pthread_mutex_lock(&ps->mutex); + mtx_lock(&ps->mutex); /* indicate that data is present */ if (list_empty(&ps->msg_queue)) { uint8_t ch = '\0'; @@ -3489,7 +3486,7 @@ static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, } } list_add_tail(&msg->link, &ps->msg_queue); - pthread_mutex_unlock(&ps->mutex); + mtx_unlock(&ps->mutex); return JS_UNDEFINED; fail: if (msg) { diff --git a/quickjs.c b/quickjs.c index 781358ba9..25b6be375 100644 --- a/quickjs.c +++ b/quickjs.c @@ -67,6 +67,12 @@ #define CONFIG_PRINTF_RNDN #endif +/* define to include Atomics.* operations which depend on the OS + threads */ +#if !defined(EMSCRIPTEN) +#define CONFIG_ATOMICS +#endif + #if !defined(EMSCRIPTEN) /* enable stack limitation */ #define CONFIG_STACK_CHECK @@ -104,7 +110,7 @@ //#define FORCE_GC_AT_MALLOC #ifdef CONFIG_ATOMICS -#include +#include #include #include #endif @@ -53932,14 +53938,21 @@ static JSValue js_atomics_isLockFree(JSContext *ctx, typedef struct JSAtomicsWaiter { struct list_head link; BOOL linked; - pthread_cond_t cond; + cnd_t cond; int32_t *ptr; } JSAtomicsWaiter; -static pthread_mutex_t js_atomics_mutex = PTHREAD_MUTEX_INITIALIZER; +static once_flag js_atomics_mutex_init_flag = ONCE_FLAG_INIT; +static mtx_t js_atomics_mutex; static struct list_head js_atomics_waiter_list = LIST_HEAD_INIT(js_atomics_waiter_list); +static void js_atomics_mutex_init(void) +{ + if (mtx_init(&js_atomics_mutex, mtx_timed)) + abort(); +} + static JSValue js_atomics_wait(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv) @@ -53981,43 +53994,42 @@ 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); + call_once(&js_atomics_mutex_init_flag, js_atomics_mutex_init); + mtx_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); + mtx_unlock(&js_atomics_mutex); return JS_AtomToString(ctx, JS_ATOM_not_equal); } waiter = &waiter_s; waiter->ptr = ptr; - pthread_cond_init(&waiter->cond, NULL); + cnd_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); + cnd_wait(&waiter->cond, &js_atomics_mutex); ret = 0; } else { /* XXX: use clock monotonic */ - clock_gettime(CLOCK_REALTIME, &ts); + timespec_get(&ts, TIME_UTC); 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 = cnd_timedwait(&waiter->cond, &js_atomics_mutex, &ts); } if (waiter->linked) list_del(&waiter->link); - pthread_mutex_unlock(&js_atomics_mutex); - pthread_cond_destroy(&waiter->cond); + mtx_unlock(&js_atomics_mutex); + cnd_destroy(&waiter->cond); if (ret == ETIMEDOUT) { return JS_AtomToString(ctx, JS_ATOM_timed_out); } else { @@ -54050,7 +54062,8 @@ static JSValue js_atomics_notify(JSContext *ctx, n = 0; if (abuf->shared && count > 0) { - pthread_mutex_lock(&js_atomics_mutex); + call_once(&js_atomics_mutex_init_flag, js_atomics_mutex_init); + mtx_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); @@ -54065,9 +54078,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); + cnd_signal(&waiter->cond); } - pthread_mutex_unlock(&js_atomics_mutex); + mtx_unlock(&js_atomics_mutex); } return JS_NewInt32(ctx, n); } diff --git a/release.sh b/release.sh index 26fba1b03..9dc91cc25 100755 --- a/release.sh +++ b/release.sh @@ -45,7 +45,6 @@ if echo $release_list | grep -w -q win_binary ; then # win64 -dlldir=/usr/x86_64-w64-mingw32/sys-root/mingw/bin cross_prefix="x86_64-w64-mingw32-" d="quickjs-win-x86_64-${version}" outdir="/tmp/${d}" @@ -56,7 +55,6 @@ mkdir -p $outdir make CONFIG_WIN32=y qjs.exe cp qjs.exe $outdir ${cross_prefix}strip $outdir/qjs.exe -cp $dlldir/libwinpthread-1.dll $outdir ( cd /tmp/$d && rm -f ../${d}.zip && zip -r ../${d}.zip . ) @@ -64,7 +62,6 @@ make CONFIG_WIN32=y clean # win32 -dlldir=/usr/i686-w64-mingw32/sys-root/mingw/bin cross_prefix="i686-w64-mingw32-" d="quickjs-win-i686-${version}" outdir="/tmp/${d}" @@ -78,7 +75,6 @@ make CONFIG_WIN32=y clean make CONFIG_WIN32=y CONFIG_M32=y qjs.exe cp qjs.exe $outdir ${cross_prefix}strip $outdir/qjs.exe -cp $dlldir/libwinpthread-1.dll $outdir ( cd /tmp/$d && rm -f ../${d}.zip && zip -r ../${d}.zip . ) diff --git a/run-test262.c b/run-test262.c index d31f723ac..439f764b8 100644 --- a/run-test262.c +++ b/run-test262.c @@ -419,11 +419,11 @@ static JSValue js_evalScript(JSContext *ctx, JSValue this_val, #ifdef CONFIG_AGENT -#include +#include typedef struct { struct list_head link; - pthread_t tid; + thrd_t tid; char *script; JSValue broadcast_func; BOOL broadcast_pending; @@ -441,16 +441,29 @@ typedef struct { static JSValue add_helpers1(JSContext *ctx); static void add_helpers(JSContext *ctx); -static pthread_mutex_t agent_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t agent_cond = PTHREAD_COND_INITIALIZER; +static once_flag agent_init_flag = ONCE_FLAG_INIT; +static mtx_t agent_mutex; +static cnd_t agent_cond; /* list of Test262Agent.link */ static struct list_head agent_list = LIST_HEAD_INIT(agent_list); -static pthread_mutex_t report_mutex = PTHREAD_MUTEX_INITIALIZER; +static mtx_t report_mutex; /* list of AgentReport.link */ static struct list_head report_list = LIST_HEAD_INIT(report_list); -static void *agent_start(void *arg) +static void agent_init(void) +{ + if (mtx_init(&agent_mutex, mtx_timed)) + abort(); + + if (cnd_init(&agent_cond)) + abort(); + + if (mtx_init(&report_mutex, mtx_timed)) + abort(); +} + +static int agent_start(void *arg) { Test262Agent *agent = arg; JSRuntime *rt; @@ -470,7 +483,7 @@ static void *agent_start(void *arg) JS_SetContextOpaque(ctx, agent); JS_SetRuntimeInfo(rt, "agent"); JS_SetCanBlock(rt, TRUE); - + add_helpers(ctx); ret_val = JS_Eval(ctx, agent->script, strlen(agent->script), "", JS_EVAL_TYPE_GLOBAL); @@ -479,7 +492,8 @@ static void *agent_start(void *arg) if (JS_IsException(ret_val)) js_std_dump_error(ctx); JS_FreeValue(ctx, ret_val); - + + call_once(&agent_init_flag, &agent_init); for(;;) { JSContext *ctx1; ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); @@ -492,15 +506,15 @@ static void *agent_start(void *arg) } else { JSValue args[2]; - pthread_mutex_lock(&agent_mutex); + mtx_lock(&agent_mutex); while (!agent->broadcast_pending) { - pthread_cond_wait(&agent_cond, &agent_mutex); + cnd_wait(&agent_cond, &agent_mutex); } agent->broadcast_pending = FALSE; - pthread_cond_signal(&agent_cond); + cnd_signal(&agent_cond); - pthread_mutex_unlock(&agent_mutex); + mtx_unlock(&agent_mutex); args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf, agent->broadcast_sab_size, @@ -522,7 +536,7 @@ static void *agent_start(void *arg) JS_FreeContext(ctx); JS_FreeRuntime(rt); - return NULL; + return 0; } static JSValue js_agent_start(JSContext *ctx, JSValue this_val, @@ -544,7 +558,8 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val, agent->script = strdup(script); JS_FreeCString(ctx, script); list_add_tail(&agent->link, &agent_list); - pthread_create(&agent->tid, NULL, agent_start, agent); + if (thrd_success != thrd_create(&agent->tid, agent_start, agent)) + abort(); return JS_UNDEFINED; } @@ -555,7 +570,7 @@ static void js_agent_free(JSContext *ctx) list_for_each_safe(el, el1, &agent_list) { agent = list_entry(el, Test262Agent, link); - pthread_join(agent->tid, NULL); + thrd_join(agent->tid, NULL); JS_FreeValue(ctx, agent->broadcast_sab); list_del(&agent->link); free(agent); @@ -605,7 +620,8 @@ static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, /* broadcast the values and wait until all agents have started calling their callbacks */ - pthread_mutex_lock(&agent_mutex); + call_once(&agent_init_flag, agent_init); + mtx_lock(&agent_mutex); list_for_each(el, &agent_list) { agent = list_entry(el, Test262Agent, link); agent->broadcast_pending = TRUE; @@ -616,12 +632,12 @@ static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, agent->broadcast_sab_size = buf_size; agent->broadcast_val = val; } - pthread_cond_broadcast(&agent_cond); + cnd_broadcast(&agent_cond); while (is_broadcast_pending()) { - pthread_cond_wait(&agent_cond, &agent_mutex); + cnd_wait(&agent_cond, &agent_mutex); } - pthread_mutex_unlock(&agent_mutex); + mtx_unlock(&agent_mutex); return JS_UNDEFINED; } @@ -651,7 +667,11 @@ static JSValue js_agent_sleep(JSContext *ctx, JSValue this_val, static int64_t get_clock_ms(void) { struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); + /* TODO(bnoordhuis) Should use a monotonic clock but we'd have to + * be really unlucky to actually observe a backwards jump in time + * during a test run so I'm calling it good enough. + */ + timespec_get(&ts, TIME_UTC); return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); } @@ -667,14 +687,15 @@ static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val, AgentReport *rep; JSValue ret; - pthread_mutex_lock(&report_mutex); + call_once(&agent_init_flag, &agent_init); + mtx_lock(&report_mutex); if (list_empty(&report_list)) { rep = NULL; } else { rep = list_entry(report_list.next, AgentReport, link); list_del(&rep->link); } - pthread_mutex_unlock(&report_mutex); + mtx_unlock(&report_mutex); if (rep) { ret = JS_NewString(ctx, rep->str); free(rep->str); @@ -697,10 +718,11 @@ static JSValue js_agent_report(JSContext *ctx, JSValue this_val, rep = malloc(sizeof(*rep)); rep->str = strdup(str); JS_FreeCString(ctx, str); - - pthread_mutex_lock(&report_mutex); + + call_once(&agent_init_flag, &agent_init); + mtx_lock(&report_mutex); list_add_tail(&rep->link, &report_list); - pthread_mutex_unlock(&report_mutex); + mtx_unlock(&report_mutex); return JS_UNDEFINED; } From 9d44ef4ac48fe845116bc26007b1329731a586e2 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 3 Nov 2023 12:51:57 +0100 Subject: [PATCH 2/2] fixup! compile with -std=c11 --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 770c2697d..66d6ff06f 100644 --- a/Makefile +++ b/Makefile @@ -113,6 +113,7 @@ DEFINES+=-D__USE_MINGW_ANSI_STDIO # for standard snprintf behavior endif CFLAGS+=$(DEFINES) +CFLAGS+=-std=c11 CFLAGS_DEBUG=$(CFLAGS) -O0 CFLAGS_SMALL=$(CFLAGS) -Os CFLAGS_OPT=$(CFLAGS) -O2