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" | |
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 | } |