1 // SPDX-License-Identifier: LGPL-2.1-or-later
3 * Copyright (C) 2019-2021 Paul Cercueil <paul@crapouillou.net>
7 #include "interpreter.h"
8 #include "lightrec-private.h"
9 #include "memmanager.h"
13 #include <stdatomic.h>
23 struct slist_elm slist;
27 struct recompiler_thd {
28 struct lightrec_cstate *cstate;
34 struct lightrec_state *state;
37 pthread_mutex_t mutex;
39 struct slist_elm slist;
42 struct recompiler_thd thds[];
45 static unsigned int get_processors_count(void)
49 #if defined(PTW32_VERSION)
50 nb = pthread_num_processors_np();
51 #elif defined(__APPLE__) || defined(__FreeBSD__)
53 size_t size = sizeof(count);
55 nb = sysctlbyname("hw.ncpu", &count, &size, NULL, 0) ? 1 : count;
56 #elif defined(__linux__)
57 nb = sysconf(_SC_NPROCESSORS_ONLN);
60 return nb < 1 ? 1 : nb;
63 static struct slist_elm * lightrec_get_first_elm(struct slist_elm *head)
65 struct block_rec *block_rec;
66 struct slist_elm *elm;
68 for (elm = slist_first(head); elm; elm = elm->next) {
69 block_rec = container_of(elm, struct block_rec, slist);
71 if (!block_rec->compiling)
78 static void lightrec_compile_list(struct recompiler *rec,
79 struct recompiler_thd *thd)
81 struct block_rec *block_rec;
82 struct slist_elm *next;
86 while (!!(next = lightrec_get_first_elm(&rec->slist))) {
87 block_rec = container_of(next, struct block_rec, slist);
88 block_rec->compiling = true;
89 block = block_rec->block;
91 pthread_mutex_unlock(&rec->mutex);
93 if (likely(!(block->flags & BLOCK_IS_DEAD))) {
94 ret = lightrec_compile_block(thd->cstate, block);
96 pr_err("Unable to compile block at PC 0x%x: %d\n",
101 pthread_mutex_lock(&rec->mutex);
103 slist_remove(&rec->slist, next);
104 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
105 sizeof(*block_rec), block_rec);
106 pthread_cond_signal(&rec->cond2);
110 static void * lightrec_recompiler_thd(void *d)
112 struct recompiler_thd *thd = d;
113 struct recompiler *rec = container_of(thd, struct recompiler, thds[thd->tid]);
115 pthread_mutex_lock(&rec->mutex);
119 pthread_cond_wait(&rec->cond, &rec->mutex);
124 } while (slist_empty(&rec->slist));
126 lightrec_compile_list(rec, thd);
130 pthread_mutex_unlock(&rec->mutex);
134 struct recompiler *lightrec_recompiler_init(struct lightrec_state *state)
136 struct recompiler *rec;
137 unsigned int i, nb_recs, nb_cpus;
140 nb_cpus = get_processors_count();
141 nb_recs = nb_cpus < 2 ? 1 : nb_cpus - 1;
143 rec = lightrec_malloc(state, MEM_FOR_LIGHTREC, sizeof(*rec)
144 + nb_recs * sizeof(*rec->thds));
146 pr_err("Cannot create recompiler: Out of memory\n");
150 for (i = 0; i < nb_recs; i++) {
151 rec->thds[i].tid = i;
152 rec->thds[i].cstate = NULL;
155 for (i = 0; i < nb_recs; i++) {
156 rec->thds[i].cstate = lightrec_create_cstate(state);
157 if (!rec->thds[i].cstate) {
158 pr_err("Cannot create recompiler: Out of memory\n");
159 goto err_free_cstates;
165 rec->nb_recs = nb_recs;
166 slist_init(&rec->slist);
168 ret = pthread_cond_init(&rec->cond, NULL);
170 pr_err("Cannot init cond variable: %d\n", ret);
171 goto err_free_cstates;
174 ret = pthread_cond_init(&rec->cond2, NULL);
176 pr_err("Cannot init cond variable: %d\n", ret);
177 goto err_cnd_destroy;
180 ret = pthread_mutex_init(&rec->mutex, NULL);
182 pr_err("Cannot init mutex variable: %d\n", ret);
183 goto err_cnd2_destroy;
186 for (i = 0; i < nb_recs; i++) {
187 ret = pthread_create(&rec->thds[i].thd, NULL,
188 lightrec_recompiler_thd, &rec->thds[i]);
190 pr_err("Cannot create recompiler thread: %d\n", ret);
191 /* TODO: Handle cleanup properly */
192 goto err_mtx_destroy;
196 pr_info("Threaded recompiler started with %u workers.\n", nb_recs);
201 pthread_mutex_destroy(&rec->mutex);
203 pthread_cond_destroy(&rec->cond2);
205 pthread_cond_destroy(&rec->cond);
207 for (i = 0; i < nb_recs; i++) {
208 if (rec->thds[i].cstate)
209 lightrec_free_cstate(rec->thds[i].cstate);
211 lightrec_free(state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
215 void lightrec_free_recompiler(struct recompiler *rec)
221 /* Stop the thread */
222 pthread_mutex_lock(&rec->mutex);
223 pthread_cond_broadcast(&rec->cond);
224 pthread_mutex_unlock(&rec->mutex);
226 for (i = 0; i < rec->nb_recs; i++)
227 pthread_join(rec->thds[i].thd, NULL);
229 for (i = 0; i < rec->nb_recs; i++)
230 lightrec_free_cstate(rec->thds[i].cstate);
232 pthread_mutex_destroy(&rec->mutex);
233 pthread_cond_destroy(&rec->cond);
234 pthread_cond_destroy(&rec->cond2);
235 lightrec_free(rec->state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
238 int lightrec_recompiler_add(struct recompiler *rec, struct block *block)
240 struct slist_elm *elm, *prev;
241 struct block_rec *block_rec;
244 pthread_mutex_lock(&rec->mutex);
246 /* If the block is marked as dead, don't compile it, it will be removed
247 * as soon as it's safe. */
248 if (block->flags & BLOCK_IS_DEAD)
251 for (elm = slist_first(&rec->slist), prev = NULL; elm;
252 prev = elm, elm = elm->next) {
253 block_rec = container_of(elm, struct block_rec, slist);
255 if (block_rec->block == block) {
256 /* The block to compile is already in the queue - bump
257 * it to the top of the list, unless the block is being
259 if (prev && !block_rec->compiling &&
260 !(block->flags & BLOCK_SHOULD_RECOMPILE)) {
261 slist_remove_next(prev);
262 slist_append(&rec->slist, elm);
269 /* By the time this function was called, the block has been recompiled
270 * and ins't in the wait list anymore. Just return here. */
271 if (block->function && !(block->flags & BLOCK_SHOULD_RECOMPILE))
274 block_rec = lightrec_malloc(rec->state, MEM_FOR_LIGHTREC,
281 pr_debug("Adding block PC 0x%x to recompiler\n", block->pc);
283 block_rec->block = block;
284 block_rec->compiling = false;
288 /* If the block is being recompiled, push it to the end of the queue;
289 * otherwise push it to the front of the queue. */
290 if (block->flags & BLOCK_SHOULD_RECOMPILE)
291 for (; elm->next; elm = elm->next);
293 slist_append(elm, &block_rec->slist);
295 /* Signal the thread */
296 pthread_cond_signal(&rec->cond);
299 pthread_mutex_unlock(&rec->mutex);
304 void lightrec_recompiler_remove(struct recompiler *rec, struct block *block)
306 struct block_rec *block_rec;
307 struct slist_elm *elm;
309 pthread_mutex_lock(&rec->mutex);
312 for (elm = slist_first(&rec->slist); elm; elm = elm->next) {
313 block_rec = container_of(elm, struct block_rec, slist);
315 if (block_rec->block != block)
318 if (block_rec->compiling) {
319 /* Block is being recompiled - wait for
321 pthread_cond_wait(&rec->cond2, &rec->mutex);
323 /* We can't guarantee the signal was for us.
324 * Since block_rec may have been removed while
325 * we were waiting on the condition, we cannot
326 * check block_rec->compiling again. The best
327 * thing is just to restart the function. */
330 /* Block is not yet being processed - remove it
332 slist_remove(&rec->slist, elm);
333 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
334 sizeof(*block_rec), block_rec);
345 pthread_mutex_unlock(&rec->mutex);
348 void * lightrec_recompiler_run_first_pass(struct lightrec_state *state,
349 struct block *block, u32 *pc)
353 /* There's no point in running the first pass if the block will never
354 * be compiled. Let the main loop run the interpreter instead. */
355 if (block->flags & BLOCK_NEVER_COMPILE)
358 /* If the block is already fully tagged, there is no point in running
359 * the first pass. Request a recompilation of the block, and maybe the
360 * interpreter will run the block in the meantime. */
361 if (block->flags & BLOCK_FULLY_TAGGED)
362 lightrec_recompiler_add(state->rec, block);
364 if (likely(block->function)) {
365 if (block->flags & BLOCK_FULLY_TAGGED) {
366 freed = atomic_flag_test_and_set(&block->op_list_freed);
369 pr_debug("Block PC 0x%08x is fully tagged"
370 " - free opcode list\n", block->pc);
372 /* The block was already compiled but the opcode list
373 * didn't get freed yet - do it now */
374 lightrec_free_opcode_list(state, block);
375 block->opcode_list = NULL;
379 return block->function;
382 /* Mark the opcode list as freed, so that the threaded compiler won't
383 * free it while we're using it in the interpreter. */
384 freed = atomic_flag_test_and_set(&block->op_list_freed);
386 /* Block wasn't compiled yet - run the interpreter */
387 *pc = lightrec_emulate_block(state, block, *pc);
390 atomic_flag_clear(&block->op_list_freed);
392 /* The block got compiled while the interpreter was running.
393 * We can free the opcode list now. */
394 if (block->function && (block->flags & BLOCK_FULLY_TAGGED) &&
395 !atomic_flag_test_and_set(&block->op_list_freed)) {
396 pr_debug("Block PC 0x%08x is fully tagged"
397 " - free opcode list\n", block->pc);
399 lightrec_free_opcode_list(state, block);
400 block->opcode_list = NULL;