aboutsummaryrefslogtreecommitdiff
path: root/deps/lightrec/regcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'deps/lightrec/regcache.c')
-rw-r--r--deps/lightrec/regcache.c498
1 files changed, 498 insertions, 0 deletions
diff --git a/deps/lightrec/regcache.c b/deps/lightrec/regcache.c
new file mode 100644
index 0000000..0256015
--- /dev/null
+++ b/deps/lightrec/regcache.c
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2014-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.
+ */
+
+#include "debug.h"
+#include "memmanager.h"
+#include "regcache.h"
+
+#include <lightning.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+struct native_register {
+ bool used, loaded, dirty, output, extend, extended, locked;
+ s8 emulated_register;
+};
+
+struct regcache {
+ struct lightrec_state *state;
+ struct native_register lightrec_regs[NUM_REGS + NUM_TEMPS];
+};
+
+static const char * mips_regs[] = {
+ "zero",
+ "at",
+ "v0", "v1",
+ "a0", "a1", "a2", "a3",
+ "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
+ "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
+ "t8", "t9",
+ "k0", "k1",
+ "gp", "sp", "fp", "ra",
+ "lo", "hi",
+};
+
+const char * lightrec_reg_name(u8 reg)
+{
+ return mips_regs[reg];
+}
+
+static inline u8 lightrec_reg_number(const struct regcache *cache,
+ const struct native_register *nreg)
+{
+ return (u8) (((uintptr_t) nreg - (uintptr_t) cache->lightrec_regs)
+ / sizeof(*nreg));
+}
+
+static inline u8 lightrec_reg_to_lightning(const struct regcache *cache,
+ const struct native_register *nreg)
+{
+ u8 offset = lightrec_reg_number(cache, nreg);
+ return offset < NUM_REGS ? JIT_V(offset) : JIT_R(offset - NUM_REGS);
+}
+
+static inline struct native_register * lightning_reg_to_lightrec(
+ struct regcache *cache, u8 reg)
+{
+ if ((JIT_V0 > JIT_R0 && reg >= JIT_V0) ||
+ (JIT_V0 < JIT_R0 && reg < JIT_R0)) {
+ if (JIT_V1 > JIT_V0)
+ return &cache->lightrec_regs[reg - JIT_V0];
+ else
+ return &cache->lightrec_regs[JIT_V0 - reg];
+ } else {
+ if (JIT_R1 > JIT_R0)
+ return &cache->lightrec_regs[NUM_REGS + reg - JIT_R0];
+ else
+ return &cache->lightrec_regs[NUM_REGS + JIT_R0 - reg];
+ }
+}
+
+static struct native_register * alloc_temp(struct regcache *cache)
+{
+ unsigned int i;
+
+ /* We search the register list in reverse order. As temporaries are
+ * meant to be used only in the emitter functions, they can be mapped to
+ * caller-saved registers, as they won't have to be saved back to
+ * memory. */
+ for (i = ARRAY_SIZE(cache->lightrec_regs); i; i--) {
+ struct native_register *nreg = &cache->lightrec_regs[i - 1];
+ if (!nreg->used && !nreg->loaded && !nreg->dirty)
+ return nreg;
+ }
+
+ for (i = ARRAY_SIZE(cache->lightrec_regs); i; i--) {
+ struct native_register *nreg = &cache->lightrec_regs[i - 1];
+ if (!nreg->used)
+ return nreg;
+ }
+
+ return NULL;
+}
+
+static struct native_register * find_mapped_reg(struct regcache *cache,
+ u8 reg, bool out)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(cache->lightrec_regs); i++) {
+ struct native_register *nreg = &cache->lightrec_regs[i];
+ if ((!reg || nreg->loaded || nreg->dirty) &&
+ nreg->emulated_register == reg &&
+ (!out || !nreg->locked))
+ return nreg;
+ }
+
+ return NULL;
+}
+
+static struct native_register * alloc_in_out(struct regcache *cache,
+ u8 reg, bool out)
+{
+ struct native_register *nreg;
+ unsigned int i;
+
+ /* Try to find if the register is already mapped somewhere */
+ nreg = find_mapped_reg(cache, reg, out);
+ if (nreg)
+ return nreg;
+
+ /* Try to allocate a non-dirty, non-loaded register.
+ * Loaded registers may be re-used later, so it's better to avoid
+ * re-using one if possible. */
+ for (i = 0; i < ARRAY_SIZE(cache->lightrec_regs); i++) {
+ nreg = &cache->lightrec_regs[i];
+ if (!nreg->used && !nreg->dirty && !nreg->loaded)
+ return nreg;
+ }
+
+ /* Try to allocate a non-dirty register */
+ for (i = 0; i < ARRAY_SIZE(cache->lightrec_regs); i++) {
+ nreg = &cache->lightrec_regs[i];
+ if (!nreg->used && !nreg->dirty)
+ return nreg;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cache->lightrec_regs); i++) {
+ nreg = &cache->lightrec_regs[i];
+ if (!nreg->used)
+ return nreg;
+ }
+
+ return NULL;
+}
+
+static void lightrec_discard_nreg(struct native_register *nreg)
+{
+ nreg->extended = false;
+ nreg->loaded = false;
+ nreg->output = false;
+ nreg->dirty = false;
+ nreg->used = false;
+ nreg->locked = false;
+ nreg->emulated_register = -1;
+}
+
+static void lightrec_unload_nreg(struct regcache *cache, jit_state_t *_jit,
+ struct native_register *nreg, u8 jit_reg)
+{
+ /* If we get a dirty register, store back the old value */
+ if (nreg->dirty) {
+ s16 offset = offsetof(struct lightrec_state, native_reg_cache)
+ + (nreg->emulated_register << 2);
+
+ jit_stxi_i(offset, LIGHTREC_REG_STATE, jit_reg);
+ }
+
+ lightrec_discard_nreg(nreg);
+}
+
+void lightrec_unload_reg(struct regcache *cache, jit_state_t *_jit, u8 jit_reg)
+{
+ lightrec_unload_nreg(cache, _jit,
+ lightning_reg_to_lightrec(cache, jit_reg), jit_reg);
+}
+
+/* lightrec_lock_reg: the register will be cleaned if dirty, then locked.
+ * A locked register cannot only be used as input, not output. */
+void lightrec_lock_reg(struct regcache *cache, jit_state_t *_jit, u8 jit_reg)
+{
+ struct native_register *reg = lightning_reg_to_lightrec(cache, jit_reg);
+
+ lightrec_clean_reg(cache, _jit, jit_reg);
+
+ reg->locked = true;
+}
+
+u8 lightrec_alloc_reg(struct regcache *cache, jit_state_t *_jit, u8 jit_reg)
+{
+ struct native_register *reg = lightning_reg_to_lightrec(cache, jit_reg);
+
+ lightrec_unload_nreg(cache, _jit, reg, jit_reg);
+
+ reg->used = true;
+ return jit_reg;
+}
+
+u8 lightrec_alloc_reg_temp(struct regcache *cache, jit_state_t *_jit)
+{
+ u8 jit_reg;
+ struct native_register *nreg = alloc_temp(cache);
+ if (!nreg) {
+ /* No free register, no dirty register to free. */
+ pr_err("No more registers! Abandon ship!\n");
+ return 0;
+ }
+
+ jit_reg = lightrec_reg_to_lightning(cache, nreg);
+ lightrec_unload_nreg(cache, _jit, nreg, jit_reg);
+
+ nreg->used = true;
+ return jit_reg;
+}
+
+u8 lightrec_alloc_reg_out(struct regcache *cache, jit_state_t *_jit, u8 reg)
+{
+ u8 jit_reg;
+ struct native_register *nreg = alloc_in_out(cache, reg, true);
+ if (!nreg) {
+ /* No free register, no dirty register to free. */
+ pr_err("No more registers! Abandon ship!\n");
+ return 0;
+ }
+
+ jit_reg = lightrec_reg_to_lightning(cache, nreg);
+
+ /* If we get a dirty register that doesn't correspond to the one
+ * we're requesting, store back the old value */
+ if (nreg->emulated_register != reg)
+ lightrec_unload_nreg(cache, _jit, nreg, jit_reg);
+
+ nreg->extend = false;
+ nreg->used = true;
+ nreg->output = true;
+ nreg->emulated_register = reg;
+ return jit_reg;
+}
+
+u8 lightrec_alloc_reg_in(struct regcache *cache, jit_state_t *_jit, u8 reg)
+{
+ u8 jit_reg;
+ bool reg_changed;
+ struct native_register *nreg = alloc_in_out(cache, reg, false);
+ if (!nreg) {
+ /* No free register, no dirty register to free. */
+ pr_err("No more registers! Abandon ship!\n");
+ return 0;
+ }
+
+ jit_reg = lightrec_reg_to_lightning(cache, nreg);
+
+ /* If we get a dirty register that doesn't correspond to the one
+ * we're requesting, store back the old value */
+ reg_changed = nreg->emulated_register != reg;
+ if (reg_changed)
+ lightrec_unload_nreg(cache, _jit, nreg, jit_reg);
+
+ if (!nreg->loaded && !nreg->dirty && reg != 0) {
+ s16 offset = offsetof(struct lightrec_state, native_reg_cache)
+ + (reg << 2);
+
+ /* Load previous value from register cache */
+ jit_ldxi_i(jit_reg, LIGHTREC_REG_STATE, offset);
+ nreg->loaded = true;
+ nreg->extended = true;
+ }
+
+ /* Clear register r0 before use */
+ if (reg == 0 && (!nreg->loaded || nreg->dirty)) {
+ jit_movi(jit_reg, 0);
+ nreg->extended = true;
+ nreg->loaded = true;
+ }
+
+ nreg->used = true;
+ nreg->output = false;
+ nreg->emulated_register = reg;
+ return jit_reg;
+}
+
+u8 lightrec_alloc_reg_out_ext(struct regcache *cache, jit_state_t *_jit, u8 reg)
+{
+ struct native_register *nreg;
+ u8 jit_reg;
+
+ jit_reg = lightrec_alloc_reg_out(cache, _jit, reg);
+ nreg = lightning_reg_to_lightrec(cache, jit_reg);
+
+ nreg->extend = true;
+
+ return jit_reg;
+}
+
+u8 lightrec_alloc_reg_in_ext(struct regcache *cache, jit_state_t *_jit, u8 reg)
+{
+ struct native_register *nreg;
+ u8 jit_reg;
+
+ jit_reg = lightrec_alloc_reg_in(cache, _jit, reg);
+ nreg = lightning_reg_to_lightrec(cache, jit_reg);
+
+#if __WORDSIZE == 64
+ if (!nreg->extended) {
+ nreg->extended = true;
+ jit_extr_i(jit_reg, jit_reg);
+ }
+#endif
+
+ return jit_reg;
+}
+
+u8 lightrec_request_reg_in(struct regcache *cache, jit_state_t *_jit,
+ u8 reg, u8 jit_reg)
+{
+ struct native_register *nreg;
+ u16 offset;
+
+ nreg = find_mapped_reg(cache, reg, false);
+ if (nreg) {
+ jit_reg = lightrec_reg_to_lightning(cache, nreg);
+ nreg->used = true;
+ return jit_reg;
+ }
+
+ nreg = lightning_reg_to_lightrec(cache, jit_reg);
+ lightrec_unload_nreg(cache, _jit, nreg, jit_reg);
+
+ /* Load previous value from register cache */
+ offset = offsetof(struct lightrec_state, native_reg_cache) + (reg << 2);
+ jit_ldxi_i(jit_reg, LIGHTREC_REG_STATE, offset);
+
+ nreg->extended = true;
+ nreg->used = true;
+ nreg->loaded = true;
+ nreg->emulated_register = reg;
+
+ return jit_reg;
+}
+
+static void free_reg(struct native_register *nreg)
+{
+ /* Set output registers as dirty */
+ if (nreg->used && nreg->output && nreg->emulated_register > 0)
+ nreg->dirty = true;
+ if (nreg->output)
+ nreg->extended = nreg->extend;
+ nreg->used = false;
+}
+
+void lightrec_free_reg(struct regcache *cache, u8 jit_reg)
+{
+ free_reg(lightning_reg_to_lightrec(cache, jit_reg));
+}
+
+void lightrec_free_regs(struct regcache *cache)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(cache->lightrec_regs); i++)
+ free_reg(&cache->lightrec_regs[i]);
+}
+
+static void clean_reg(jit_state_t *_jit,
+ struct native_register *nreg, u8 jit_reg, bool clean)
+{
+ if (nreg->dirty) {
+ s16 offset = offsetof(struct lightrec_state, native_reg_cache)
+ + (nreg->emulated_register << 2);
+
+ jit_stxi_i(offset, LIGHTREC_REG_STATE, jit_reg);
+ nreg->loaded |= nreg->dirty;
+ nreg->dirty ^= clean;
+ }
+}
+
+static void clean_regs(struct regcache *cache, jit_state_t *_jit, bool clean)
+{
+ unsigned int i;
+
+ for (i = 0; i < NUM_REGS; i++)
+ clean_reg(_jit, &cache->lightrec_regs[i], JIT_V(i), clean);
+ for (i = 0; i < NUM_TEMPS; i++) {
+ clean_reg(_jit, &cache->lightrec_regs[i + NUM_REGS],
+ JIT_R(i), clean);
+ }
+}
+
+void lightrec_storeback_regs(struct regcache *cache, jit_state_t *_jit)
+{
+ clean_regs(cache, _jit, false);
+}
+
+void lightrec_clean_regs(struct regcache *cache, jit_state_t *_jit)
+{
+ clean_regs(cache, _jit, true);
+}
+
+void lightrec_clean_reg(struct regcache *cache, jit_state_t *_jit, u8 jit_reg)
+{
+ struct native_register *reg = lightning_reg_to_lightrec(cache, jit_reg);
+ clean_reg(_jit, reg, jit_reg, true);
+}
+
+void lightrec_clean_reg_if_loaded(struct regcache *cache, jit_state_t *_jit,
+ u8 reg, bool unload)
+{
+ struct native_register *nreg;
+ u8 jit_reg;
+
+ nreg = find_mapped_reg(cache, reg, false);
+ if (nreg) {
+ jit_reg = lightrec_reg_to_lightning(cache, nreg);
+
+ if (unload)
+ lightrec_unload_nreg(cache, _jit, nreg, jit_reg);
+ else
+ clean_reg(_jit, nreg, jit_reg, true);
+ }
+}
+
+struct native_register * lightrec_regcache_enter_branch(struct regcache *cache)
+{
+ struct native_register *backup;
+
+ backup = lightrec_malloc(cache->state, MEM_FOR_LIGHTREC,
+ sizeof(cache->lightrec_regs));
+ memcpy(backup, &cache->lightrec_regs, sizeof(cache->lightrec_regs));
+
+ return backup;
+}
+
+void lightrec_regcache_leave_branch(struct regcache *cache,
+ struct native_register *regs)
+{
+ memcpy(&cache->lightrec_regs, regs, sizeof(cache->lightrec_regs));
+ lightrec_free(cache->state, MEM_FOR_LIGHTREC,
+ sizeof(cache->lightrec_regs), regs);
+}
+
+void lightrec_regcache_reset(struct regcache *cache)
+{
+ memset(&cache->lightrec_regs, 0, sizeof(cache->lightrec_regs));
+}
+
+struct regcache * lightrec_regcache_init(struct lightrec_state *state)
+{
+ struct regcache *cache;
+
+ cache = lightrec_calloc(state, MEM_FOR_LIGHTREC, sizeof(*cache));
+ if (!cache)
+ return NULL;
+
+ cache->state = state;
+
+ return cache;
+}
+
+void lightrec_free_regcache(struct regcache *cache)
+{
+ return lightrec_free(cache->state, MEM_FOR_LIGHTREC,
+ sizeof(*cache), cache);
+}
+
+void lightrec_regcache_mark_live(struct regcache *cache, jit_state_t *_jit)
+{
+ struct native_register *nreg;
+ unsigned int i;
+
+#ifdef _WIN32
+ /* FIXME: GNU Lightning on Windows seems to use our mapped registers as
+ * temporaries. Until the actual bug is found and fixed, unconditionally
+ * mark our registers as live here. */
+ for (i = 0; i < NUM_REGS; i++) {
+ nreg = &cache->lightrec_regs[i];
+
+ if (nreg->used || nreg->loaded || nreg->dirty)
+ jit_live(JIT_V(i));
+ }
+#endif
+
+ for (i = 0; i < NUM_TEMPS; i++) {
+ nreg = &cache->lightrec_regs[NUM_REGS + i];
+
+ if (nreg->used || nreg->loaded || nreg->dirty)
+ jit_live(JIT_R(i));
+ }
+}