diff options
Diffstat (limited to 'src/engine/thread.c')
| -rw-r--r-- | src/engine/thread.c | 280 |
1 files changed, 275 insertions, 5 deletions
diff --git a/src/engine/thread.c b/src/engine/thread.c index df4dec8..5891558 100644 --- a/src/engine/thread.c +++ b/src/engine/thread.c @@ -4,6 +4,7 @@ #include <stdio.h> #include <stdlib.h> #include <limits.h> +#include <sched.h> #define ROOT_MAX_DEPTH 100 #define MATE_WINDOW 1000 @@ -20,8 +21,14 @@ static int __score_to_mate(i32 score) { void threads_go(struct threads *handler) { int running_count = handler->running_count; + + // initialise every arg struct before spawning: the root hands out + // pointers into this array (workers_stop), so all of them must be valid + // and settled before any thread starts running. for (int i = 0; i < running_count; i++) { __threads_args_init(handler->args + i, handler, i); + } + for (int i = 0; i < running_count; i++) { pthread_create( handler->threads + i, NULL, @@ -60,10 +67,16 @@ void *child_thread(void *_args) { args->sp_pool_capacity ); - // nothing active anywhere: the pool is drained, so we are done helping - // (the root re-publishes per iteration once root splitting lands) - if (sp == NULL) break; + // nothing active right now: idle until the root publishes the next + // iteration's block or the search is stopped + if (sp == NULL) { + static _Thread_local int spins = 0; + if (++spins == 5000000) fprintf(stderr, "CHILD %d STUCK stop=%d\n", args->id, atomic_load(&args->stop)); + sched_yield(); + continue; + } + fprintf(stderr,"CHILD %d found sp\n",args->id); // register as a worker at this block so Merge knows the headcount atomic_fetch_add(&sp->workers, 1); @@ -136,7 +149,85 @@ void *root_thread(void *_args) { // iterative deepening: search at increasing depths, publishing an info // message per completed depth so the UCI thread can emit `info ... pv`. for (int depth = 1; depth <= max_depth; depth++) { - result = search(*args->position, depth, -SEARCH_INF, SEARCH_INF, &ctx); + if (atomic_load(&stop)) break; + + // root move list (DTS §3.4: splitting at the root is always safe/PV) + moves_t moves; + moves_init(&moves); + get_legal_moves(&moves, *args->position); + + bool can_split = moves.length > 1 && args->split_points != NULL; + split_point_t *sp = can_split + ? __pool_alloc(args->split_points, args->sp_pool_capacity) + : NULL; + + fprintf(stderr,"ROOT iter %d sp=%p\n",depth,(void*)sp); + if (sp != NULL) { + // Split (§2.4): snapshot the root node and hand the whole move list to + // the pool; the root works the list alongside its workers. + sp->depth = depth; + sp->ply = 0; + sp->node_type = NODE_PV; + sp->position_snapshot = *args->position; + atomic_store(&sp->alpha, -SEARCH_INF); + atomic_store(&sp->beta, SEARCH_INF); + atomic_store(&sp->best_score, -SEARCH_INF); + sp->best_move = (move_t) { 0 }; + moves_deinit(sp->moves); + sp->moves = moves; + split_point_publish(sp); + atomic_fetch_add(&sp->workers, 1); // the root is a worker too + + i32 best_score = -SEARCH_INF; + move_t best_move = (move_t) { 0 }; + move_t move; + while (split_point_claim_move(sp, &move)) { + position_t child = sp->position_snapshot; + position_make_move(&child, move); + + int alpha = atomic_load(&sp->alpha); + search_result_t res = search( + child, + depth - 1, + -SEARCH_INF, + -alpha, + &ctx + ); + + i32 score = -res.score; + split_point_share(sp, score, move); + if (score > best_score) { + best_score = score; + best_move = move; + atomic_store(&sp->alpha, best_score); // Share narrowed bounds + } + } + + // Merge (§2.6): leave the block; the last worker out Unsplits it. + // The root then waits until nobody can write to the block anymore. + if (split_point_merge(sp)) { + atomic_store(&sp->state, SPLITPOINT_STATE_FREE); + } + fprintf(stderr,"ROOT iter %d waiting w=%d\n",depth,atomic_load(&sp->workers)); + int spins=0; + while (atomic_load(&sp->state) == SPLITPOINT_STATE_ACTIVE) { + sched_yield(); + if (++spins==1000000) fprintf(stderr,"ROOT STUCK iter %d w=%d idx=%d\n",depth,atomic_load(&sp->workers),atomic_load(&sp->next_move_idx)); + } + while (atomic_load(&sp->state) == SPLITPOINT_STATE_ACTIVE) { + sched_yield(); + } + fprintf(stderr,"ROOT iter %d done\n",depth); + + result.score = atomic_load(&sp->best_score); + result.best_move = sp->best_move; + // the move list now belongs to the pool block (released on its next + // reuse), so there is nothing to free here + } else { + // single reply or no pool available: search sequentially + result = search(*args->position, depth, -SEARCH_INF, SEARCH_INF, &ctx); + moves_deinit(moves); + } if (atomic_load(&stop)) break; @@ -165,7 +256,10 @@ void *root_thread(void *_args) { message->next = NULL; engine_message_mark_ready(message); - // signal children to stop their loops and mark this thread as finished. + // signal workers to stop their loops, then mark this thread as finished. + for (int i = 0; i < args->workers_count; i++) { + atomic_store(&args->workers_stop[i], 1); + } atomic_store(&args->stop, 1); atomic_store(&args->finished, 1); @@ -377,6 +471,17 @@ static void __child_test_setup( } } +static void __child_test_stop_all(__child_test_env_t *env, int worker_count) { + // workers idle when the pool is drained, so they must be signalled + for (int i = 0; i < worker_count; i++) { + atomic_store(&env->args[i].stop, 1); + } +} + +static void __wait_split_point_done(split_point_t *sp) { + while (atomic_load(&sp->state) == SPLITPOINT_STATE_ACTIVE) sched_yield(); +} + static void __child_test_teardown(__child_test_env_t *env, int worker_count) { ttable_deinit(env->handler->ttable); for (int i = 0; i < worker_count; i++) { @@ -410,6 +515,10 @@ bool test_child_workers_claim_and_share() { for (int i = 0; i < WORKERS; i++) { pthread_create(env.threads + i, NULL, child_thread, env.args + i); } + + // workers idle once the block is drained — signal them, then collect + __wait_split_point_done(sp); + __child_test_stop_all(&env, WORKERS); for (int i = 0; i < WORKERS; i++) { pthread_join(env.threads[i], NULL); } @@ -494,6 +603,9 @@ bool test_child_workers_find_mate_score() { for (int i = 0; i < WORKERS; i++) { pthread_create(env.threads + i, NULL, child_thread, env.args + i); } + + __wait_split_point_done(sp); + __child_test_stop_all(&env, WORKERS); for (int i = 0; i < WORKERS; i++) { pthread_join(env.threads[i], NULL); } @@ -507,5 +619,163 @@ bool test_child_workers_find_mate_score() { __child_test_teardown(&env, WORKERS); return ok; } + +// ---- Phase F tests: root move-list splitting ---- + +enum { ROOT_SPLIT_MAX = 5 }; + +typedef struct { + struct threads handler; + go_args_t go_args; + position_t position; + engine_message_handler_t message_handlers[ROOT_SPLIT_MAX]; + engine_message_handler_t *message_ptrs[ROOT_SPLIT_MAX]; + engine_messages_t messages; + pthread_t threads[ROOT_SPLIT_MAX]; +} __root_split_env_t; + +static void __root_split_env_init( + __root_split_env_t *env, + int thread_count, + go_args_t go_args, + position_t position +) { + threads_init(&env->handler, (size_t)thread_count); + ttable_init(&env->handler.ttable, 2); + + env->go_args = go_args; + env->position = position; + env->messages.count = thread_count; + env->messages.capacity = thread_count; + for (int i = 0; i < thread_count; i++) { + env->message_handlers[i] = (engine_message_handler_t) { 0 }; + env->message_ptrs[i] = &env->message_handlers[i]; + engine_message_handler_init(env->message_ptrs[i]); + } + env->messages.data = env->message_ptrs; + + env->handler.engine_messages = &env->messages; + env->handler.go_args = &env->go_args; + env->handler.position = &env->position; + env->handler.running_count = thread_count; + + // args live in handler.args so that the root's workers_stop pointers + // target the very structs the worker threads receive. + for (int i = 0; i < thread_count; i++) { + __threads_args_init(env->handler.args + i, &env->handler, i); + } +} + +static void __root_split_env_run(__root_split_env_t *env, int thread_count) { + for (int i = 1; i < thread_count; i++) { + pthread_create( + env->threads + i, + NULL, + child_thread, + env->handler.args + i + ); + } + pthread_create(env->threads, NULL, root_thread, env->handler.args); + + pthread_join(env->threads[0], NULL); // root stops its workers on exit + for (int i = 1; i < thread_count; i++) { + pthread_join(env->threads[i], NULL); + } +} + +static void __root_split_env_deinit( + __root_split_env_t *env, + int thread_count +) { + for (int i = 0; i < thread_count; i++) { + while (engine_message_handler_can_pop(&env->message_handlers[i])) { + engine_message_deinit(engine_message_handler_pop(&env->message_handlers[i])); + } + engine_message_handler_deinit(&env->message_handlers[i]); + } + ttable_deinit(env->handler.ttable); + threads_deinit(&env->handler); +} + +bool test_root_split_all_moves_claimed() { + enum { THREADS = 4 }; + __root_split_env_t env; + __root_split_env_init( + &env, + THREADS, + (go_args_t) { .depth = 3 }, + position_starting() + ); + + __root_split_env_run(&env, THREADS); + + bool ok = true; + + // root + workers all finished cleanly + for (int i = 0; i < THREADS; i++) { + ok &= atomic_load( + &((thread_args_t*)env.handler.args)[i].root_thread_args.finished + ) == 1; + } + + // every pool block was Unsplitten back to FREE + split_point_t *pool = env.handler.split_point_pool; + bool any_used = false; + for (size_t b = 0; b < env.handler.capacity; b++) { + ok &= atomic_load(&pool[b].state) == SPLITPOINT_STATE_FREE; + if (atomic_load(&pool[b].next_move_idx) > 0) any_used = true; + } + ok &= any_used; // splitting actually happened + + // the root published info messages for each completed depth + int count = 0; + engine_message_t *messages = + __root_thread_pop_all(&env.message_handlers[0], &count); + ok &= count == 3; + ok &= messages->depth == 1 && messages->next->depth == 2; + ok &= messages->score_cp > -SEARCH_INF; + + __root_thread_free_messages(messages); + __root_split_env_deinit(&env, THREADS); + return ok; +} + +bool test_root_with_workers_reports_mate() { + struct fen_load r = load_fen("k7/8/KQ6/8/8/8/8/8 w - - 0 1"); + if (r.failed) return false; + + enum { THREADS = 3 }; + __root_split_env_t env; + __root_split_env_init( + &env, + THREADS, + (go_args_t) { .depth = 3 }, + r.position + ); + + __root_split_env_run(&env, THREADS); + + bool ok = true; + for (int i = 0; i < THREADS; i++) { + ok &= atomic_load( + &((thread_args_t*)env.handler.args)[i].root_thread_args.finished + ) == 1; + } + + // the merged root result must report the mate in 1 + int count = 0; + engine_message_t *messages = + __root_thread_pop_all(&env.message_handlers[0], &count); + engine_message_t *tail = messages; + while (tail->next != NULL) tail = tail->next; + ok &= tail->mate == 1; + ok &= tail->score_cp == SEARCH_MATE; + ok &= tail->best_move.from >= 0 && tail->best_move.from < 64; + ok &= tail->best_move.to >= 0 && tail->best_move.to < 64; + + __root_thread_free_messages(messages); + __root_split_env_deinit(&env, THREADS); + return ok; +} #endif |
