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 
lightrec_compile_list(struct recompiler * rec)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 
lightrec_recompiler_thd(void * d)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 
lightrec_recompiler_init(struct lightrec_state * state)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 
lightrec_free_recompiler(struct recompiler * rec)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 
lightrec_recompiler_add(struct recompiler * rec,struct block * block)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 
lightrec_recompiler_remove(struct recompiler * rec,struct block * block)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 
lightrec_recompiler_run_first_pass(struct block * block,u32 * pc)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 }
298