Merge pull request #542 from gameblabla/mdec_fix
[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 #include "slist.h"
20
21 #include <errno.h>
22 #include <stdatomic.h>
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <pthread.h>
26
27 struct block_rec {
28         struct block *block;
29         struct slist_elm slist;
30 };
31
32 struct 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;
39         struct slist_elm slist;
40 };
41
42 static void lightrec_compile_list(struct recompiler *rec)
43 {
44         struct block_rec *block_rec;
45         struct slist_elm *next;
46         struct block *block;
47         int ret;
48
49         while (!!(next = slist_first(&rec->slist))) {
50                 block_rec = container_of(next, struct block_rec, slist);
51                 block = block_rec->block;
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
64                 slist_remove(&rec->slist, next);
65                 lightrec_free(rec->state, MEM_FOR_LIGHTREC,
66                               sizeof(*block_rec), block_rec);
67                 pthread_cond_signal(&rec->cond);
68         }
69
70         rec->current_block = NULL;
71 }
72
73 static void * lightrec_recompiler_thd(void *d)
74 {
75         struct recompiler *rec = d;
76
77         pthread_mutex_lock(&rec->mutex);
78
79         while (!rec->stop) {
80                 do {
81                         pthread_cond_wait(&rec->cond, &rec->mutex);
82
83                         if (rec->stop)
84                                 goto out_unlock;
85
86                 } while (slist_empty(&rec->slist));
87
88                 lightrec_compile_list(rec);
89         }
90
91 out_unlock:
92         pthread_mutex_unlock(&rec->mutex);
93         return NULL;
94 }
95
96 struct 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;
110         slist_init(&rec->slist);
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
132 err_mtx_destroy:
133         pthread_mutex_destroy(&rec->mutex);
134 err_cnd_destroy:
135         pthread_cond_destroy(&rec->cond);
136 err_free_rec:
137         lightrec_free(state, MEM_FOR_LIGHTREC, sizeof(*rec), rec);
138         return NULL;
139 }
140
141 void 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
156 int lightrec_recompiler_add(struct recompiler *rec, struct block *block)
157 {
158         struct slist_elm *elm, *prev;
159         struct block_rec *block_rec;
160         int ret = 0;
161
162         pthread_mutex_lock(&rec->mutex);
163
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
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, 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);
180                         }
181
182                         goto out_unlock;
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. */
188         if (block->function && !(block->flags & BLOCK_SHOULD_RECOMPILE))
189                 goto out_unlock;
190
191         block_rec = lightrec_malloc(rec->state, MEM_FOR_LIGHTREC,
192                                     sizeof(*block_rec));
193         if (!block_rec) {
194                 ret = -ENOMEM;
195                 goto out_unlock;
196         }
197
198         pr_debug("Adding block PC 0x%x to recompiler\n", block->pc);
199
200         block_rec->block = block;
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);
210
211         /* Signal the thread */
212         pthread_cond_signal(&rec->cond);
213
214 out_unlock:
215         pthread_mutex_unlock(&rec->mutex);
216         return ret;
217 }
218
219 void lightrec_recompiler_remove(struct recompiler *rec, struct block *block)
220 {
221         struct block_rec *block_rec;
222         struct slist_elm *elm;
223
224         pthread_mutex_lock(&rec->mutex);
225
226         for (elm = slist_first(&rec->slist); elm; elm = elm->next) {
227                 block_rec = container_of(elm, struct block_rec, slist);
228
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 */
240                                 slist_remove(&rec->slist, elm);
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
252 void * 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 }