git subrepo clone https://github.com/pcercuei/lightrec.git deps/lightrec
[pcsx_rearmed.git] / deps / lightrec / recompiler.c
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
26 struct block_rec {
27         struct block *block;
28         struct block_rec *next;
29 };
30
31 struct 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
41 static 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
55 static 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
84 static 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
105 struct 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
141 err_mtx_destroy:
142         pthread_mutex_destroy(&rec->mutex);
143 err_cnd_destroy:
144         pthread_cond_destroy(&rec->cond);
145 err_free_rec:
146         lightrec_free(state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
147         return NULL;
148 }
149
150 void 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
165 int 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
214 void 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
244 void * 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 }