Merge pull request #428 from negativeExponent/libretro
[pcsx_rearmed.git] / deps / lightrec / recompiler.c
CommitLineData
d16005f8
PC
1/*
2 * Copyright (C) 2019-2020 Paul Cercueil <paul@crapouillou.net>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 */
14
15#include "debug.h"
16#include "interpreter.h"
17#include "lightrec-private.h"
18#include "memmanager.h"
a59e5536 19#include "slist.h"
d16005f8
PC
20
21#include <errno.h>
22#include <stdatomic.h>
23#include <stdbool.h>
24#include <stdlib.h>
25#include <pthread.h>
26
27struct block_rec {
28 struct block *block;
a59e5536 29 struct slist_elm slist;
d16005f8
PC
30};
31
32struct recompiler {
33 struct lightrec_state *state;
34 pthread_t thd;
35 pthread_cond_t cond;
36 pthread_mutex_t mutex;
37 bool stop;
38 struct block *current_block;
a59e5536 39 struct slist_elm slist;
d16005f8
PC
40};
41
d16005f8
PC
42static void lightrec_compile_list(struct recompiler *rec)
43{
a59e5536 44 struct block_rec *block_rec;
45 struct slist_elm *next;
d16005f8
PC
46 struct block *block;
47 int ret;
48
a59e5536 49 while (!!(next = slist_first(&rec->slist))) {
50 block_rec = container_of(next, struct block_rec, slist);
51 block = block_rec->block;
d16005f8
PC
52 rec->current_block = block;
53
54 pthread_mutex_unlock(&rec->mutex);
55
56 ret = lightrec_compile_block(block);
57 if (ret) {
58 pr_err("Unable to compile block at PC 0x%x: %d\n",
59 block->pc, ret);
60 }
61
62 pthread_mutex_lock(&rec->mutex);
63
a59e5536 64 slist_remove(&rec->slist, next);
d16005f8 65 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
a59e5536 66 sizeof(*block_rec), block_rec);
d16005f8
PC
67 pthread_cond_signal(&rec->cond);
68 }
69
70 rec->current_block = NULL;
71}
72
73static void * lightrec_recompiler_thd(void *d)
74{
75 struct recompiler *rec = d;
76
77 pthread_mutex_lock(&rec->mutex);
78
a59e5536 79 do {
d16005f8
PC
80 do {
81 pthread_cond_wait(&rec->cond, &rec->mutex);
82
a59e5536 83 if (rec->stop)
84 goto out_unlock;
d16005f8 85
a59e5536 86 } while (slist_empty(&rec->slist));
d16005f8
PC
87
88 lightrec_compile_list(rec);
a59e5536 89 } while (!rec->stop);
90
91out_unlock:
92 pthread_mutex_unlock(&rec->mutex);
93 return NULL;
d16005f8
PC
94}
95
96struct recompiler *lightrec_recompiler_init(struct lightrec_state *state)
97{
98 struct recompiler *rec;
99 int ret;
100
101 rec = lightrec_malloc(state, MEM_FOR_LIGHTREC, sizeof(*rec));
102 if (!rec) {
103 pr_err("Cannot create recompiler: Out of memory\n");
104 return NULL;
105 }
106
107 rec->state = state;
108 rec->stop = false;
109 rec->current_block = NULL;
a59e5536 110 slist_init(&rec->slist);
d16005f8
PC
111
112 ret = pthread_cond_init(&rec->cond, NULL);
113 if (ret) {
114 pr_err("Cannot init cond variable: %d\n", ret);
115 goto err_free_rec;
116 }
117
118 ret = pthread_mutex_init(&rec->mutex, NULL);
119 if (ret) {
120 pr_err("Cannot init mutex variable: %d\n", ret);
121 goto err_cnd_destroy;
122 }
123
124 ret = pthread_create(&rec->thd, NULL, lightrec_recompiler_thd, rec);
125 if (ret) {
126 pr_err("Cannot create recompiler thread: %d\n", ret);
127 goto err_mtx_destroy;
128 }
129
130 return rec;
131
132err_mtx_destroy:
133 pthread_mutex_destroy(&rec->mutex);
134err_cnd_destroy:
135 pthread_cond_destroy(&rec->cond);
136err_free_rec:
137 lightrec_free(state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
138 return NULL;
139}
140
141void lightrec_free_recompiler(struct recompiler *rec)
142{
143 rec->stop = true;
144
145 /* Stop the thread */
146 pthread_mutex_lock(&rec->mutex);
147 pthread_cond_signal(&rec->cond);
148 pthread_mutex_unlock(&rec->mutex);
149 pthread_join(rec->thd, NULL);
150
151 pthread_mutex_destroy(&rec->mutex);
152 pthread_cond_destroy(&rec->cond);
153 lightrec_free(rec->state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
154}
155
156int lightrec_recompiler_add(struct recompiler *rec, struct block *block)
157{
a59e5536 158 struct slist_elm *elm, *prev;
159 struct block_rec *block_rec;
160 int ret = 0;
d16005f8
PC
161
162 pthread_mutex_lock(&rec->mutex);
163
a59e5536 164 /* If the block is marked as dead, don't compile it, it will be removed
165 * as soon as it's safe. */
166 if (block->flags & BLOCK_IS_DEAD)
167 goto out_unlock;
168
169 for (elm = slist_first(&rec->slist), prev = NULL; elm;
170 prev = elm, elm = elm->next) {
171 block_rec = container_of(elm, struct block_rec, slist);
172
d16005f8
PC
173 if (block_rec->block == block) {
174 /* The block to compile is already in the queue - bump
a59e5536 175 * it to the top of the list, unless the block is being
176 * recompiled. */
177 if (prev && !(block->flags & BLOCK_SHOULD_RECOMPILE)) {
178 slist_remove_next(prev);
179 slist_append(&rec->slist, elm);
d16005f8
PC
180 }
181
a59e5536 182 goto out_unlock;
d16005f8
PC
183 }
184 }
185
186 /* By the time this function was called, the block has been recompiled
187 * and ins't in the wait list anymore. Just return here. */
a59e5536 188 if (block->function && !(block->flags & BLOCK_SHOULD_RECOMPILE))
189 goto out_unlock;
d16005f8
PC
190
191 block_rec = lightrec_malloc(rec->state, MEM_FOR_LIGHTREC,
192 sizeof(*block_rec));
193 if (!block_rec) {
a59e5536 194 ret = -ENOMEM;
195 goto out_unlock;
d16005f8
PC
196 }
197
198 pr_debug("Adding block PC 0x%x to recompiler\n", block->pc);
199
200 block_rec->block = block;
a59e5536 201
202 elm = &rec->slist;
203
204 /* If the block is being recompiled, push it to the end of the queue;
205 * otherwise push it to the front of the queue. */
206 if (block->flags & BLOCK_SHOULD_RECOMPILE)
207 for (; elm->next; elm = elm->next);
208
209 slist_append(elm, &block_rec->slist);
d16005f8
PC
210
211 /* Signal the thread */
212 pthread_cond_signal(&rec->cond);
d16005f8 213
a59e5536 214out_unlock:
215 pthread_mutex_unlock(&rec->mutex);
216 return ret;
d16005f8
PC
217}
218
219void lightrec_recompiler_remove(struct recompiler *rec, struct block *block)
220{
221 struct block_rec *block_rec;
a59e5536 222 struct slist_elm *elm;
d16005f8
PC
223
224 pthread_mutex_lock(&rec->mutex);
225
a59e5536 226 for (elm = slist_first(&rec->slist); elm; elm = elm->next) {
227 block_rec = container_of(elm, struct block_rec, slist);
228
d16005f8
PC
229 if (block_rec->block == block) {
230 if (block == rec->current_block) {
231 /* Block is being recompiled - wait for
232 * completion */
233 do {
234 pthread_cond_wait(&rec->cond,
235 &rec->mutex);
236 } while (block == rec->current_block);
237 } else {
238 /* Block is not yet being processed - remove it
239 * from the list */
a59e5536 240 slist_remove(&rec->slist, elm);
d16005f8
PC
241 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
242 sizeof(*block_rec), block_rec);
243 }
244
245 break;
246 }
247 }
248
249 pthread_mutex_unlock(&rec->mutex);
250}
251
252void * lightrec_recompiler_run_first_pass(struct block *block, u32 *pc)
253{
254 bool freed;
255
256 if (likely(block->function)) {
257 if (block->flags & BLOCK_FULLY_TAGGED) {
258 freed = atomic_flag_test_and_set(&block->op_list_freed);
259
260 if (!freed) {
261 pr_debug("Block PC 0x%08x is fully tagged"
262 " - free opcode list\n", block->pc);
263
264 /* The block was already compiled but the opcode list
265 * didn't get freed yet - do it now */
266 lightrec_free_opcode_list(block->state,
267 block->opcode_list);
268 block->opcode_list = NULL;
269 }
270 }
271
272 return block->function;
273 }
274
275 /* Mark the opcode list as freed, so that the threaded compiler won't
276 * free it while we're using it in the interpreter. */
277 freed = atomic_flag_test_and_set(&block->op_list_freed);
278
279 /* Block wasn't compiled yet - run the interpreter */
280 *pc = lightrec_emulate_block(block, *pc);
281
282 if (!freed)
283 atomic_flag_clear(&block->op_list_freed);
284
285 /* The block got compiled while the interpreter was running.
286 * We can free the opcode list now. */
287 if (block->function && (block->flags & BLOCK_FULLY_TAGGED) &&
288 !atomic_flag_test_and_set(&block->op_list_freed)) {
289 pr_debug("Block PC 0x%08x is fully tagged"
290 " - free opcode list\n", block->pc);
291
292 lightrec_free_opcode_list(block->state, block->opcode_list);
293 block->opcode_list = NULL;
294 }
295
296 return NULL;
297}