From 2504823832cccc9d3930d8811ef60a5891c37495 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Tue, 2 Jun 2026 18:12:02 +0200 Subject: [PATCH] added multi-threading support in run-test262 (initial patch by bnoordhuis) - fixed unsafe thread termination in show_progress() - use the number of physical cores by default - added -T option to force the number of threads - avoid hardcoding the maximum number of threads --- Makefile | 10 +- run-test262.c | 502 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 375 insertions(+), 137 deletions(-) diff --git a/Makefile b/Makefile index 9456798..61e70a5 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,8 @@ PREFIX?=/usr/local #CONFIG_MSAN=y # use UB sanitizer #CONFIG_UBSAN=y +# use thread sanitizer +#CONFIG_TSAN=y # TEST262 bootstrap config: commit id and shallow "since" parameter TEST262_COMMIT?=5c8206929d81b2d3d727ca6aac56c18358c8d790 @@ -192,6 +194,10 @@ ifdef CONFIG_UBSAN CFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer LDFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer endif +ifdef CONFIG_TSAN +CFLAGS+=-fsanitize=thread -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=thread -fno-omit-frame-pointer +endif ifdef CONFIG_WIN32 LDEXPORT= else @@ -487,7 +493,7 @@ test2o: run-test262 time ./run-test262 -t -m -c test262o.conf test2o-update: run-test262 - ./run-test262 -t -u -c test262o.conf + ./run-test262 -t -u -c test262o.conf -T 1 endif ifeq ($(wildcard test262/features.txt),) @@ -502,7 +508,7 @@ test2: run-test262 time ./run-test262 -t -m -c test262.conf -a test2-update: run-test262 - ./run-test262 -t -u -c test262.conf -a + ./run-test262 -t -u -c test262.conf -a -T 1 test2-check: run-test262 time ./run-test262 -t -m -c test262.conf -E -a diff --git a/run-test262.c b/run-test262.c index c55d0fe..8e2a7e7 100644 --- a/run-test262.c +++ b/run-test262.c @@ -34,6 +34,11 @@ #include #include #include +#include +#include +#ifdef _WIN32 +#include +#endif #include "cutils.h" #include "list.h" @@ -45,13 +50,50 @@ typedef struct namelist_t { char **array; int count; int size; - unsigned int sorted : 1; } namelist_t; +/* per execution thread context */ +typedef struct { + pthread_mutex_t agent_mutex; + pthread_cond_t agent_cond; + /* list of Test262Agent.link */ + struct list_head agent_list; + + pthread_mutex_t report_mutex; + /* list of AgentReport.link */ + struct list_head report_list; + + int async_done; +} ThreadLocalStorage; + +typedef struct { + struct list_head link; + ThreadLocalStorage *tls; + pthread_t tid; + char *script; + JSValue broadcast_func; + BOOL broadcast_pending; + JSValue broadcast_sab; /* in the main context */ + uint8_t *broadcast_sab_buf; + size_t broadcast_sab_size; + int32_t broadcast_val; +} Test262Agent; + +typedef struct { + struct list_head link; + char *str; +} AgentReport; + namelist_t test_list; namelist_t exclude_list; namelist_t exclude_dir_list; +int nthreads; +pthread_t progress_thread; +BOOL progress_exit_request; +pthread_cond_t progress_cond; +pthread_mutex_t progress_mutex; + FILE *outfile; enum test_mode_t { TEST_DEFAULT_NOSTRICT, /* run tests as nostrict unless test is flagged as strictonly */ @@ -70,6 +112,7 @@ int stats_count; JSMemoryUsage stats_all, stats_avg, stats_min, stats_max; char *stats_min_filename; char *stats_max_filename; +pthread_mutex_t stats_mutex; int verbose; char *harness_dir; char *harness_exclude; @@ -81,13 +124,110 @@ char *error_file; FILE *error_out; char *report_filename; int update_errors; -int test_count, test_failed, test_index, test_skipped, test_excluded; -int new_errors, changed_errors, fixed_errors; -int async_done; +int slow_test_threshold; +int start_index, stop_index; +int test_excluded; +_Atomic int test_count, test_failed, test_skipped; +_Atomic int new_errors, changed_errors, fixed_errors; void warning(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); void fatal(int, const char *, ...) __attribute__((__format__(__printf__, 2, 3))); +void atomic_inc(volatile _Atomic int *p) +{ + atomic_fetch_add(p, 1); +} + +#if defined(_WIN32) +static int cpu_count(void) +{ + DWORD_PTR procmask, sysmask; + long count; + int i; + + count = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &procmask, &sysmask)) + for (i = 0; i < 8 * sizeof(procmask); i++) + count += 1 & (procmask >> i); + return count; +} +#else +/* return the number of available physical cores or -1 if not available */ +static int get_cpu_info_physical_cores(void) +{ + FILE *f; + int nb_cores, physical_id; + char line[1024], *p; + char *field, *value; + int len; + + f = fopen("/proc/cpuinfo", "rb"); + if (!f) + return -1; + nb_cores = 0; + physical_id = -1; + for(;;) { + if (fgets(line, sizeof(line), f) == NULL) + break; + len = strlen(line); + while (len > 0 && isspace(line[len - 1])) + len--; + line[len] = '\0'; + field = line; + p = line; + if (*p == '#') + continue; + while (*p != ':' && *p != '\0') + p++; + if (*p == '\0') + continue; + *p = '\0'; + p++; + while (isspace(*p)) + p++; + value = p; + + len = strlen(field); + while (len > 0 && isspace(field[len - 1])) + len--; + field[len] = '\0'; + + // printf("'%s' '%s'\n", field, value); + if (!strcmp(field, "cpu cores")) { + if (nb_cores == 0) { + nb_cores = strtol(value, NULL, 0); + } + } else if (!strcmp(field, "physical id")) { + physical_id = max_int(physical_id, strtol(value, NULL, 0)); + } + } + fclose(f); + // printf("nb_cores=%d physical_id=%d\n", nb_cores, physical_id); + if (nb_cores <= 0 || physical_id < 0) + return -1; + return nb_cores * (physical_id + 1); +} + +static int cpu_count(void) +{ + int n = get_cpu_info_physical_cores(); + if (n <= 0) + n = 1; + return n; +} +#endif /* !_WIN32 */ + +static void init_thread_local_storage(ThreadLocalStorage *tls) +{ + memset(tls, 0, sizeof(*tls)); + pthread_mutex_init(&tls->agent_mutex, NULL); + pthread_cond_init(&tls->agent_cond, NULL); + init_list_head(&tls->agent_list); + + pthread_mutex_init(&tls->report_mutex, NULL); + init_list_head(&tls->report_list); +} + void warning(const char *fmt, ...) { va_list ap; @@ -260,16 +400,13 @@ void namelist_sort(namelist_t *lp) } lp->count = count; } - lp->sorted = 1; } -int namelist_find(namelist_t *lp, const char *name) +/* the list must be sorted */ +int namelist_find(const namelist_t *lp, const char *name) { int a, b, m, cmp; - if (!lp->sorted) { - namelist_sort(lp); - } for (a = 0, b = lp->count; a < b;) { m = a + (b - a) / 2; cmp = namelist_cmp(lp->array[m], name); @@ -379,33 +516,37 @@ static void js_print_value_write(void *opaque, const char *buf, size_t len) static JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); int i; JSValueConst v; - if (outfile) { - for (i = 0; i < argc; i++) { - if (i != 0) - fputc(' ', outfile); - v = argv[i]; - if (JS_IsString(v)) { - const char *str; - size_t len; - str = JS_ToCStringLen(ctx, &len, v); - if (!str) - return JS_EXCEPTION; - if (!strcmp(str, "Test262:AsyncTestComplete")) { - async_done++; - } else if (strstart(str, "Test262:AsyncTestFailure", NULL)) { - async_done = 2; /* force an error */ - } + for (i = 0; i < argc; i++) { + if (i != 0 && outfile) + fputc(' ', outfile); + v = argv[i]; + if (JS_IsString(v)) { + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v); + if (!str) + return JS_EXCEPTION; + if (!strcmp(str, "Test262:AsyncTestComplete")) { + tls->async_done++; + } else if (strstart(str, "Test262:AsyncTestFailure", NULL)) { + tls->async_done = 2; /* force an error */ + } + if (outfile) { fwrite(str, 1, len, outfile); - JS_FreeCString(ctx, str); - } else { + } + JS_FreeCString(ctx, str); + } else { + if (outfile) { JS_PrintValue(ctx, js_print_value_write, outfile, v, NULL); } } - fputc('\n', outfile); } + if (outfile) + fputc('\n', outfile); return JS_UNDEFINED; } @@ -430,40 +571,13 @@ static JSValue js_evalScript(JSContext *ctx, JSValue this_val, return ret; } -#include - -typedef struct { - struct list_head link; - pthread_t tid; - char *script; - JSValue broadcast_func; - BOOL broadcast_pending; - JSValue broadcast_sab; /* in the main context */ - uint8_t *broadcast_sab_buf; - size_t broadcast_sab_size; - int32_t broadcast_val; -} Test262Agent; - -typedef struct { - struct list_head link; - char *str; -} AgentReport; - 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; -/* list of Test262Agent.link */ -static struct list_head agent_list = LIST_HEAD_INIT(agent_list); - -static pthread_mutex_t report_mutex = PTHREAD_MUTEX_INITIALIZER; -/* list of AgentReport.link */ -static struct list_head report_list = LIST_HEAD_INIT(report_list); - static void *agent_start(void *arg) { Test262Agent *agent = arg; + ThreadLocalStorage *tls = agent->tls; JSRuntime *rt; JSContext *ctx; JSValue ret_val; @@ -473,6 +587,7 @@ static void *agent_start(void *arg) if (rt == NULL) { fatal(1, "JS_NewRuntime failure"); } + JS_SetRuntimeOpaque(rt, tls); ctx = JS_NewContext(rt); if (ctx == NULL) { JS_FreeRuntime(rt); @@ -502,15 +617,15 @@ static void *agent_start(void *arg) } else { JSValue args[2]; - pthread_mutex_lock(&agent_mutex); + pthread_mutex_lock(&tls->agent_mutex); while (!agent->broadcast_pending) { - pthread_cond_wait(&agent_cond, &agent_mutex); + pthread_cond_wait(&tls->agent_cond, &tls->agent_mutex); } agent->broadcast_pending = FALSE; - pthread_cond_signal(&agent_cond); + pthread_cond_signal(&tls->agent_cond); - pthread_mutex_unlock(&agent_mutex); + pthread_mutex_unlock(&tls->agent_mutex); args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf, agent->broadcast_sab_size, @@ -538,6 +653,7 @@ static void *agent_start(void *arg) static JSValue js_agent_start(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); const char *script; Test262Agent *agent; pthread_attr_t attr; @@ -550,11 +666,12 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val, return JS_EXCEPTION; agent = malloc(sizeof(*agent)); memset(agent, 0, sizeof(*agent)); + agent->tls = tls; agent->broadcast_func = JS_UNDEFINED; agent->broadcast_sab = JS_UNDEFINED; agent->script = strdup(script); JS_FreeCString(ctx, script); - list_add_tail(&agent->link, &agent_list); + list_add_tail(&agent->link, &tls->agent_list); pthread_attr_init(&attr); // musl libc gives threads 80 kb stacks, much smaller than // JS_DEFAULT_STACK_SIZE (256 kb) @@ -566,10 +683,11 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val, static void js_agent_free(JSContext *ctx) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); struct list_head *el, *el1; Test262Agent *agent; - list_for_each_safe(el, el1, &agent_list) { + list_for_each_safe(el, el1, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); pthread_join(agent->tid, NULL); JS_FreeValue(ctx, agent->broadcast_sab); @@ -588,11 +706,11 @@ static JSValue js_agent_leaving(JSContext *ctx, JSValue this_val, return JS_UNDEFINED; } -static BOOL is_broadcast_pending(void) +static BOOL is_broadcast_pending(ThreadLocalStorage *tls) { struct list_head *el; Test262Agent *agent; - list_for_each(el, &agent_list) { + list_for_each(el, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); if (agent->broadcast_pending) return TRUE; @@ -603,6 +721,7 @@ static BOOL is_broadcast_pending(void) static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); JSValueConst sab = argv[0]; struct list_head *el; Test262Agent *agent; @@ -621,8 +740,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); - list_for_each(el, &agent_list) { + pthread_mutex_lock(&tls->agent_mutex); + list_for_each(el, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); agent->broadcast_pending = TRUE; /* the shared array buffer is used by the thread, so increment @@ -632,12 +751,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); + pthread_cond_broadcast(&tls->agent_cond); - while (is_broadcast_pending()) { - pthread_cond_wait(&agent_cond, &agent_mutex); + while (is_broadcast_pending(tls)) { + pthread_cond_wait(&tls->agent_cond, &tls->agent_mutex); } - pthread_mutex_unlock(&agent_mutex); + pthread_mutex_unlock(&tls->agent_mutex); return JS_UNDEFINED; } @@ -680,17 +799,18 @@ static JSValue js_agent_monotonicNow(JSContext *ctx, JSValue this_val, static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); AgentReport *rep; JSValue ret; - pthread_mutex_lock(&report_mutex); - if (list_empty(&report_list)) { + pthread_mutex_lock(&tls->report_mutex); + if (list_empty(&tls->report_list)) { rep = NULL; } else { - rep = list_entry(report_list.next, AgentReport, link); + rep = list_entry(tls->report_list.next, AgentReport, link); list_del(&rep->link); } - pthread_mutex_unlock(&report_mutex); + pthread_mutex_unlock(&tls->report_mutex); if (rep) { ret = JS_NewString(ctx, rep->str); free(rep->str); @@ -704,6 +824,7 @@ static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val, static JSValue js_agent_report(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); const char *str; AgentReport *rep; @@ -714,9 +835,9 @@ static JSValue js_agent_report(JSContext *ctx, JSValue this_val, rep->str = strdup(str); JS_FreeCString(ctx, str); - pthread_mutex_lock(&report_mutex); - list_add_tail(&rep->link, &report_list); - pthread_mutex_unlock(&report_mutex); + pthread_mutex_lock(&tls->report_mutex); + list_add_tail(&rep->link, &tls->report_list); + pthread_mutex_unlock(&tls->report_mutex); return JS_UNDEFINED; } @@ -939,7 +1060,7 @@ void update_exclude_dirs(void) char *name; int i, j, count; - /* split directpries from exclude_list */ + /* split directories from exclude_list */ for (count = i = 0; i < ep->count; i++) { name = ep->array[i]; if (has_suffix(name, "/")) { @@ -1237,6 +1358,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, const char *error_type, FILE *outfile, int eval_flags, int is_async) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); JSValue res_val, exception_val; int ret, error_line, pos, pos_line; BOOL is_error, has_error_line, ret_promise; @@ -1250,7 +1372,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, /* a module evaluation returns a promise */ ret_promise = ((eval_flags & JS_EVAL_TYPE_MODULE) != 0); - async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */ + tls->async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */ res_val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); @@ -1269,7 +1391,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, } else if (ret == 0) { if (is_async) { /* test if the test called $DONE() once */ - if (async_done != 1) { + if (tls->async_done != 1) { res_val = JS_ThrowTypeError(ctx, "$DONE() not called"); } else { res_val = JS_UNDEFINED; @@ -1537,6 +1659,8 @@ static char *get_option(char **pp, int *state) void update_stats(JSRuntime *rt, const char *filename) { JSMemoryUsage stats; JS_ComputeMemoryUsage(rt, &stats); + + pthread_mutex_lock(&stats_mutex); if (stats_count++ == 0) { stats_avg = stats_all = stats_min = stats_max = stats; stats_min_filename = strdup(filename); @@ -1579,9 +1703,11 @@ void update_stats(JSRuntime *rt, const char *filename) { update(fast_array_elements); } #undef update + pthread_mutex_unlock(&stats_mutex); } -int run_test_buf(const char *filename, const char *harness, namelist_t *ip, +int run_test_buf(ThreadLocalStorage *tls, + const char *filename, const char *harness, namelist_t *ip, char *buf, size_t buf_len, const char* error_type, int eval_flags, BOOL is_negative, BOOL is_async, BOOL can_block) @@ -1594,6 +1720,7 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip, if (rt == NULL) { fatal(1, "JS_NewRuntime failure"); } + JS_SetRuntimeOpaque(rt, tls); ctx = JS_NewContext(rt); if (ctx == NULL) { JS_FreeRuntime(rt); @@ -1626,9 +1753,9 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip, JS_FreeContext(ctx); JS_FreeRuntime(rt); - test_count++; + atomic_inc(&test_count); if (ret) { - test_failed++; + atomic_inc(&test_failed); if (outfile) { /* do not output a failure number to minimize diff */ fprintf(outfile, " FAILED\n"); @@ -1637,7 +1764,7 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip, return ret; } -int run_test(const char *filename, int index) +int run_test(ThreadLocalStorage *tls, const char *filename, int index) { char harnessbuf[1024]; char *harness; @@ -1843,7 +1970,7 @@ int run_test(const char *filename, int index) } if (skip || use_strict + use_nostrict == 0) { - test_skipped++; + atomic_inc(&test_skipped); ret = -2; } else { clock_t clocks; @@ -1856,12 +1983,12 @@ int run_test(const char *filename, int index) clocks = clock(); ret = 0; if (use_nostrict) { - ret = run_test_buf(filename, harness, ip, buf, buf_len, + ret = run_test_buf(tls, filename, harness, ip, buf, buf_len, error_type, eval_flags, is_negative, is_async, can_block); } if (use_strict) { - ret |= run_test_buf(filename, harness, ip, buf, buf_len, + ret |= run_test_buf(tls, filename, harness, ip, buf, buf_len, error_type, eval_flags | JS_EVAL_FLAG_STRICT, is_negative, is_async, can_block); } @@ -1879,7 +2006,8 @@ int run_test(const char *filename, int index) } /* run a test when called by test262-harness+eshost */ -int run_test262_harness_test(const char *filename, BOOL is_module, BOOL can_block) +int run_test262_harness_test(ThreadLocalStorage *tls, + const char *filename, BOOL is_module, BOOL can_block) { JSRuntime *rt; JSContext *ctx; @@ -1894,6 +2022,7 @@ int run_test262_harness_test(const char *filename, BOOL is_module, BOOL can_bloc if (rt == NULL) { fatal(1, "JS_NewRuntime failure"); } + JS_SetRuntimeOpaque(rt, tls); ctx = JS_NewContext(rt); if (ctx == NULL) { JS_FreeRuntime(rt); @@ -1954,70 +2083,109 @@ int run_test262_harness_test(const char *filename, BOOL is_module, BOOL can_bloc return ret_code; } -clock_t last_clock; +static int pthread_cond_timedwait2(pthread_cond_t *cond, pthread_mutex_t *mutex, int timeout) +{ + struct timespec ts; + + 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++; + } + return pthread_cond_timedwait(cond, mutex, &ts); +} + +void *show_progress(void *opaque) +{ + int test_skipped1, test_failed1, test_count1; + + pthread_mutex_lock(&progress_mutex); + for(;;) { + pthread_cond_timedwait2(&progress_cond, &progress_mutex, 50); + + test_failed1 = atomic_load(&test_failed); + test_count1 = atomic_load(&test_count); + test_skipped1 = atomic_load(&test_skipped); -void show_progress(int force) { - clock_t t = clock(); - if (force || !last_clock || (t - last_clock) > CLOCKS_PER_SEC / 20) { - last_clock = t; if (compact) { static int last_test_skipped; static int last_test_failed; static int dots; char c = '.'; - if (test_skipped > last_test_skipped) + + if (test_skipped1 > last_test_skipped) c = '-'; - if (test_failed > last_test_failed) + if (test_failed1 > last_test_failed) c = '!'; - last_test_skipped = test_skipped; - last_test_failed = test_failed; + last_test_skipped = test_skipped1; + last_test_failed = test_failed1; + fputc(c, stderr); - if (force || ++dots % 60 == 0) { + if (progress_exit_request || ++dots % 60 == 0) { fprintf(stderr, " %d/%d/%d\n", - test_failed, test_count, test_skipped); + test_failed1, test_count1, test_skipped1); } } else { /* output progress indicator: erase end of line and return to col 0 */ fprintf(stderr, "%d/%d/%d\033[K\r", - test_failed, test_count, test_skipped); + test_failed1, test_count1, test_skipped1); } fflush(stderr); + if (progress_exit_request) + break; } + pthread_mutex_unlock(&progress_mutex); + return NULL; } -static int slow_test_threshold; +enum { INCLUDE, EXCLUDE, SKIP }; -void run_test_dir_list(namelist_t *lp, int start_index, int stop_index) +int include_exclude_or_skip(int i) // naming is hard... { - int i; + if (namelist_find(&exclude_list, test_list.array[i]) >= 0) + return EXCLUDE; + if (i < start_index) + return SKIP; + if (stop_index >= 0 && i > stop_index) + return SKIP; + return INCLUDE; +} - namelist_sort(lp); - for (i = 0; i < lp->count; i++) { +typedef struct { + pthread_t tid; + int thread_index; +} RunTestDirThread; + +void *run_test_dir_list(void *opaque) +{ + RunTestDirThread *th = opaque; + ThreadLocalStorage tls_s, *tls = &tls_s; + namelist_t *lp = &test_list; + int i; + + init_thread_local_storage(tls); + + for (i = th->thread_index; i < lp->count; i += nthreads) { const char *p = lp->array[i]; - if (namelist_find(&exclude_list, p) >= 0) { - test_excluded++; - } else if (test_index < start_index) { - test_skipped++; - } else if (stop_index >= 0 && test_index > stop_index) { - test_skipped++; + int ti; + if (INCLUDE != include_exclude_or_skip(i)) + continue; + + if (slow_test_threshold != 0) { + ti = get_clock_ms(); } else { - int ti; - if (slow_test_threshold != 0) { - ti = get_clock_ms(); - } else { - ti = 0; - } - run_test(p, test_index); - if (slow_test_threshold != 0) { - ti = get_clock_ms() - ti; - if (ti >= slow_test_threshold) - fprintf(stderr, "\n%s (%d ms)\n", p, ti); - } - show_progress(FALSE); + ti = 0; + } + run_test(tls, p, i); + if (slow_test_threshold != 0) { + ti = get_clock_ms() - ti; + if (ti >= slow_test_threshold) + fprintf(stderr, "\n%s (%d ms)\n", p, ti); } - test_index++; } - show_progress(TRUE); + return NULL; } void help(void) @@ -2035,7 +2203,8 @@ void help(void) "-t show timings\n" "-u update error file\n" "-v verbose: output error messages\n" - "-T duration display tests taking more than 'duration' ms\n" + "-D duration display tests taking more than 'duration' ms\n" + "-T threads number of parallel threads\n" "-c file read configuration from 'file'\n" "-d dir run all test files in directory tree 'dir'\n" "-e file load the known errors from 'file'\n" @@ -2056,7 +2225,8 @@ char *get_opt_arg(const char *option, char *arg) int main(int argc, char **argv) { - int optind, start_index, stop_index; + ThreadLocalStorage tls_s, *tls = &tls_s; + int optind; BOOL is_dir_list; BOOL only_check_errors = FALSE; const char *filename; @@ -2066,6 +2236,9 @@ int main(int argc, char **argv) BOOL can_block = TRUE; BOOL count_skipped_features = FALSE; clock_t clocks; + + init_thread_local_storage(tls); + pthread_mutex_init(&stats_mutex, NULL); #if !defined(_WIN32) compact = !isatty(STDERR_FILENO); @@ -2079,7 +2252,7 @@ int main(int argc, char **argv) if (*arg != '-') break; optind++; - if (strstr("-c -d -e -x -f -r -E -T", arg)) + if (strstr("-c -d -e -x -f -r -E -D -T", arg)) optind++; if (strstr("-d -f", arg)) ignore = "testdir"; // run only the tests from -d or -f @@ -2126,8 +2299,10 @@ int main(int argc, char **argv) report_filename = get_opt_arg(arg, argv[optind++]); } else if (str_equal(arg, "-E")) { only_check_errors = TRUE; - } else if (str_equal(arg, "-T")) { + } else if (str_equal(arg, "-D")) { slow_test_threshold = atoi(get_opt_arg(arg, argv[optind++])); + } else if (str_equal(arg, "-T")) { + nthreads = atoi(get_opt_arg(arg, argv[optind++])); } else if (str_equal(arg, "-N")) { is_test262_harness = TRUE; } else if (str_equal(arg, "--module")) { @@ -2146,9 +2321,18 @@ int main(int argc, char **argv) help(); if (is_test262_harness) { - return run_test262_harness_test(argv[optind], is_module, can_block); + return run_test262_harness_test(tls, argv[optind], is_module, can_block); } + if (nthreads == 0) { + nthreads = cpu_count(); + if (nthreads >= 8) { + // minus one to not (over)commit the system completely + nthreads--; + } + } + nthreads = max_int(nthreads, 1); + error_out = stdout; if (error_filename) { error_file = load_file(error_filename, NULL); @@ -2179,10 +2363,14 @@ int main(int argc, char **argv) } if (is_dir_list) { + RunTestDirThread *threads; + int i; + if (optind < argc && !isdigit((unsigned char)argv[optind][0])) { filename = argv[optind++]; namelist_load(&test_list, filename); } + start_index = 0; stop_index = -1; if (optind < argc) { @@ -2191,7 +2379,8 @@ int main(int argc, char **argv) stop_index = atoi(argv[optind++]); } } - if (!report_filename || str_equal(report_filename, "none")) { + /* XXX: could reorder the report and the errors when nthreads > 1 */ + if (!report_filename || str_equal(report_filename, "none") || nthreads > 1) { outfile = NULL; } else if (str_equal(report_filename, "-")) { outfile = stdout; @@ -2201,7 +2390,50 @@ int main(int argc, char **argv) perror_exit(1, report_filename); } } - run_test_dir_list(&test_list, start_index, stop_index); + + // exclude_dir_list has already been sorted by update_exclude_dirs() + namelist_sort(&test_list); + namelist_sort(&exclude_list); + + for (i = 0; i < test_list.count; i++) { + switch (include_exclude_or_skip(i)) { + case EXCLUDE: + test_excluded++; + break; + case SKIP: + test_skipped++; + break; + } + } + + pthread_cond_init(&progress_cond, NULL); + pthread_mutex_init(&progress_mutex, NULL); + pthread_create(&progress_thread, NULL, show_progress, NULL); + + threads = malloc(sizeof(threads[0]) * nthreads); + for (i = 0; i < nthreads; i++) { + RunTestDirThread *th = &threads[i]; + pthread_attr_t attr; + + th->thread_index = i; + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 2 << 20); // 2 MB, glibc default + pthread_create(&th->tid, &attr, run_test_dir_list, th); + pthread_attr_destroy(&attr); + } + for (i = 0; i < nthreads; i++) + pthread_join(threads[i].tid, NULL); + free(threads); + + pthread_mutex_lock(&progress_mutex); + progress_exit_request = TRUE; + pthread_cond_signal(&progress_cond); + pthread_mutex_unlock(&progress_mutex); + pthread_join(progress_thread, NULL); + + pthread_mutex_destroy(&progress_mutex); + pthread_cond_destroy(&progress_cond); if (outfile && outfile != stdout) { fclose(outfile); @@ -2210,7 +2442,7 @@ int main(int argc, char **argv) } else { outfile = stdout; while (optind < argc) { - run_test(argv[optind++], -1); + run_test(tls, argv[optind++], -1); } } @@ -2269,7 +2501,7 @@ int main(int argc, char **argv) } fprintf(stderr, "\n"); if (show_timings) - fprintf(stderr, "Total time: %.3fs\n", (double)clocks / CLOCKS_PER_SEC); + fprintf(stderr, "Total user time: %.3fs (nthreads=%d)\n", (double)clocks / CLOCKS_PER_SEC, nthreads); } if (error_out && error_out != stdout) {