Update lightrec 20220910 (#686)
[pcsx_rearmed.git] / deps / lightrec / blockcache.c
index 833a8e1..bb58cdb 100644 (file)
@@ -1,24 +1,18 @@
+// 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
@@ -28,6 +22,11 @@ struct blockcache {
        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;
@@ -42,28 +41,33 @@ struct block * lightrec_find_block(struct blockcache *cache, u32 pc)
        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 lightrec_mark_for_recompilation(struct blockcache *cache,
-                                    struct block *block)
+void remove_from_code_lut(struct blockcache *cache, struct block *block)
 {
-       block->flags |= BLOCK_SHOULD_RECOMPILE;
+       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)
@@ -85,8 +89,6 @@ void lightrec_unregister_block(struct blockcache *cache, struct block *block)
        u32 pc = kunseg(block->pc);
        struct block *old = cache->lut[(pc >> 2) & (LUT_SIZE - 1)];
 
-       remove_from_code_lut(cache, block);
-
        if (old == block) {
                cache->lut[(pc >> 2) & (LUT_SIZE - 1)] = old->next;
                return;
@@ -102,18 +104,64 @@ void lightrec_unregister_block(struct blockcache *cache, struct block *block)
        pr_err("Block at PC 0x%x 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 0x%08x\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);
 }
 
@@ -132,18 +180,10 @@ struct blockcache * lightrec_blockcache_init(struct lightrec_state *state)
 
 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++;
@@ -158,22 +198,53 @@ u32 lightrec_calculate_block_hash(const struct block *block)
        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;