+// SPDX-License-Identifier: LGPL-2.1-or-later
/*
- * Copyright (C) 2015-2020 Paul Cercueil <paul@crapouillou.net>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
+ * Copyright (C) 2015-2021 Paul Cercueil <paul@crapouillou.net>
*/
#include "blockcache.h"
#include "debug.h"
#include "lightrec-private.h"
#include "memmanager.h"
+#include "reaper.h"
+#include "recompiler.h"
#include <stdbool.h>
#include <stdlib.h>
+#include <string.h>
/* Must be power of two */
#define LUT_SIZE 0x4000
struct block * lut[LUT_SIZE];
};
+u16 lightrec_get_lut_entry(const struct block *block)
+{
+ return (kunseg(block->pc) >> 2) & (LUT_SIZE - 1);
+}
+
struct block * lightrec_find_block(struct blockcache *cache, u32 pc)
{
struct block *block;
return NULL;
}
-void remove_from_code_lut(struct blockcache *cache, struct block *block)
+struct block * lightrec_find_block_from_lut(struct blockcache *cache,
+ u16 lut_entry, u32 addr_in_block)
{
- struct lightrec_state *state = block->state;
- const struct opcode *op;
- u32 offset = lut_offset(block->pc);
+ struct block *block;
+ u32 pc;
- /* Use state->get_next_block in the code LUT, which basically
- * calls back get_next_block_func(), until the compiler
- * overrides this. This is required, as a NULL value in the code
- * LUT means an outdated block. */
- state->code_lut[offset] = state->get_next_block;
+ addr_in_block = kunseg(addr_in_block);
- for (op = block->opcode_list; op; op = op->next)
- if (op->c.i.op == OP_META_SYNC)
- state->code_lut[offset + op->offset] = NULL;
+ for (block = cache->lut[lut_entry]; block; block = block->next) {
+ pc = kunseg(block->pc);
+ if (addr_in_block >= pc &&
+ addr_in_block < pc + (block->nb_ops << 2))
+ return block;
+ }
+ return NULL;
+}
+
+void remove_from_code_lut(struct blockcache *cache, struct block *block)
+{
+ struct lightrec_state *state = cache->state;
+ u32 offset = lut_offset(block->pc);
+
+ if (block->function) {
+ memset(lut_address(state, offset), 0,
+ block->nb_ops * lut_elm_size(state));
+ }
}
void lightrec_register_block(struct blockcache *cache, struct block *block)
}
}
- pr_err("Block at PC 0x%x is not in cache\n", block->pc);
+ pr_err("Block at "PC_FMT" is not in cache\n", block->pc);
}
-void lightrec_free_block_cache(struct blockcache *cache)
+static bool lightrec_block_is_old(const struct lightrec_state *state,
+ const struct block *block)
+{
+ u32 diff = state->current_cycle - block->precompile_date;
+
+ return diff > (1 << 27); /* About 4 seconds */
+}
+
+static void lightrec_free_blocks(struct blockcache *cache,
+ const struct block *except, bool all)
{
+ struct lightrec_state *state = cache->state;
struct block *block, *next;
+ bool outdated = all;
unsigned int i;
+ u8 old_flags;
for (i = 0; i < LUT_SIZE; i++) {
for (block = cache->lut[i]; block; block = next) {
next = block->next;
- lightrec_free_block(block);
+
+ if (except && block == except)
+ continue;
+
+ if (!all) {
+ outdated = lightrec_block_is_old(state, block) ||
+ lightrec_block_is_outdated(state, block);
+ }
+
+ if (!outdated)
+ continue;
+
+ old_flags = block_set_flags(block, BLOCK_IS_DEAD);
+
+ if (!(old_flags & BLOCK_IS_DEAD)) {
+ if (ENABLE_THREADED_COMPILER)
+ lightrec_recompiler_remove(state->rec, block);
+
+ pr_debug("Freeing outdated block at "PC_FMT"\n", block->pc);
+ remove_from_code_lut(cache, block);
+ lightrec_unregister_block(cache, block);
+ lightrec_free_block(state, block);
+ }
}
}
+}
+
+void lightrec_remove_outdated_blocks(struct blockcache *cache,
+ const struct block *except)
+{
+ pr_info("Running out of code space. Cleaning block cache...\n");
+
+ lightrec_free_blocks(cache, except, false);
+}
+void lightrec_free_block_cache(struct blockcache *cache)
+{
+ lightrec_free_blocks(cache, NULL, true);
lightrec_free(cache->state, MEM_FOR_LIGHTREC, sizeof(*cache), cache);
}
u32 lightrec_calculate_block_hash(const struct block *block)
{
- const struct lightrec_mem_map *map = block->map;
- u32 pc, hash = 0xffffffff;
- const u32 *code;
+ const u32 *code = block->code;
+ u32 hash = 0xffffffff;
unsigned int i;
- pc = kunseg(block->pc) - map->pc;
-
- while (map->mirror_of)
- map = map->mirror_of;
-
- code = map->address + pc;
-
/* Jenkins one-at-a-time hash algorithm */
for (i = 0; i < block->nb_ops; i++) {
hash += *code++;
return hash;
}
-bool lightrec_block_is_outdated(struct block *block)
+static void lightrec_reset_lut_offset(struct lightrec_state *state, void *d)
{
- void **lut_entry = &block->state->code_lut[lut_offset(block->pc)];
+ u32 pc = (u32)(uintptr_t) d;
+ struct block *block;
+ void *addr;
+
+ block = lightrec_find_block(state->block_cache, pc);
+ if (!block)
+ return;
+
+ if (block_has_flag(block, BLOCK_IS_DEAD))
+ return;
+
+ addr = block->function ?: state->get_next_block;
+ lut_write(state, lut_offset(pc), addr);
+}
+
+bool lightrec_block_is_outdated(struct lightrec_state *state, struct block *block)
+{
+ u32 offset = lut_offset(block->pc);
bool outdated;
- if (*lut_entry)
+ if (lut_read(state, offset))
return false;
outdated = block->hash != lightrec_calculate_block_hash(block);
if (likely(!outdated)) {
/* The block was marked as outdated, but the content is still
* the same */
- if (block->function)
- *lut_entry = block->function;
- else
- *lut_entry = block->state->get_next_block;
+
+ if (ENABLE_THREADED_COMPILER) {
+ /*
+ * When compiling a block that covers ours, the threaded
+ * compiler will set the LUT entries of the various
+ * entry points. Therefore we cannot write the LUT here,
+ * as we would risk overwriting the new entry points.
+ * Leave it to the reaper to re-install the LUT entries.
+ */
+
+ lightrec_reaper_add(state->reaper,
+ lightrec_reset_lut_offset,
+ (void *)(uintptr_t) block->pc);
+ } else if (block->function) {
+ lut_write(state, offset, block->function);
+ } else {
+ lut_write(state, offset, state->get_next_block);
+ }
}
return outdated;