Commit | Line | Data |
---|---|---|
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 | ||
27 | struct block_rec { | |
28 | struct block *block; | |
a59e5536 | 29 | struct slist_elm slist; |
d16005f8 PC |
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; | |
a59e5536 | 39 | struct slist_elm slist; |
d16005f8 PC |
40 | }; |
41 | ||
d16005f8 PC |
42 | static 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 | ||
73 | static void * lightrec_recompiler_thd(void *d) | |
74 | { | |
75 | struct recompiler *rec = d; | |
76 | ||
77 | pthread_mutex_lock(&rec->mutex); | |
78 | ||
ea17432f | 79 | while (!rec->stop) { |
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); | |
ea17432f | 89 | } |
a59e5536 | 90 | |
91 | out_unlock: | |
92 | pthread_mutex_unlock(&rec->mutex); | |
93 | return NULL; | |
d16005f8 PC |
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; | |
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 | ||
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 | { | |
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 | 214 | out_unlock: |
215 | pthread_mutex_unlock(&rec->mutex); | |
216 | return ret; | |
d16005f8 PC |
217 | } |
218 | ||
219 | void 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 | ||
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 | } |