git subrepo clone https://github.com/pcercuei/lightrec.git deps/lightrec
[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"
19
20#include <errno.h>
21#include <stdatomic.h>
22#include <stdbool.h>
23#include <stdlib.h>
24#include <pthread.h>
25
26struct block_rec {
27 struct block *block;
28 struct block_rec *next;
29};
30
31struct recompiler {
32 struct lightrec_state *state;
33 pthread_t thd;
34 pthread_cond_t cond;
35 pthread_mutex_t mutex;
36 bool stop;
37 struct block *current_block;
38 struct block_rec *list;
39};
40
41static void slist_remove(struct recompiler *rec, struct block_rec *elm)
42{
43 struct block_rec *prev;
44
45 if (rec->list == elm) {
46 rec->list = elm->next;
47 } else {
48 for (prev = rec->list; prev && prev->next != elm; )
49 prev = prev->next;
50 if (prev)
51 prev->next = elm->next;
52 }
53}
54
55static void lightrec_compile_list(struct recompiler *rec)
56{
57 struct block_rec *next;
58 struct block *block;
59 int ret;
60
61 while (!!(next = rec->list)) {
62 block = next->block;
63 rec->current_block = block;
64
65 pthread_mutex_unlock(&rec->mutex);
66
67 ret = lightrec_compile_block(block);
68 if (ret) {
69 pr_err("Unable to compile block at PC 0x%x: %d\n",
70 block->pc, ret);
71 }
72
73 pthread_mutex_lock(&rec->mutex);
74
75 slist_remove(rec, next);
76 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
77 sizeof(*next), next);
78 pthread_cond_signal(&rec->cond);
79 }
80
81 rec->current_block = NULL;
82}
83
84static void * lightrec_recompiler_thd(void *d)
85{
86 struct recompiler *rec = d;
87
88 pthread_mutex_lock(&rec->mutex);
89
90 for (;;) {
91 do {
92 pthread_cond_wait(&rec->cond, &rec->mutex);
93
94 if (rec->stop) {
95 pthread_mutex_unlock(&rec->mutex);
96 return NULL;
97 }
98
99 } while (!rec->list);
100
101 lightrec_compile_list(rec);
102 }
103}
104
105struct recompiler *lightrec_recompiler_init(struct lightrec_state *state)
106{
107 struct recompiler *rec;
108 int ret;
109
110 rec = lightrec_malloc(state, MEM_FOR_LIGHTREC, sizeof(*rec));
111 if (!rec) {
112 pr_err("Cannot create recompiler: Out of memory\n");
113 return NULL;
114 }
115
116 rec->state = state;
117 rec->stop = false;
118 rec->current_block = NULL;
119 rec->list = NULL;
120
121 ret = pthread_cond_init(&rec->cond, NULL);
122 if (ret) {
123 pr_err("Cannot init cond variable: %d\n", ret);
124 goto err_free_rec;
125 }
126
127 ret = pthread_mutex_init(&rec->mutex, NULL);
128 if (ret) {
129 pr_err("Cannot init mutex variable: %d\n", ret);
130 goto err_cnd_destroy;
131 }
132
133 ret = pthread_create(&rec->thd, NULL, lightrec_recompiler_thd, rec);
134 if (ret) {
135 pr_err("Cannot create recompiler thread: %d\n", ret);
136 goto err_mtx_destroy;
137 }
138
139 return rec;
140
141err_mtx_destroy:
142 pthread_mutex_destroy(&rec->mutex);
143err_cnd_destroy:
144 pthread_cond_destroy(&rec->cond);
145err_free_rec:
146 lightrec_free(state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
147 return NULL;
148}
149
150void lightrec_free_recompiler(struct recompiler *rec)
151{
152 rec->stop = true;
153
154 /* Stop the thread */
155 pthread_mutex_lock(&rec->mutex);
156 pthread_cond_signal(&rec->cond);
157 pthread_mutex_unlock(&rec->mutex);
158 pthread_join(rec->thd, NULL);
159
160 pthread_mutex_destroy(&rec->mutex);
161 pthread_cond_destroy(&rec->cond);
162 lightrec_free(rec->state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
163}
164
165int lightrec_recompiler_add(struct recompiler *rec, struct block *block)
166{
167 struct block_rec *block_rec, *prev;
168
169 pthread_mutex_lock(&rec->mutex);
170
171 for (block_rec = rec->list, prev = NULL; block_rec;
172 prev = block_rec, block_rec = block_rec->next) {
173 if (block_rec->block == block) {
174 /* The block to compile is already in the queue - bump
175 * it to the top of the list */
176 if (prev) {
177 prev->next = block_rec->next;
178 block_rec->next = rec->list;
179 rec->list = block_rec;
180 }
181
182 pthread_mutex_unlock(&rec->mutex);
183 return 0;
184 }
185 }
186
187 /* By the time this function was called, the block has been recompiled
188 * and ins't in the wait list anymore. Just return here. */
189 if (block->function) {
190 pthread_mutex_unlock(&rec->mutex);
191 return 0;
192 }
193
194 block_rec = lightrec_malloc(rec->state, MEM_FOR_LIGHTREC,
195 sizeof(*block_rec));
196 if (!block_rec) {
197 pthread_mutex_unlock(&rec->mutex);
198 return -ENOMEM;
199 }
200
201 pr_debug("Adding block PC 0x%x to recompiler\n", block->pc);
202
203 block_rec->block = block;
204 block_rec->next = rec->list;
205 rec->list = block_rec;
206
207 /* Signal the thread */
208 pthread_cond_signal(&rec->cond);
209 pthread_mutex_unlock(&rec->mutex);
210
211 return 0;
212}
213
214void lightrec_recompiler_remove(struct recompiler *rec, struct block *block)
215{
216 struct block_rec *block_rec;
217
218 pthread_mutex_lock(&rec->mutex);
219
220 for (block_rec = rec->list; block_rec; block_rec = block_rec->next) {
221 if (block_rec->block == block) {
222 if (block == rec->current_block) {
223 /* Block is being recompiled - wait for
224 * completion */
225 do {
226 pthread_cond_wait(&rec->cond,
227 &rec->mutex);
228 } while (block == rec->current_block);
229 } else {
230 /* Block is not yet being processed - remove it
231 * from the list */
232 slist_remove(rec, block_rec);
233 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
234 sizeof(*block_rec), block_rec);
235 }
236
237 break;
238 }
239 }
240
241 pthread_mutex_unlock(&rec->mutex);
242}
243
244void * lightrec_recompiler_run_first_pass(struct block *block, u32 *pc)
245{
246 bool freed;
247
248 if (likely(block->function)) {
249 if (block->flags & BLOCK_FULLY_TAGGED) {
250 freed = atomic_flag_test_and_set(&block->op_list_freed);
251
252 if (!freed) {
253 pr_debug("Block PC 0x%08x is fully tagged"
254 " - free opcode list\n", block->pc);
255
256 /* The block was already compiled but the opcode list
257 * didn't get freed yet - do it now */
258 lightrec_free_opcode_list(block->state,
259 block->opcode_list);
260 block->opcode_list = NULL;
261 }
262 }
263
264 return block->function;
265 }
266
267 /* Mark the opcode list as freed, so that the threaded compiler won't
268 * free it while we're using it in the interpreter. */
269 freed = atomic_flag_test_and_set(&block->op_list_freed);
270
271 /* Block wasn't compiled yet - run the interpreter */
272 *pc = lightrec_emulate_block(block, *pc);
273
274 if (!freed)
275 atomic_flag_clear(&block->op_list_freed);
276
277 /* The block got compiled while the interpreter was running.
278 * We can free the opcode list now. */
279 if (block->function && (block->flags & BLOCK_FULLY_TAGGED) &&
280 !atomic_flag_test_and_set(&block->op_list_freed)) {
281 pr_debug("Block PC 0x%08x is fully tagged"
282 " - free opcode list\n", block->pc);
283
284 lightrec_free_opcode_list(block->state, block->opcode_list);
285 block->opcode_list = NULL;
286 }
287
288 return NULL;
289}