Update lightrec 20220910 (#686)
[pcsx_rearmed.git] / deps / lightrec / blockcache.c
CommitLineData
98fa08a5 1// SPDX-License-Identifier: LGPL-2.1-or-later
d16005f8 2/*
98fa08a5 3 * Copyright (C) 2015-2021 Paul Cercueil <paul@crapouillou.net>
d16005f8
PC
4 */
5
6#include "blockcache.h"
7#include "debug.h"
8#include "lightrec-private.h"
9#include "memmanager.h"
ba3814c1
PC
10#include "reaper.h"
11#include "recompiler.h"
d16005f8
PC
12
13#include <stdbool.h>
14#include <stdlib.h>
98fa08a5 15#include <string.h>
d16005f8
PC
16
17/* Must be power of two */
18#define LUT_SIZE 0x4000
19
20struct blockcache {
21 struct lightrec_state *state;
22 struct block * lut[LUT_SIZE];
23};
24
98fa08a5
PC
25u16 lightrec_get_lut_entry(const struct block *block)
26{
27 return (kunseg(block->pc) >> 2) & (LUT_SIZE - 1);
28}
29
d16005f8
PC
30struct block * lightrec_find_block(struct blockcache *cache, u32 pc)
31{
32 struct block *block;
33
34 pc = kunseg(pc);
35
36 for (block = cache->lut[(pc >> 2) & (LUT_SIZE - 1)];
37 block; block = block->next)
38 if (kunseg(block->pc) == pc)
39 return block;
40
41 return NULL;
42}
43
98fa08a5
PC
44struct block * lightrec_find_block_from_lut(struct blockcache *cache,
45 u16 lut_entry, u32 addr_in_block)
d16005f8 46{
98fa08a5
PC
47 struct block *block;
48 u32 pc;
d16005f8 49
98fa08a5 50 addr_in_block = kunseg(addr_in_block);
d16005f8 51
98fa08a5
PC
52 for (block = cache->lut[lut_entry]; block; block = block->next) {
53 pc = kunseg(block->pc);
54 if (addr_in_block >= pc &&
55 addr_in_block < pc + (block->nb_ops << 2))
56 return block;
57 }
d16005f8 58
98fa08a5
PC
59 return NULL;
60}
61
62void remove_from_code_lut(struct blockcache *cache, struct block *block)
63{
64 struct lightrec_state *state = cache->state;
65 u32 offset = lut_offset(block->pc);
66
67 if (block->function) {
02487de7
PC
68 memset(lut_address(state, offset), 0,
69 block->nb_ops * lut_elm_size(state));
98fa08a5 70 }
d16005f8
PC
71}
72
d16005f8
PC
73void lightrec_register_block(struct blockcache *cache, struct block *block)
74{
75 u32 pc = kunseg(block->pc);
76 struct block *old;
77
78 old = cache->lut[(pc >> 2) & (LUT_SIZE - 1)];
79 if (old)
80 block->next = old;
81
82 cache->lut[(pc >> 2) & (LUT_SIZE - 1)] = block;
83
84 remove_from_code_lut(cache, block);
85}
86
87void lightrec_unregister_block(struct blockcache *cache, struct block *block)
88{
89 u32 pc = kunseg(block->pc);
90 struct block *old = cache->lut[(pc >> 2) & (LUT_SIZE - 1)];
91
d16005f8
PC
92 if (old == block) {
93 cache->lut[(pc >> 2) & (LUT_SIZE - 1)] = old->next;
94 return;
95 }
96
97 for (; old; old = old->next) {
98 if (old->next == block) {
99 old->next = block->next;
100 return;
101 }
102 }
103
104 pr_err("Block at PC 0x%x is not in cache\n", block->pc);
105}
106
d8b04acd
PC
107static bool lightrec_block_is_old(const struct lightrec_state *state,
108 const struct block *block)
109{
110 u32 diff = state->current_cycle - block->precompile_date;
111
112 return diff > (1 << 27); /* About 4 seconds */
113}
114
115static void lightrec_free_blocks(struct blockcache *cache,
116 const struct block *except, bool all)
d16005f8 117{
d8b04acd 118 struct lightrec_state *state = cache->state;
d16005f8 119 struct block *block, *next;
d8b04acd 120 bool outdated = all;
d16005f8 121 unsigned int i;
ba3814c1 122 u8 old_flags;
d16005f8
PC
123
124 for (i = 0; i < LUT_SIZE; i++) {
125 for (block = cache->lut[i]; block; block = next) {
126 next = block->next;
d8b04acd
PC
127
128 if (except && block == except)
129 continue;
130
131 if (!all) {
132 outdated = lightrec_block_is_old(state, block) ||
133 lightrec_block_is_outdated(state, block);
134 }
135
ba3814c1
PC
136 if (!outdated)
137 continue;
138
139 old_flags = block_set_flags(block, BLOCK_IS_DEAD);
140
141 if (!(old_flags & BLOCK_IS_DEAD)) {
142 if (ENABLE_THREADED_COMPILER)
143 lightrec_recompiler_remove(state->rec, block);
144
d8b04acd
PC
145 pr_debug("Freeing outdated block at PC 0x%08x\n", block->pc);
146 remove_from_code_lut(cache, block);
147 lightrec_unregister_block(cache, block);
148 lightrec_free_block(state, block);
149 }
d16005f8
PC
150 }
151 }
d8b04acd
PC
152}
153
154void lightrec_remove_outdated_blocks(struct blockcache *cache,
155 const struct block *except)
156{
157 pr_info("Running out of code space. Cleaning block cache...\n");
d16005f8 158
d8b04acd
PC
159 lightrec_free_blocks(cache, except, false);
160}
161
162void lightrec_free_block_cache(struct blockcache *cache)
163{
164 lightrec_free_blocks(cache, NULL, true);
d16005f8
PC
165 lightrec_free(cache->state, MEM_FOR_LIGHTREC, sizeof(*cache), cache);
166}
167
168struct blockcache * lightrec_blockcache_init(struct lightrec_state *state)
169{
170 struct blockcache *cache;
171
172 cache = lightrec_calloc(state, MEM_FOR_LIGHTREC, sizeof(*cache));
173 if (!cache)
174 return NULL;
175
176 cache->state = state;
177
178 return cache;
179}
180
181u32 lightrec_calculate_block_hash(const struct block *block)
182{
98fa08a5
PC
183 const u32 *code = block->code;
184 u32 hash = 0xffffffff;
d16005f8
PC
185 unsigned int i;
186
d16005f8
PC
187 /* Jenkins one-at-a-time hash algorithm */
188 for (i = 0; i < block->nb_ops; i++) {
189 hash += *code++;
190 hash += (hash << 10);
191 hash ^= (hash >> 6);
192 }
193
194 hash += (hash << 3);
195 hash ^= (hash >> 11);
196 hash += (hash << 15);
197
198 return hash;
199}
200
ba3814c1
PC
201static void lightrec_reset_lut_offset(struct lightrec_state *state, void *d)
202{
203 u32 pc = (u32)(uintptr_t) d;
204 struct block *block;
205 void *addr;
206
207 block = lightrec_find_block(state->block_cache, pc);
208 if (!block)
209 return;
210
211 if (block_has_flag(block, BLOCK_IS_DEAD))
212 return;
213
214 addr = block->function ?: state->get_next_block;
215 lut_write(state, lut_offset(pc), addr);
216}
217
98fa08a5 218bool lightrec_block_is_outdated(struct lightrec_state *state, struct block *block)
d16005f8 219{
02487de7 220 u32 offset = lut_offset(block->pc);
d16005f8
PC
221 bool outdated;
222
02487de7 223 if (lut_read(state, offset))
d16005f8
PC
224 return false;
225
226 outdated = block->hash != lightrec_calculate_block_hash(block);
227 if (likely(!outdated)) {
228 /* The block was marked as outdated, but the content is still
229 * the same */
02487de7 230
ba3814c1
PC
231 if (ENABLE_THREADED_COMPILER) {
232 /*
233 * When compiling a block that covers ours, the threaded
234 * compiler will set the LUT entries of the various
235 * entry points. Therefore we cannot write the LUT here,
236 * as we would risk overwriting the new entry points.
237 * Leave it to the reaper to re-install the LUT entries.
238 */
239
240 lightrec_reaper_add(state->reaper,
241 lightrec_reset_lut_offset,
242 (void *)(uintptr_t) block->pc);
243 } else if (block->function) {
244 lut_write(state, offset, block->function);
245 } else {
246 lut_write(state, offset, state->get_next_block);
247 }
d16005f8
PC
248 }
249
250 return outdated;
251}