Merge pull request #836 from pcercuei/update-lightrec-20240611
[pcsx_rearmed.git] / deps / lightrec / recompiler.c
CommitLineData
98fa08a5 1// SPDX-License-Identifier: LGPL-2.1-or-later
d16005f8 2/*
98fa08a5 3 * Copyright (C) 2019-2021 Paul Cercueil <paul@crapouillou.net>
d16005f8
PC
4 */
5
d8b04acd 6#include "blockcache.h"
d16005f8
PC
7#include "debug.h"
8#include "interpreter.h"
9#include "lightrec-private.h"
10#include "memmanager.h"
d8b04acd 11#include "reaper.h"
a59e5536 12#include "slist.h"
d16005f8
PC
13
14#include <errno.h>
15#include <stdatomic.h>
16#include <stdbool.h>
17#include <stdlib.h>
18#include <pthread.h>
98fa08a5
PC
19#ifdef __linux__
20#include <unistd.h>
21#endif
d16005f8
PC
22
23struct block_rec {
24 struct block *block;
a59e5536 25 struct slist_elm slist;
a5a6f7b8 26 unsigned int requests;
98fa08a5
PC
27 bool compiling;
28};
29
30struct recompiler_thd {
31 struct lightrec_cstate *cstate;
32 unsigned int tid;
33 pthread_t thd;
d16005f8
PC
34};
35
36struct recompiler {
37 struct lightrec_state *state;
d16005f8 38 pthread_cond_t cond;
98fa08a5 39 pthread_cond_t cond2;
d16005f8 40 pthread_mutex_t mutex;
878e6cda 41 bool stop, pause, must_flush;
a59e5536 42 struct slist_elm slist;
98fa08a5 43
d8b04acd
PC
44 pthread_mutex_t alloc_mutex;
45
98fa08a5
PC
46 unsigned int nb_recs;
47 struct recompiler_thd thds[];
d16005f8
PC
48};
49
98fa08a5
PC
50static unsigned int get_processors_count(void)
51{
878e6cda 52 int nb = 1;
98fa08a5
PC
53
54#if defined(PTW32_VERSION)
55 nb = pthread_num_processors_np();
56#elif defined(__APPLE__) || defined(__FreeBSD__)
57 int count;
58 size_t size = sizeof(count);
59
60 nb = sysctlbyname("hw.ncpu", &count, &size, NULL, 0) ? 1 : count;
11357fef 61#elif defined(_SC_NPROCESSORS_ONLN)
878e6cda 62 nb = (int)sysconf(_SC_NPROCESSORS_ONLN);
98fa08a5
PC
63#endif
64
65 return nb < 1 ? 1 : nb;
66}
67
a5a6f7b8 68static struct block_rec * lightrec_get_best_elm(struct slist_elm *head)
98fa08a5 69{
a5a6f7b8 70 struct block_rec *block_rec, *best = NULL;
98fa08a5
PC
71 struct slist_elm *elm;
72
73 for (elm = slist_first(head); elm; elm = elm->next) {
74 block_rec = container_of(elm, struct block_rec, slist);
75
a5a6f7b8
PC
76 if (!block_rec->compiling
77 && (!best || block_rec->requests > best->requests))
78 best = block_rec;
98fa08a5
PC
79 }
80
a5a6f7b8 81 return best;
98fa08a5
PC
82}
83
d8b04acd
PC
84static bool lightrec_cancel_block_rec(struct recompiler *rec,
85 struct block_rec *block_rec)
86{
87 if (block_rec->compiling) {
88 /* Block is being recompiled - wait for
89 * completion */
90 pthread_cond_wait(&rec->cond2, &rec->mutex);
91
92 /* We can't guarantee the signal was for us.
93 * Since block_rec may have been removed while
94 * we were waiting on the condition, we cannot
95 * check block_rec->compiling again. The best
96 * thing is just to restart the function. */
97 return false;
98 }
99
100 /* Block is not yet being processed - remove it from the list */
101 slist_remove(&rec->slist, &block_rec->slist);
102 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
103 sizeof(*block_rec), block_rec);
104
105 return true;
106}
107
108static void lightrec_cancel_list(struct recompiler *rec)
109{
110 struct block_rec *block_rec;
ba3814c1 111 struct slist_elm *elm, *head = &rec->slist;
d8b04acd 112
ba3814c1
PC
113 for (elm = slist_first(head); elm; elm = slist_first(head)) {
114 block_rec = container_of(elm, struct block_rec, slist);
d8b04acd
PC
115 lightrec_cancel_block_rec(rec, block_rec);
116 }
d8b04acd
PC
117}
118
119static void lightrec_flush_code_buffer(struct lightrec_state *state, void *d)
120{
121 struct recompiler *rec = d;
122
ba3814c1
PC
123 lightrec_remove_outdated_blocks(state->block_cache, NULL);
124 rec->must_flush = false;
d8b04acd
PC
125}
126
98fa08a5
PC
127static void lightrec_compile_list(struct recompiler *rec,
128 struct recompiler_thd *thd)
d16005f8 129{
a59e5536 130 struct block_rec *block_rec;
d16005f8
PC
131 struct block *block;
132 int ret;
133
878e6cda
PC
134 while (!rec->pause &&
135 !!(block_rec = lightrec_get_best_elm(&rec->slist))) {
98fa08a5 136 block_rec->compiling = true;
a59e5536 137 block = block_rec->block;
d16005f8
PC
138
139 pthread_mutex_unlock(&rec->mutex);
140
ba3814c1 141 if (likely(!block_has_flag(block, BLOCK_IS_DEAD))) {
98fa08a5 142 ret = lightrec_compile_block(thd->cstate, block);
d8b04acd
PC
143 if (ret == -ENOMEM) {
144 /* Code buffer is full. Request the reaper to
145 * flush it. */
146
147 pthread_mutex_lock(&rec->mutex);
ba3814c1
PC
148 block_rec->compiling = false;
149 pthread_cond_broadcast(&rec->cond2);
150
d8b04acd 151 if (!rec->must_flush) {
ba3814c1
PC
152 rec->must_flush = true;
153 lightrec_cancel_list(rec);
154
d8b04acd
PC
155 lightrec_reaper_add(rec->state->reaper,
156 lightrec_flush_code_buffer,
157 rec);
d8b04acd
PC
158 }
159 return;
160 }
161
98fa08a5 162 if (ret) {
f5ee77ca 163 pr_err("Unable to compile block at "PC_FMT": %d\n",
98fa08a5
PC
164 block->pc, ret);
165 }
d16005f8
PC
166 }
167
168 pthread_mutex_lock(&rec->mutex);
169
a5a6f7b8 170 slist_remove(&rec->slist, &block_rec->slist);
d16005f8 171 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
a59e5536 172 sizeof(*block_rec), block_rec);
ba3814c1 173 pthread_cond_broadcast(&rec->cond2);
d16005f8 174 }
d16005f8
PC
175}
176
177static void * lightrec_recompiler_thd(void *d)
178{
98fa08a5
PC
179 struct recompiler_thd *thd = d;
180 struct recompiler *rec = container_of(thd, struct recompiler, thds[thd->tid]);
d16005f8
PC
181
182 pthread_mutex_lock(&rec->mutex);
183
ea17432f 184 while (!rec->stop) {
d16005f8
PC
185 do {
186 pthread_cond_wait(&rec->cond, &rec->mutex);
187
a59e5536 188 if (rec->stop)
189 goto out_unlock;
d16005f8 190
878e6cda 191 } while (rec->pause || slist_empty(&rec->slist));
d16005f8 192
98fa08a5 193 lightrec_compile_list(rec, thd);
ea17432f 194 }
a59e5536 195
196out_unlock:
197 pthread_mutex_unlock(&rec->mutex);
198 return NULL;
d16005f8
PC
199}
200
201struct recompiler *lightrec_recompiler_init(struct lightrec_state *state)
202{
203 struct recompiler *rec;
98fa08a5 204 unsigned int i, nb_recs, nb_cpus;
d16005f8
PC
205 int ret;
206
98fa08a5
PC
207 nb_cpus = get_processors_count();
208 nb_recs = nb_cpus < 2 ? 1 : nb_cpus - 1;
209
210 rec = lightrec_malloc(state, MEM_FOR_LIGHTREC, sizeof(*rec)
211 + nb_recs * sizeof(*rec->thds));
d16005f8
PC
212 if (!rec) {
213 pr_err("Cannot create recompiler: Out of memory\n");
214 return NULL;
215 }
216
98fa08a5
PC
217 for (i = 0; i < nb_recs; i++) {
218 rec->thds[i].tid = i;
219 rec->thds[i].cstate = NULL;
220 }
221
222 for (i = 0; i < nb_recs; i++) {
223 rec->thds[i].cstate = lightrec_create_cstate(state);
25851a1e 224 if (!rec->thds[i].cstate) {
98fa08a5
PC
225 pr_err("Cannot create recompiler: Out of memory\n");
226 goto err_free_cstates;
227 }
228 }
229
d16005f8
PC
230 rec->state = state;
231 rec->stop = false;
878e6cda 232 rec->pause = false;
d8b04acd 233 rec->must_flush = false;
98fa08a5 234 rec->nb_recs = nb_recs;
a59e5536 235 slist_init(&rec->slist);
d16005f8
PC
236
237 ret = pthread_cond_init(&rec->cond, NULL);
238 if (ret) {
239 pr_err("Cannot init cond variable: %d\n", ret);
98fa08a5 240 goto err_free_cstates;
d16005f8
PC
241 }
242
98fa08a5 243 ret = pthread_cond_init(&rec->cond2, NULL);
d16005f8 244 if (ret) {
98fa08a5 245 pr_err("Cannot init cond variable: %d\n", ret);
d16005f8
PC
246 goto err_cnd_destroy;
247 }
248
d8b04acd
PC
249 ret = pthread_mutex_init(&rec->alloc_mutex, NULL);
250 if (ret) {
251 pr_err("Cannot init alloc mutex variable: %d\n", ret);
252 goto err_cnd2_destroy;
253 }
254
98fa08a5 255 ret = pthread_mutex_init(&rec->mutex, NULL);
d16005f8 256 if (ret) {
98fa08a5 257 pr_err("Cannot init mutex variable: %d\n", ret);
d8b04acd 258 goto err_alloc_mtx_destroy;
d16005f8
PC
259 }
260
98fa08a5
PC
261 for (i = 0; i < nb_recs; i++) {
262 ret = pthread_create(&rec->thds[i].thd, NULL,
263 lightrec_recompiler_thd, &rec->thds[i]);
264 if (ret) {
265 pr_err("Cannot create recompiler thread: %d\n", ret);
266 /* TODO: Handle cleanup properly */
267 goto err_mtx_destroy;
268 }
269 }
270
271 pr_info("Threaded recompiler started with %u workers.\n", nb_recs);
272
d16005f8
PC
273 return rec;
274
275err_mtx_destroy:
276 pthread_mutex_destroy(&rec->mutex);
d8b04acd
PC
277err_alloc_mtx_destroy:
278 pthread_mutex_destroy(&rec->alloc_mutex);
98fa08a5
PC
279err_cnd2_destroy:
280 pthread_cond_destroy(&rec->cond2);
d16005f8
PC
281err_cnd_destroy:
282 pthread_cond_destroy(&rec->cond);
98fa08a5
PC
283err_free_cstates:
284 for (i = 0; i < nb_recs; i++) {
285 if (rec->thds[i].cstate)
286 lightrec_free_cstate(rec->thds[i].cstate);
287 }
d16005f8
PC
288 lightrec_free(state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
289 return NULL;
290}
291
292void lightrec_free_recompiler(struct recompiler *rec)
293{
98fa08a5
PC
294 unsigned int i;
295
d16005f8
PC
296 rec->stop = true;
297
298 /* Stop the thread */
299 pthread_mutex_lock(&rec->mutex);
98fa08a5 300 pthread_cond_broadcast(&rec->cond);
d8b04acd 301 lightrec_cancel_list(rec);
d16005f8 302 pthread_mutex_unlock(&rec->mutex);
98fa08a5
PC
303
304 for (i = 0; i < rec->nb_recs; i++)
305 pthread_join(rec->thds[i].thd, NULL);
306
307 for (i = 0; i < rec->nb_recs; i++)
308 lightrec_free_cstate(rec->thds[i].cstate);
d16005f8
PC
309
310 pthread_mutex_destroy(&rec->mutex);
d8b04acd 311 pthread_mutex_destroy(&rec->alloc_mutex);
d16005f8 312 pthread_cond_destroy(&rec->cond);
98fa08a5 313 pthread_cond_destroy(&rec->cond2);
d16005f8
PC
314 lightrec_free(rec->state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
315}
316
317int lightrec_recompiler_add(struct recompiler *rec, struct block *block)
318{
a5a6f7b8 319 struct slist_elm *elm;
a59e5536 320 struct block_rec *block_rec;
a5a6f7b8 321 u32 pc1, pc2;
a59e5536 322 int ret = 0;
d16005f8
PC
323
324 pthread_mutex_lock(&rec->mutex);
325
d8b04acd
PC
326 /* If the recompiler must flush the code cache, we can't add the new
327 * job. It will be re-added next time the block's address is jumped to
328 * again. */
329 if (rec->must_flush)
330 goto out_unlock;
331
a59e5536 332 /* If the block is marked as dead, don't compile it, it will be removed
333 * as soon as it's safe. */
ba3814c1 334 if (block_has_flag(block, BLOCK_IS_DEAD))
a59e5536 335 goto out_unlock;
336
a5a6f7b8 337 for (elm = slist_first(&rec->slist); elm; elm = elm->next) {
a59e5536 338 block_rec = container_of(elm, struct block_rec, slist);
339
d16005f8 340 if (block_rec->block == block) {
a5a6f7b8
PC
341 /* The block to compile is already in the queue -
342 * increment its counter to increase its priority */
343 block_rec->requests++;
344 goto out_unlock;
345 }
d16005f8 346
a5a6f7b8
PC
347 pc1 = kunseg(block_rec->block->pc);
348 pc2 = kunseg(block->pc);
349 if (pc2 >= pc1 && pc2 < pc1 + block_rec->block->nb_ops * 4) {
350 /* The block we want to compile is already covered by
351 * another one in the queue - increment its counter to
352 * increase its priority */
353 block_rec->requests++;
a59e5536 354 goto out_unlock;
d16005f8
PC
355 }
356 }
357
358 /* By the time this function was called, the block has been recompiled
359 * and ins't in the wait list anymore. Just return here. */
ba3814c1 360 if (block->function && !block_has_flag(block, BLOCK_SHOULD_RECOMPILE))
a59e5536 361 goto out_unlock;
d16005f8
PC
362
363 block_rec = lightrec_malloc(rec->state, MEM_FOR_LIGHTREC,
364 sizeof(*block_rec));
365 if (!block_rec) {
a59e5536 366 ret = -ENOMEM;
367 goto out_unlock;
d16005f8
PC
368 }
369
f5ee77ca 370 pr_debug("Adding block "PC_FMT" to recompiler\n", block->pc);
d16005f8
PC
371
372 block_rec->block = block;
98fa08a5 373 block_rec->compiling = false;
a5a6f7b8 374 block_rec->requests = 1;
a59e5536 375
376 elm = &rec->slist;
377
a5a6f7b8 378 /* Push the new entry to the front of the queue */
a59e5536 379 slist_append(elm, &block_rec->slist);
d16005f8
PC
380
381 /* Signal the thread */
382 pthread_cond_signal(&rec->cond);
d16005f8 383
a59e5536 384out_unlock:
385 pthread_mutex_unlock(&rec->mutex);
98fa08a5 386
a59e5536 387 return ret;
d16005f8
PC
388}
389
390void lightrec_recompiler_remove(struct recompiler *rec, struct block *block)
391{
392 struct block_rec *block_rec;
a59e5536 393 struct slist_elm *elm;
d16005f8
PC
394
395 pthread_mutex_lock(&rec->mutex);
396
98fa08a5
PC
397 while (true) {
398 for (elm = slist_first(&rec->slist); elm; elm = elm->next) {
399 block_rec = container_of(elm, struct block_rec, slist);
a59e5536 400
d8b04acd
PC
401 if (block_rec->block == block) {
402 if (lightrec_cancel_block_rec(rec, block_rec))
403 goto out_unlock;
98fa08a5 404
98fa08a5 405 break;
d16005f8 406 }
98fa08a5 407 }
d16005f8 408
98fa08a5 409 if (!elm)
d16005f8 410 break;
d16005f8
PC
411 }
412
98fa08a5 413out_unlock:
d16005f8
PC
414 pthread_mutex_unlock(&rec->mutex);
415}
416
98fa08a5
PC
417void * lightrec_recompiler_run_first_pass(struct lightrec_state *state,
418 struct block *block, u32 *pc)
d16005f8 419{
ba3814c1 420 u8 old_flags;
d16005f8 421
98fa08a5
PC
422 /* There's no point in running the first pass if the block will never
423 * be compiled. Let the main loop run the interpreter instead. */
ba3814c1 424 if (block_has_flag(block, BLOCK_NEVER_COMPILE))
98fa08a5
PC
425 return NULL;
426
ba3814c1
PC
427 /* The block is marked as dead, and will be removed the next time the
428 * reaper is run. In the meantime, the old function can still be
429 * executed. */
430 if (block_has_flag(block, BLOCK_IS_DEAD))
431 return block->function;
432
98fa08a5
PC
433 /* If the block is already fully tagged, there is no point in running
434 * the first pass. Request a recompilation of the block, and maybe the
435 * interpreter will run the block in the meantime. */
ba3814c1 436 if (block_has_flag(block, BLOCK_FULLY_TAGGED))
98fa08a5
PC
437 lightrec_recompiler_add(state->rec, block);
438
d16005f8 439 if (likely(block->function)) {
ba3814c1
PC
440 if (block_has_flag(block, BLOCK_FULLY_TAGGED)) {
441 old_flags = block_set_flags(block, BLOCK_NO_OPCODE_LIST);
d16005f8 442
ba3814c1 443 if (!(old_flags & BLOCK_NO_OPCODE_LIST)) {
f5ee77ca 444 pr_debug("Block "PC_FMT" is fully tagged"
d16005f8
PC
445 " - free opcode list\n", block->pc);
446
447 /* The block was already compiled but the opcode list
448 * didn't get freed yet - do it now */
ba3814c1 449 lightrec_free_opcode_list(state, block->opcode_list);
d16005f8
PC
450 }
451 }
452
453 return block->function;
454 }
455
456 /* Mark the opcode list as freed, so that the threaded compiler won't
457 * free it while we're using it in the interpreter. */
ba3814c1 458 old_flags = block_set_flags(block, BLOCK_NO_OPCODE_LIST);
d16005f8
PC
459
460 /* Block wasn't compiled yet - run the interpreter */
98fa08a5 461 *pc = lightrec_emulate_block(state, block, *pc);
d16005f8 462
ba3814c1
PC
463 if (!(old_flags & BLOCK_NO_OPCODE_LIST))
464 block_clear_flags(block, BLOCK_NO_OPCODE_LIST);
d16005f8
PC
465
466 /* The block got compiled while the interpreter was running.
467 * We can free the opcode list now. */
ba3814c1
PC
468 if (block->function && block_has_flag(block, BLOCK_FULLY_TAGGED)) {
469 old_flags = block_set_flags(block, BLOCK_NO_OPCODE_LIST);
d16005f8 470
ba3814c1 471 if (!(old_flags & BLOCK_NO_OPCODE_LIST)) {
f5ee77ca 472 pr_debug("Block "PC_FMT" is fully tagged"
ba3814c1
PC
473 " - free opcode list\n", block->pc);
474
475 lightrec_free_opcode_list(state, block->opcode_list);
476 }
d16005f8
PC
477 }
478
479 return NULL;
480}
d8b04acd
PC
481
482void lightrec_code_alloc_lock(struct lightrec_state *state)
483{
484 pthread_mutex_lock(&state->rec->alloc_mutex);
485}
486
487void lightrec_code_alloc_unlock(struct lightrec_state *state)
488{
489 pthread_mutex_unlock(&state->rec->alloc_mutex);
490}
878e6cda
PC
491
492void lightrec_recompiler_pause(struct recompiler *rec)
493{
494 rec->pause = true;
495
496 pthread_mutex_lock(&rec->mutex);
497 pthread_cond_broadcast(&rec->cond);
498 lightrec_cancel_list(rec);
499 pthread_mutex_unlock(&rec->mutex);
500}
501
502void lightrec_recompiler_unpause(struct recompiler *rec)
503{
504 rec->pause = false;
505}