summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2024-02-29 02:39:32 +0100
committerPaul Oliver <contact@pauloliver.dev>2024-04-16 02:28:53 +0200
commit5daf52d92c472ebf2a675cb2d27ca3e3fbdf0034 (patch)
tree36f58b9fd17f38724ff5a5263ac9a326a857f76e /src
Initial
Diffstat (limited to 'src')
-rw-r--r--src/arch/dummy.c147
-rw-r--r--src/bench.c49
-rw-r--r--src/graphics.c223
-rw-r--r--src/salis.c661
-rw-r--r--src/ui/curses.c944
-rw-r--r--src/ui/daemon.c63
6 files changed, 2087 insertions, 0 deletions
diff --git a/src/arch/dummy.c b/src/arch/dummy.c
new file mode 100644
index 0000000..b440be3
--- /dev/null
+++ b/src/arch/dummy.c
@@ -0,0 +1,147 @@
+// Project: Salis
+// Author: Paul Oliver
+// Email: contact@pauloliver.dev
+
+/*
+ * Defines a minimal viable architecture for the Salis VM. Useful for
+ * debugging and benchmarking. Also, this file can be used as a template when
+ * implementing a real architecture.
+ */
+
+bool proc_is_live(const Core *core, u64 pix);
+
+#define PROC_FIELDS \
+ PROC_FIELD(u64, dmmy)
+
+struct Proc {
+#define PROC_FIELD(type, name) type name;
+ PROC_FIELDS
+#undef PROC_FIELD
+};
+
+#define MNEMONIC_BUFF_SIZE (0x10)
+
+const wchar_t *g_arch_byte_symbols = (
+ L"⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟"
+ L"⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿"
+ L"⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟"
+ L"⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿"
+);
+
+u64 arch_proc_mb0_addr(const Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 0;
+}
+
+u64 arch_proc_mb0_size(const Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 0;
+}
+
+u64 arch_proc_mb1_addr(const Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 0;
+}
+
+u64 arch_proc_mb1_size(const Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 0;
+}
+
+u64 arch_proc_ip_addr(const Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 0;
+}
+
+u64 arch_proc_sp_addr(const Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 0;
+}
+
+u64 arch_proc_slice(const Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 1;
+}
+
+void arch_on_proc_kill(Core *core) {
+ assert(core);
+ assert(core->pnum > 1);
+
+ (void)core;
+}
+
+#if ACTION == ACT_BENCH || ACTION == ACT_NEW
+void arch_anc_init(Core *core, u64 size) {
+ assert(core);
+
+ (void)core;
+ (void)size;
+}
+#endif
+
+void arch_proc_step(Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return;
+}
+
+#ifndef NDEBUG
+void arch_validate_proc(const Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ assert(true);
+}
+#endif
+
+wchar_t arch_symbol(u8 inst) {
+ return g_arch_byte_symbols[inst];
+}
+
+void arch_mnemonic(u8 inst, char *buff) {
+ assert(buff);
+
+ snprintf(buff, MNEMONIC_BUFF_SIZE, "dummy %#x", inst);
+}
diff --git a/src/bench.c b/src/bench.c
new file mode 100644
index 0000000..eb7e0e5
--- /dev/null
+++ b/src/bench.c
@@ -0,0 +1,49 @@
+// Project: Salis
+// Author: Paul Oliver
+// Email: contact@pauloliver.dev
+
+/*
+ * Simple benchmark test helps measure simulation speed by stepping the
+ * simulator N times and printing results.
+ */
+
+#if ACTION != ACT_BENCH
+#error Using bench UI with unsupported action
+#endif
+
+int main() {
+ printf("Salis Benchmark Test\n\n");
+
+ salis_init("", SEED);
+ salis_step(BENCH_STEPS);
+
+ printf("seed => %#lx\n", SEED);
+ printf("g_steps => %#lx\n", g_steps);
+ printf("g_syncs => %#lx\n", g_syncs);
+
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ putchar('\n');
+ printf("core %d mall => %#lx\n", i, g_cores[i].mall);
+ printf("core %d mut0 => %#lx\n", i, g_cores[i].muta[0]);
+ printf("core %d mut1 => %#lx\n", i, g_cores[i].muta[1]);
+ printf("core %d mut2 => %#lx\n", i, g_cores[i].muta[2]);
+ printf("core %d mut3 => %#lx\n", i, g_cores[i].muta[3]);
+ printf("core %d pnum => %#lx\n", i, g_cores[i].pnum);
+ printf("core %d pcap => %#lx\n", i, g_cores[i].pcap);
+ printf("core %d pfst => %#lx\n", i, g_cores[i].pfst);
+ printf("core %d plst => %#lx\n", i, g_cores[i].plst);
+ printf("core %d pcur => %#lx\n", i, g_cores[i].pcur);
+ printf("core %d psli => %#lx\n", i, g_cores[i].psli);
+ printf("core %d ncyc => %#lx\n", i, g_cores[i].ncyc);
+ printf("core %d ivpt => %#lx\n", i, g_cores[i].ivpt);
+ putchar('\n');
+
+ for (int j = 0; j < 32; ++j) {
+ printf("%02x ", g_cores[i].mvec[j]);
+ }
+
+ putchar('\n');
+ }
+
+ salis_free();
+}
diff --git a/src/graphics.c b/src/graphics.c
new file mode 100644
index 0000000..8114f30
--- /dev/null
+++ b/src/graphics.c
@@ -0,0 +1,223 @@
+// Project: Salis
+// Author: Paul Oliver
+// Email: contact@pauloliver.dev
+
+/*
+ * This module renders the contents of the VM memory buffer into a 7 channel
+ * image. It supports zooming in and out, condensing the state of several
+ * bytes of memory into single pixels, when zoomed out. When zoomed in, each
+ * pixel represents a single byte in memory.
+ */
+
+u64 g_gfx_vsiz; // zoom level
+
+u64 *g_gfx_inst; // instruction channel
+u64 *g_gfx_mall; // allocated state channel
+u64 *g_gfx_mbst; // memory block start channel
+u64 *g_gfx_mb0s; // selected organism's memory block #1 channel
+u64 *g_gfx_mb1s; // selected organism's memory block #2 channel
+u64 *g_gfx_ipas; // selected organism's IP channel
+u64 *g_gfx_spas; // selected organism's SP channel
+
+void gfx_init(u64 vsiz) {
+ assert(vsiz);
+
+ g_gfx_vsiz = vsiz;
+
+ g_gfx_inst = calloc(g_gfx_vsiz, sizeof(u64));
+ g_gfx_mall = calloc(g_gfx_vsiz, sizeof(u64));
+ g_gfx_mbst = calloc(g_gfx_vsiz, sizeof(u64));
+ g_gfx_mb0s = calloc(g_gfx_vsiz, sizeof(u64));
+ g_gfx_mb1s = calloc(g_gfx_vsiz, sizeof(u64));
+ g_gfx_ipas = calloc(g_gfx_vsiz, sizeof(u64));
+ g_gfx_spas = calloc(g_gfx_vsiz, sizeof(u64));
+
+ assert(g_gfx_inst);
+ assert(g_gfx_mall);
+ assert(g_gfx_mbst);
+ assert(g_gfx_mb0s);
+ assert(g_gfx_mb1s);
+ assert(g_gfx_ipas);
+ assert(g_gfx_spas);
+}
+
+void gfx_free() {
+ if (g_gfx_vsiz == 0) {
+ return;
+ }
+
+ assert(g_gfx_inst);
+ assert(g_gfx_mall);
+ assert(g_gfx_mbst);
+ assert(g_gfx_mb0s);
+ assert(g_gfx_mb1s);
+ assert(g_gfx_ipas);
+ assert(g_gfx_spas);
+
+ g_gfx_vsiz = 0;
+
+ free(g_gfx_inst);
+ free(g_gfx_mall);
+ free(g_gfx_mbst);
+ free(g_gfx_mb0s);
+ free(g_gfx_mb1s);
+ free(g_gfx_ipas);
+ free(g_gfx_spas);
+
+ g_gfx_inst = NULL;
+ g_gfx_mall = NULL;
+ g_gfx_mbst = NULL;
+ g_gfx_mb0s = NULL;
+ g_gfx_mb1s = NULL;
+ g_gfx_ipas = NULL;
+ g_gfx_spas = NULL;
+}
+
+void gfx_resize(u64 vsiz) {
+ assert(vsiz);
+
+ gfx_free();
+ gfx_init(vsiz);
+}
+
+void gfx_render_inst(const Core *core, u64 pos, u64 zoom) {
+ assert(core);
+
+ for (u64 i = 0; i < g_gfx_vsiz; ++i) {
+ g_gfx_inst[i] = 0;
+ g_gfx_mall[i] = 0;
+
+ for (u64 j = 0; j < zoom; ++j) {
+ u64 addr = pos + (i * zoom) + j;
+ u8 byte = mvec_get_byte(core, addr);
+
+ g_gfx_inst[i] += byte;
+ g_gfx_mall[i] += (byte & MALL_FLAG) ? 1 : 0;
+ }
+ }
+}
+
+void gfx_clear_array(u64 *arry) {
+ assert(arry);
+ memset(arry, 0, g_gfx_vsiz * sizeof(u64));
+}
+
+void gfx_accumulate_pixel(u64 pos, u64 zoom, u64 pixa, u64 *arry) {
+ assert(arry);
+
+ u64 beg_mod = pos % MVEC_SIZE;
+ u64 end_mod = beg_mod + (g_gfx_vsiz * zoom);
+ u64 pix_mod = pixa % MVEC_SIZE;
+
+#ifndef NDEBUG
+ u64 inc_cnt = 0;
+#endif
+
+ while (pix_mod < end_mod) {
+ if (pix_mod >= beg_mod && pix_mod < end_mod) {
+ u64 pixi = (pix_mod - beg_mod) / zoom;
+ assert(pixi < g_gfx_vsiz);
+ arry[pixi]++;
+
+#ifndef NDEBUG
+ inc_cnt++;
+#endif
+ }
+
+ pix_mod += MVEC_SIZE;
+ }
+
+
+#ifndef NDEBUG
+ if (zoom != 1) {
+ assert(inc_cnt <= 2);
+ }
+#endif
+}
+
+void gfx_render_mbst(const Core *core, u64 pos, u64 zoom) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_mbst);
+
+ for (u64 pix = core->pfst; pix <= core->plst; ++pix) {
+ u64 mb0a = arch_proc_mb0_addr(core, pix);
+ u64 mb1a = arch_proc_mb1_addr(core, pix);
+
+ gfx_accumulate_pixel(pos, zoom, mb0a, g_gfx_mbst);
+ gfx_accumulate_pixel(pos, zoom, mb1a, g_gfx_mbst);
+ }
+}
+
+void gfx_render_mb0s(const Core *core, u64 pos, u64 zoom, u64 psel) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_mb0s);
+
+ if (psel < core->pfst || psel > core->plst) {
+ return;
+ }
+
+ u64 mb0a = arch_proc_mb0_addr(core, psel);
+ u64 mb0s = arch_proc_mb0_size(core, psel);
+
+ for (u64 i = 0; i < mb0s; ++i) {
+ gfx_accumulate_pixel(pos, zoom, mb0a + i, g_gfx_mb0s);
+ }
+}
+
+void gfx_render_mb1s(const Core *core, u64 pos, u64 zoom, u64 psel) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_mb1s);
+
+ if (psel < core->pfst || psel > core->plst) {
+ return;
+ }
+
+ u64 mb1a = arch_proc_mb1_addr(core, psel);
+ u64 mb1s = arch_proc_mb1_size(core, psel);
+
+ for (u64 i = 0; i < mb1s; ++i) {
+ gfx_accumulate_pixel(pos, zoom, mb1a + i, g_gfx_mb1s);
+ }
+}
+
+void gfx_render_ipas(const Core *core, u64 pos, u64 zoom, u64 psel) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_ipas);
+
+ if (psel < core->pfst || psel > core->plst) {
+ return;
+ }
+
+ u64 ipa = arch_proc_ip_addr(core, psel);
+
+ gfx_accumulate_pixel(pos, zoom, ipa, g_gfx_ipas);
+}
+
+void gfx_render_spas(const Core *core, u64 pos, u64 zoom, u64 psel) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_spas);
+
+ if (psel < core->pfst || psel > core->plst) {
+ return;
+ }
+
+ u64 spa = arch_proc_sp_addr(core, psel);
+
+ gfx_accumulate_pixel(pos, zoom, spa, g_gfx_spas);
+}
+
+void gfx_render(const Core *core, u64 pos, u64 zoom, u64 psel) {
+ assert(core);
+
+ gfx_render_inst(core, pos, zoom);
+ gfx_render_mbst(core, pos, zoom);
+ gfx_render_mb0s(core, pos, zoom, psel);
+ gfx_render_mb1s(core, pos, zoom, psel);
+ gfx_render_ipas(core, pos, zoom, psel);
+ gfx_render_spas(core, pos, zoom, psel);
+}
diff --git a/src/salis.c b/src/salis.c
new file mode 100644
index 0000000..c3de4f4
--- /dev/null
+++ b/src/salis.c
@@ -0,0 +1,661 @@
+// Project: Salis
+// Author: Paul Oliver
+// Email: contact@pauloliver.dev
+
+/*
+ * Core of the Salis simulator. Can be built against different architectures
+ * and UI modules.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <threads.h>
+
+#define ACT_BENCH (1)
+#define ACT_LOAD (2)
+#define ACT_NEW (3)
+
+#define ASM_LINE_LEN (0x100)
+
+#define MALL_FLAG (0x80)
+#define IPCM_FLAG (0x80)
+#define INST_CAPS (0x80)
+#define INST_MASK (0x7f)
+
+typedef struct Core Core;
+typedef struct Ipcm Ipcm;
+typedef struct Proc Proc;
+typedef thrd_t Thread;
+typedef uint64_t u64;
+typedef uint8_t u8;
+
+struct Core {
+ u64 mall;
+ u64 muta[4];
+ u64 pnum;
+ u64 pcap;
+ u64 pfst;
+ u64 plst;
+ u64 pcur;
+ u64 psli;
+ u64 ncyc;
+
+ Thread thread;
+ u64 tix;
+
+ u64 ivpt;
+ u8 *iviv;
+ u64 *ivav;
+
+ Proc *pvec;
+ u8 mvec[MVEC_SIZE];
+ u8 tgap[TGAP_SIZE];
+};
+
+Core g_cores[CORE_COUNT];
+u64 g_steps;
+u64 g_syncs;
+#if ACTION == ACT_LOAD || ACTION == ACT_NEW
+char g_asav_pbuf[AUTO_SAVE_NAME_LEN];
+#endif
+const Proc g_dead_proc;
+
+#include ARCH_SOURCE
+
+#if ACTION == ACT_BENCH || ACTION == ACT_NEW
+char g_mnemo_table[0x100][MNEMONIC_BUFF_SIZE];
+#endif
+
+u64 mvec_loop(u64 addr) {
+ return addr % MVEC_SIZE;
+}
+
+bool mvec_is_alloc(const Core *core, u64 addr) {
+ assert(core);
+ return core->mvec[mvec_loop(addr)] & MALL_FLAG ? true : false;
+}
+
+void mvec_alloc(Core *core, u64 addr) {
+ assert(core);
+ assert(!mvec_is_alloc(core, addr));
+ core->mvec[mvec_loop(addr)] |= MALL_FLAG;
+ core->mall++;
+}
+
+void mvec_free(Core *core, u64 addr) {
+ assert(core);
+ assert(mvec_is_alloc(core, addr));
+ core->mvec[mvec_loop(addr)] ^= MALL_FLAG;
+ core->mall--;
+}
+
+u8 mvec_get_byte(const Core *core, u64 addr) {
+ assert(core);
+ return core->mvec[mvec_loop(addr)];
+}
+
+u8 mvec_get_inst(const Core *core, u64 addr) {
+ assert(core);
+ return core->mvec[mvec_loop(addr)] & INST_MASK;
+}
+
+void mvec_set_inst(Core *core, u64 addr, u8 inst) {
+ assert(core);
+ assert(inst < INST_CAPS);
+ core->mvec[mvec_loop(addr)] &= MALL_FLAG;
+ core->mvec[mvec_loop(addr)] |= inst;
+}
+
+#if MUTA_FLIP_BIT == 1
+void mvec_flip_bit(Core *core, u64 addr, int bit) {
+ assert(core);
+ assert(bit < 8);
+ core->mvec[mvec_loop(addr)] ^= (1 << bit) & INST_MASK;
+}
+#endif
+
+bool mvec_is_proc_owner(const Core *core, u64 addr, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ u64 mb0a = arch_proc_mb0_addr(core, pix);
+ u64 mb0s = arch_proc_mb0_size(core, pix);
+
+ if (((addr - mb0a) % MVEC_SIZE) < mb0s) {
+ return true;
+ }
+
+ u64 mb1a = arch_proc_mb1_addr(core, pix);
+ u64 mb1s = arch_proc_mb1_size(core, pix);
+
+ if (((addr - mb1a) % MVEC_SIZE) < mb1s) {
+ return true;
+ }
+
+ return false;
+}
+
+u64 mvec_get_owner(const Core *core, u64 addr) {
+ assert(core);
+ assert(mvec_is_alloc(core, addr));
+
+ for (u64 pix = core->pfst; pix <= core->plst; ++pix) {
+ if (mvec_is_proc_owner(core, addr, pix)) {
+ return pix;
+ }
+ }
+
+ assert(false);
+ return -1;
+}
+
+#if ACTION == ACT_BENCH || ACTION == ACT_NEW
+u64 muta_smix(u64 *seed) {
+ assert(seed);
+
+ u64 next = (*seed += 0x9e3779b97f4a7c15);
+ next = (next ^ (next >> 30)) * 0xbf58476d1ce4e5b9;
+ next = (next ^ (next >> 27)) * 0x94d049bb133111eb;
+
+ return next ^ (next >> 31);
+}
+#endif
+
+u64 muta_ro64(u64 x, int k) {
+ return (x << k) | (x >> (64 - k));
+}
+
+u64 muta_next(Core *core) {
+ assert(core);
+
+ u64 r = muta_ro64(core->muta[1] * 5, 7) * 9;
+ u64 t = core->muta[1] << 17;
+
+ core->muta[2] ^= core->muta[0];
+ core->muta[3] ^= core->muta[1];
+ core->muta[1] ^= core->muta[2];
+ core->muta[0] ^= core->muta[3];
+
+ core->muta[2] ^= t;
+ core->muta[3] = muta_ro64(core->muta[3], 45);
+
+ return r;
+}
+
+void muta_cosmic_ray(Core *core) {
+ assert(core);
+
+ u64 a = muta_next(core) % MUTA_RANGE;
+ u64 b = muta_next(core);
+
+ if (a < MVEC_SIZE) {
+#if MUTA_FLIP_BIT == 1
+ mvec_flip_bit(core, a, (int)(b % 8));
+#else
+ mvec_set_inst(core, a, b & INST_MASK);
+#endif
+ }
+}
+
+void proc_new(Core *core, const Proc *proc) {
+ assert(core);
+ assert(proc);
+
+ if (core->pnum == core->pcap) {
+ u64 new_pcap = core->pcap * 2;
+ Proc *new_pvec = calloc(new_pcap, sizeof(Proc));
+
+ for (u64 pix = core->pfst; pix <= core->plst; ++pix) {
+ u64 iold = pix % core->pcap;
+ u64 inew = pix % new_pcap;
+ memcpy(&new_pvec[inew], &core->pvec[iold], sizeof(Proc));
+ }
+
+ free(core->pvec);
+ core->pcap = new_pcap;
+ core->pvec = new_pvec;
+ }
+
+ core->pnum++;
+ core->plst++;
+ memcpy(&core->pvec[core->plst % core->pcap], proc, sizeof(Proc));
+}
+
+void proc_kill(Core *core) {
+ assert(core);
+ assert(core->pnum > 1);
+
+ arch_on_proc_kill(core);
+
+ core->pcur++;
+ core->pfst++;
+ core->pnum--;
+}
+
+bool proc_is_live(const Core *core, u64 pix) {
+ assert(core);
+
+ return pix >= core->pfst && pix <= core->plst;
+}
+
+const Proc *proc_get(const Core *core, u64 pix) {
+ assert(core);
+
+ if (proc_is_live(core, pix)) {
+ return &core->pvec[pix % core->pcap];
+ } else {
+ return &g_dead_proc;
+ }
+}
+
+Proc *proc_fetch(Core *core, u64 pix) {
+ assert(core);
+ assert(proc_is_live(core, pix));
+
+ return &core->pvec[pix % core->pcap];
+}
+
+#if ACTION == ACT_LOAD || ACTION == ACT_NEW
+void core_save(FILE *f, const Core *core) {
+ assert(f);
+ assert(core);
+
+ fwrite(&core->mall, sizeof(u64), 1, f);
+ fwrite( core->muta, sizeof(u64), 4, f);
+ fwrite(&core->pnum, sizeof(u64), 1, f);
+ fwrite(&core->pcap, sizeof(u64), 1, f);
+ fwrite(&core->pfst, sizeof(u64), 1, f);
+ fwrite(&core->plst, sizeof(u64), 1, f);
+ fwrite(&core->pcur, sizeof(u64), 1, f);
+ fwrite(&core->psli, sizeof(u64), 1, f);
+ fwrite(&core->ncyc, sizeof(u64), 1, f);
+ fwrite(&core->ivpt, sizeof(u64), 1, f);
+
+ fwrite(core->iviv, sizeof(u8), SYNC_INTERVAL, f);
+ fwrite(core->ivav, sizeof(u64), SYNC_INTERVAL, f);
+ fwrite(core->pvec, sizeof(Proc), core->pcap, f);
+ fwrite(core->mvec, sizeof(u8), MVEC_SIZE, f);
+}
+#endif
+
+#if ACTION == ACT_BENCH || ACTION == ACT_NEW
+u64 core_assemble_ancestor(int cix, const char *anc) {
+ assert(cix >= 0 && cix < CORE_COUNT);
+ assert(anc);
+
+ if (anc[0] == '_') {
+ return 0;
+ }
+
+ FILE *f = fopen(anc, "r");
+
+ assert(f);
+
+ u64 addr = 0;
+ char line[ASM_LINE_LEN] = {0};
+ Core *core = &g_cores[cix];
+
+ for (; fgets(line, ASM_LINE_LEN, f); ++addr) {
+#ifndef NDEBUG
+ bool line_ok = false;
+#endif
+
+ line[strcspn(line, "\r\n")] = '\0';
+
+ for (int i = 0; i < 0x100; ++i) {
+ if (strcmp(line, g_mnemo_table[i]) == 0) {
+ mvec_alloc(core, addr);
+ mvec_set_inst(core, addr, i);
+#ifndef NDEBUG
+ line_ok = true;
+#endif
+ break;
+ }
+ }
+
+ assert(line_ok);
+ }
+
+ fclose(f);
+
+ return addr;
+}
+
+void core_init(int cix, u64 *seed, const char *anc) {
+ assert(cix >= 0 && cix < CORE_COUNT);
+ assert(seed);
+ assert(anc);
+
+ Core *core = &g_cores[cix];
+
+ if (*seed) {
+ core->muta[0] = muta_smix(seed);
+ core->muta[1] = muta_smix(seed);
+ core->muta[2] = muta_smix(seed);
+ core->muta[3] = muta_smix(seed);
+ }
+
+ core->pnum = 1;
+ core->pcap = 1;
+ core->iviv = calloc(SYNC_INTERVAL, sizeof(u8));
+ core->ivav = calloc(SYNC_INTERVAL, sizeof(u64));
+ core->pvec = calloc(core->pcap, sizeof(Proc));
+
+ assert(core->iviv);
+ assert(core->ivav);
+ assert(core->pvec);
+
+ u64 anc_size = core_assemble_ancestor(cix, anc);
+
+ arch_anc_init(core, anc_size);
+}
+#endif
+
+#if ACTION == ACT_LOAD
+void core_load(FILE *f, Core *core) {
+ assert(f);
+ assert(core);
+
+ fread(&core->mall, sizeof(u64), 1, f);
+ fread( core->muta, sizeof(u64), 4, f);
+ fread(&core->pnum, sizeof(u64), 1, f);
+ fread(&core->pcap, sizeof(u64), 1, f);
+ fread(&core->pfst, sizeof(u64), 1, f);
+ fread(&core->plst, sizeof(u64), 1, f);
+ fread(&core->pcur, sizeof(u64), 1, f);
+ fread(&core->psli, sizeof(u64), 1, f);
+ fread(&core->ncyc, sizeof(u64), 1, f);
+ fread(&core->ivpt, sizeof(u64), 1, f);
+
+ core->iviv = calloc(SYNC_INTERVAL, sizeof(u8));
+ core->ivav = calloc(SYNC_INTERVAL, sizeof(u64));
+ core->pvec = calloc(core->pcap, sizeof(Proc));
+
+ assert(core->iviv);
+ assert(core->ivav);
+ assert(core->pvec);
+
+ fread(core->iviv, sizeof(u8), SYNC_INTERVAL, f);
+ fread(core->ivav, sizeof(u64), SYNC_INTERVAL, f);
+ fread(core->pvec, sizeof(Proc), core->pcap, f);
+ fread(core->mvec, sizeof(u8), MVEC_SIZE, f);
+}
+#endif
+
+void core_pull_ipcm(Core *core) {
+ assert(core);
+ assert(core->ivpt < SYNC_INTERVAL);
+
+ u8 *iinst = &core->iviv[core->ivpt];
+ u64 *iaddr = &core->ivav[core->ivpt];
+
+ if ((*iinst & IPCM_FLAG) != 0) {
+ mvec_set_inst(core, *iaddr, *iinst & INST_MASK);
+
+ *iinst = 0;
+ *iaddr = 0;
+ }
+
+ assert(*iinst == 0);
+ assert(*iaddr == 0);
+}
+
+void core_push_ipcm(Core *core, u8 inst, u64 addr) {
+ assert(core);
+ assert(core->ivpt < SYNC_INTERVAL);
+ assert((inst & IPCM_FLAG) == 0);
+
+ u8 *iinst = &core->iviv[core->ivpt];
+ u64 *iaddr = &core->ivav[core->ivpt];
+
+ assert(*iinst == 0);
+ assert(*iaddr == 0);
+
+ *iinst = inst | IPCM_FLAG;
+ *iaddr = addr;
+}
+
+void core_step(Core *core) {
+ assert(core);
+
+ if (core->psli != 0) {
+ core_pull_ipcm(core);
+ arch_proc_step(core, core->pcur);
+
+ core->psli--;
+ core->ivpt++;
+
+ return;
+ }
+
+ if (core->pcur != core->plst) {
+ core->psli = arch_proc_slice(core, ++core->pcur);
+ core_step(core);
+ return;
+ }
+
+ core->pcur = core->pfst;
+ core->psli = arch_proc_slice(core, core->pcur);
+ core->ncyc++;
+
+ while (core->mall > MVEC_SIZE / 2 && core->pnum > 1) {
+ proc_kill(core);
+ }
+
+ muta_cosmic_ray(core);
+ core_step(core);
+}
+
+#if ACTION == ACT_LOAD || ACTION == ACT_NEW
+void salis_save(const char *path) {
+ FILE *f = fopen(path, "wb");
+
+ assert(f);
+
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ core_save(f, &g_cores[i]);
+ }
+
+ fwrite(&g_steps, sizeof(u64), 1, f);
+ fwrite(&g_syncs, sizeof(u64), 1, f);
+ fclose(f);
+}
+
+void salis_auto_save() {
+ if (g_steps % AUTO_SAVE_INTERVAL != 0) {
+ return;
+ }
+
+#ifndef NDEBUG
+ int rem = snprintf(
+#else
+ snprintf(
+#endif
+ g_asav_pbuf,
+ AUTO_SAVE_NAME_LEN,
+ "%s-%#018lx",
+ SIM_PATH,
+ g_steps
+ );
+
+ assert(rem >= 0);
+ assert(rem < AUTO_SAVE_NAME_LEN);
+
+ salis_save(g_asav_pbuf);
+}
+#endif
+
+#if ACTION == ACT_BENCH || ACTION == ACT_NEW
+void salis_init() {
+ for (int i = 0; i < 0x100; ++i) {
+ arch_mnemonic(i, g_mnemo_table[i]);
+ }
+
+ u64 seed = SEED;
+ char anc_list[] = ANC_LIST;
+
+ assert(anc_list);
+
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ core_init(i, &seed, strtok(i ? NULL : anc_list, ","));
+ }
+
+#if ACTION == ACT_NEW
+ salis_auto_save();
+#endif
+}
+#endif
+
+#if ACTION == ACT_LOAD
+void salis_load() {
+ FILE *f = fopen(SIM_PATH, "rb");
+
+ assert(f);
+
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ core_load(f, &g_cores[i]);
+ }
+
+ fread(&g_steps, sizeof(u64), 1, f);
+ fread(&g_syncs, sizeof(u64), 1, f);
+ fclose(f);
+}
+#endif
+
+int salis_thread(Core *core) {
+ assert(core);
+
+ for (u64 i = 0; i < core->tix; ++i) {
+ core_step(core);
+ }
+
+ return 0;
+}
+
+void salis_run_thread(u64 ns) {
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ g_cores[i].tix = ns;
+
+ thrd_create(
+ &g_cores[i].thread,
+ (thrd_start_t)salis_thread,
+ &g_cores[i]
+ );
+ }
+
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ thrd_join(g_cores[i].thread, NULL);
+ }
+
+ g_steps += ns;
+}
+
+void salis_sync() {
+ u8 *iviv0 = g_cores[0].iviv;
+ u64 *ivav0 = g_cores[0].ivav;
+
+ for (int i = 1; i < CORE_COUNT; ++i) {
+ g_cores[i - 1].iviv = g_cores[i].iviv;
+ g_cores[i - 1].ivav = g_cores[i].ivav;
+ }
+
+ g_cores[CORE_COUNT - 1].iviv = iviv0;
+ g_cores[CORE_COUNT - 1].ivav = ivav0;
+
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ g_cores[i].ivpt = 0;
+ }
+
+ g_syncs++;
+}
+
+void salis_loop(u64 ns, u64 dt) {
+ assert(dt);
+
+ if (ns < dt) {
+ salis_run_thread(ns);
+ return;
+ }
+
+ salis_run_thread(dt);
+ salis_sync();
+#if ACTION == ACT_LOAD || ACTION == ACT_NEW
+ salis_auto_save();
+#endif
+ salis_loop(ns - dt, SYNC_INTERVAL);
+}
+
+#ifndef NDEBUG
+void salis_validate_core(const Core *core) {
+ assert(core->plst >= core->pfst);
+ assert(core->pnum == core->plst + 1 - core->pfst);
+ assert(core->pnum <= core->pcap);
+ assert(core->pcur >= core->pfst && core->pcur <= core->plst);
+ assert(core->ncyc <= g_steps);
+
+ u64 mall = 0;
+
+ for (u64 i = 0; i < MVEC_SIZE; ++i) {
+ mall += mvec_is_alloc(core, i) ? 1 : 0;
+ }
+
+ assert(core->mall == mall);
+
+ for (u64 i = core->pfst; i <= core->plst; ++i) {
+ arch_validate_proc(core, i);
+ }
+
+ for (u64 i = 0; i < SYNC_INTERVAL; ++i) {
+ u8 iinst = core->iviv[i];
+
+ if ((iinst & IPCM_FLAG) == 0) {
+ u64 iaddr = core->ivav[i];
+
+ assert(iinst == 0);
+ assert(iaddr == 0);
+ }
+ }
+
+ assert(core->ivpt == g_steps % SYNC_INTERVAL);
+}
+
+void salis_validate() {
+ assert(g_steps / SYNC_INTERVAL == g_syncs);
+
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ salis_validate_core(&g_cores[i]);
+ }
+}
+#endif
+
+void salis_step(u64 ns) {
+ assert(ns);
+ salis_loop(ns, SYNC_INTERVAL - (g_steps % SYNC_INTERVAL));
+
+#ifndef NDEBUG
+ salis_validate();
+#endif
+}
+
+void salis_free() {
+ for (int i = 0; i < CORE_COUNT; ++i) {
+ assert(g_cores[i].pvec);
+ assert(g_cores[i].iviv);
+ assert(g_cores[i].ivav);
+
+ free(g_cores[i].pvec);
+ free(g_cores[i].iviv);
+ free(g_cores[i].ivav);
+
+ g_cores[i].pvec = NULL;
+ g_cores[i].iviv = NULL;
+ g_cores[i].ivav = NULL;
+ }
+}
+
+#include UI
diff --git a/src/ui/curses.c b/src/ui/curses.c
new file mode 100644
index 0000000..8f91d9f
--- /dev/null
+++ b/src/ui/curses.c
@@ -0,0 +1,944 @@
+// Project: Salis
+// Author: Paul Oliver
+// Email: contact@pauloliver.dev
+
+/*
+ * Implements a TUI for the Salis simulator using the ncurses library.
+ */
+
+#include <curses.h>
+#include <locale.h>
+#include <time.h>
+
+#define CTRL(x) ((x) & 0x1f)
+#define PANE_WIDTH (27)
+#define PROC_FIELD_WIDTH (21)
+#define PROC_PAGE_LINES (12)
+
+enum {
+ PAGE_CORE,
+ PAGE_PROCESS,
+ PAGE_WORLD,
+ PAGE_IPC,
+ PAGE_COUNT
+};
+
+enum {
+ PAIR_NOUSE,
+ PAIR_NORMAL,
+ PAIR_HEADER,
+ PAIR_LIVE_PROC,
+ PAIR_SELECTED_PROC,
+ PAIR_FREE_CELL,
+ PAIR_ALLOC_CELL,
+ PAIR_MEM_BLOCK_START,
+ PAIR_SELECTED_MB1,
+ PAIR_SELECTED_MB2,
+ PAIR_SELECTED_IP,
+ PAIR_SELECTED_SP,
+};
+
+bool g_exit;
+bool g_running;
+unsigned g_core;
+unsigned g_page;
+bool g_proc_genes;
+u64 g_proc_scroll;
+u64 g_proc_field_scroll;
+u64 g_proc_gene_scroll;
+u64 g_proc_selected;
+u64 g_wrld_pos;
+u64 g_wrld_zoom;
+bool g_wcursor_mode;
+int g_wcursor_x;
+int g_wcursor_y;
+u64 g_wcursor_pointed;
+u64 g_vlin;
+u64 g_vsiz;
+u64 g_vlin_rng;
+u64 g_vsiz_rng;
+u64 g_ivpt_scroll;
+char *g_line_buff;
+u64 g_step_block;
+
+const wchar_t *g_zoomed_symbols = (
+ L"⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟"
+ L"⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿"
+ L"⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟"
+ L"⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿"
+);
+
+#include "graphics.c"
+
+void ui_line_buff_free() {
+ if (g_line_buff) {
+ free(g_line_buff);
+ }
+
+ g_line_buff = NULL;
+}
+
+void ui_line_buff_resize() {
+ ui_line_buff_free();
+
+ g_line_buff = calloc(COLS + 1, sizeof(char));
+}
+
+void ui_line(bool clear, int line, int color, int attr, const char *format, ...) {
+ assert(line >= 0);
+ assert(format);
+
+ if (line >= LINES) {
+ return;
+ }
+
+ if (clear) {
+ move(line, 0);
+ clrtoeol();
+ }
+
+ va_list args;
+
+ attron(COLOR_PAIR(color) | attr);
+ va_start(args, format);
+
+ vsnprintf(g_line_buff, COLS, format, args);
+ mvprintw(line, 1, g_line_buff);
+
+ va_end(args);
+ attroff(COLOR_PAIR(color) | attr);
+}
+
+void ui_clear_line(int l) {
+ ui_line(true, l, PAIR_NORMAL, A_NORMAL, "");
+}
+
+void ui_field(int line, int col, int color, int attr, const char *format, ...) {
+ assert(line >= 0);
+ assert(col >= 0);
+ assert(format);
+
+ if (line >= LINES || col >= COLS) {
+ return;
+ }
+
+ va_list args;
+
+ attron(COLOR_PAIR(color) | attr);
+ va_start(args, format);
+
+ vsnprintf(g_line_buff, COLS - col, format, args);
+ mvprintw(line, col, g_line_buff);
+
+ va_end(args);
+ attroff(COLOR_PAIR(color) | attr);
+}
+
+void ui_str_field(int l, const char *label, const char *value) {
+ assert(label);
+ assert(value);
+ ui_line(false, l, PAIR_NORMAL, A_NORMAL, "%s : %18s", label, value);
+}
+
+void ui_ulx_field(int l, const char *label, u64 value) {
+ assert(label);
+ ui_line(false, l, PAIR_NORMAL, A_NORMAL, "%-4s : %#18lx", label, value);
+}
+
+void ui_print_core(int l) {
+ ui_line(false, ++l, PAIR_HEADER, A_BOLD, "CORE [%d]", g_core);
+ ui_ulx_field(++l, "mall", g_cores[g_core].mall);
+ ui_ulx_field(++l, "mut0", g_cores[g_core].muta[0]);
+ ui_ulx_field(++l, "mut1", g_cores[g_core].muta[1]);
+ ui_ulx_field(++l, "mut2", g_cores[g_core].muta[2]);
+ ui_ulx_field(++l, "mut3", g_cores[g_core].muta[3]);
+ ui_ulx_field(++l, "pnum", g_cores[g_core].pnum);
+ ui_ulx_field(++l, "pcap", g_cores[g_core].pcap);
+ ui_ulx_field(++l, "pfst", g_cores[g_core].pfst);
+ ui_ulx_field(++l, "plst", g_cores[g_core].plst);
+ ui_ulx_field(++l, "pcur", g_cores[g_core].pcur);
+ ui_ulx_field(++l, "psli", g_cores[g_core].psli);
+ ui_ulx_field(++l, "ncyc", g_cores[g_core].ncyc);
+ ui_ulx_field(++l, "ivpt", g_cores[g_core].ivpt);
+}
+
+int ui_proc_pair(u64 pix) {
+ if (pix == g_proc_selected) {
+ return PAIR_SELECTED_PROC;
+ } else if (proc_is_live(&g_cores[g_core], pix)) {
+ return PAIR_LIVE_PROC;
+ } else {
+ return PAIR_NORMAL;
+ }
+}
+
+const char *ui_proc_state(u64 pix) {
+ return proc_is_live(&g_cores[g_core], pix) ? "live" : "dead";
+}
+
+void ui_print_process_genome_header(int l) {
+ ui_line(false, l++, PAIR_NORMAL, A_NORMAL, "%s : %18s : %s", "stat", "pix", "genome");
+}
+
+void ui_print_process_gene(int l, int gcol, u64 gidx, u64 mba, u64 pix, int pair) {
+ assert(gcol >= PANE_WIDTH + 2);
+ assert(gcol < COLS);
+ assert(proc_is_live(&g_cores[g_core], pix));
+ assert(pair == PAIR_SELECTED_MB1 || pair == PAIR_SELECTED_MB2);
+
+ const Core *core = &g_cores[g_core];
+
+ u64 addr = mba + gidx;
+ u8 byte = mvec_get_byte(core, addr);
+
+ wchar_t gsym[2] = { arch_symbol(byte), L'\0' };
+ cchar_t cchar = { 0 };
+
+ int pair_cell;
+
+ if (arch_proc_ip_addr(core, pix) == addr) {
+ pair_cell = PAIR_SELECTED_IP;
+ } else if (arch_proc_sp_addr(core, pix) == addr) {
+ pair_cell = PAIR_SELECTED_SP;
+ } else {
+ pair_cell = pair;
+ }
+
+ setcchar(&cchar, gsym, 0, pair_cell, NULL);
+ mvadd_wch(l, gcol, &cchar);
+}
+
+void ui_print_process_genes(int l, u64 pix) {
+ ui_line(true, l, ui_proc_pair(pix), A_NORMAL, "%s : %#18lx :", ui_proc_state(pix), pix);
+
+ if (!proc_is_live(&g_cores[g_core], pix)) {
+ return;
+ }
+
+ const Core *core = &g_cores[g_core];
+
+ int scol = PANE_WIDTH + 2;
+ int gcol = scol - g_proc_gene_scroll;
+ u64 mb0a = arch_proc_mb0_addr(core, pix);
+ u64 mb0s = arch_proc_mb0_size(core, pix);
+ u64 mb1a = arch_proc_mb1_addr(core, pix);
+ u64 mb1s = arch_proc_mb1_size(core, pix);
+
+ for (u64 gidx = 0; gidx < mb0s && gcol < COLS; ++gidx, ++gcol) {
+ if (gcol >= scol) {
+ ui_print_process_gene(l, gcol, gidx, mb0a, pix, PAIR_SELECTED_MB1);
+ }
+ }
+
+ for (u64 gidx = 0; gidx < mb1s && gcol < COLS; ++gidx, ++gcol) {
+ if (gcol >= scol) {
+ ui_print_process_gene(l, gcol, gidx, mb1a, pix, PAIR_SELECTED_MB2);
+ }
+ }
+
+ clrtoeol();
+}
+
+void ui_print_process_field_header_element(int l, int fidx, const char *name) {
+ assert(fidx >= 0);
+ assert(name);
+
+ if (fidx < (int)g_proc_field_scroll) {
+ return;
+ }
+
+ int foff = fidx - g_proc_field_scroll;
+ int fcol = foff * PROC_FIELD_WIDTH + PANE_WIDTH - 1;
+
+ ui_field(l, fcol, PAIR_NORMAL, A_NORMAL, " : %18s", name);
+}
+
+void ui_print_process_field_header(int l) {
+ ui_line(true, l, PAIR_NORMAL, A_NORMAL, "%s : %18s", "stat", "pix");
+
+ int fidx = 0;
+
+#define PROC_FIELD(type, name) ui_print_process_field_header_element(l, fidx++, #name);
+ PROC_FIELDS
+#undef PROC_FIELD
+}
+
+void ui_print_process_field_element(int l, int fidx, int fclr, u64 field) {
+ assert(fidx >= 0);
+
+ if (fidx < (int)g_proc_field_scroll) {
+ return;
+ }
+
+ int foff = fidx - g_proc_field_scroll;
+ int fcol = foff * PROC_FIELD_WIDTH + PANE_WIDTH - 1;
+
+ ui_field(l, fcol, fclr, A_NORMAL, " : %#18lx", field);
+}
+
+
+void ui_print_process_fields(int l, u64 pix) {
+ ui_line(true, l, ui_proc_pair(pix), A_NORMAL, "%s : %#18lx", ui_proc_state(pix), pix);
+
+ const Proc *proc = proc_get(&g_cores[g_core], pix);
+ int fidx = 0;
+ int fclr = ui_proc_pair(pix);
+
+#define PROC_FIELD(type, name) ui_print_process_field_element(l, fidx++, fclr, proc->name);
+ PROC_FIELDS
+#undef PROC_FIELD
+}
+
+void ui_print_process(int l) {
+ l++;
+
+ ui_line(true, l++, PAIR_HEADER, A_BOLD,
+ "PROCESS [vs:%#lx | ps:%#lx | pf:%#lx | pl:%#lx | fs:%#lx | gs:%#lx]",
+ g_proc_scroll,
+ g_proc_selected,
+ g_cores[g_core].pfst,
+ g_cores[g_core].plst,
+ g_proc_field_scroll,
+ g_proc_gene_scroll
+ );
+
+ u64 pix = g_proc_scroll;
+
+ if (g_proc_genes) {
+ ui_print_process_genome_header(l++);
+
+ while (l < LINES) {
+ ui_print_process_genes(l++, pix++);
+ }
+ } else {
+ ui_print_process_field_header(l++);
+
+ while (l < LINES) {
+ ui_print_process_fields(l++, pix++);
+ }
+ }
+}
+
+void ui_world_resize() {
+ assert(g_wrld_zoom);
+
+ g_vlin = 0;
+ g_vsiz = 0;
+ g_vlin_rng = 0;
+ g_vsiz_rng = 0;
+
+ if (COLS > PANE_WIDTH) {
+ g_vlin = COLS - PANE_WIDTH;
+ g_vsiz = LINES * g_vlin;
+ g_vlin_rng = g_vlin * g_wrld_zoom;
+ g_vsiz_rng = g_vsiz * g_wrld_zoom;
+
+ gfx_resize(g_vsiz);
+ }
+}
+
+void ui_print_cell(u64 i, u64 r, u64 x, u64 y) {
+ wchar_t inst_nstr[2] = { L'\0', L'\0' };
+ cchar_t cchar = { 0 };
+ u64 inst_avrg = g_gfx_inst[i] / g_wrld_zoom;
+
+ if (g_wrld_zoom == 1) {
+ inst_nstr[0] = arch_symbol((u8)inst_avrg);
+ } else {
+ inst_nstr[0] = g_zoomed_symbols[(u8)inst_avrg];
+ }
+
+ int pair_cell;
+
+ if (g_wcursor_mode && r == (u64)g_wcursor_x && y == (u64)g_wcursor_y) {
+ pair_cell = PAIR_NORMAL;
+ } else if (g_gfx_ipas[i] != 0) {
+ pair_cell = PAIR_SELECTED_IP;
+ } else if (g_gfx_spas[i] != 0) {
+ pair_cell = PAIR_SELECTED_SP;
+ } else if (g_gfx_mb0s[i] != 0) {
+ pair_cell = PAIR_SELECTED_MB1;
+ } else if (g_gfx_mb1s[i] != 0) {
+ pair_cell = PAIR_SELECTED_MB2;
+ } else if (g_gfx_mbst[i] != 0) {
+ pair_cell = PAIR_MEM_BLOCK_START;
+ } else if (g_gfx_mall[i] != 0) {
+ pair_cell = PAIR_ALLOC_CELL;
+ } else {
+ pair_cell = PAIR_FREE_CELL;
+ }
+
+ setcchar(&cchar, inst_nstr, 0, pair_cell, NULL);
+ mvadd_wch(y, x, &cchar);
+}
+
+void ui_print_wcursor_bar() {
+ ui_clear_line(LINES - 1);
+
+ const Core *core = &g_cores[g_core];
+
+ char cmnem[MNEMONIC_BUFF_SIZE];
+ char cownr[PROC_FIELD_WIDTH];
+
+ u64 cpos = g_vlin * g_wcursor_y + g_wcursor_x;
+ u64 caddr = cpos * g_wrld_zoom + g_wrld_pos;
+ u8 cbyte = mvec_get_byte(core, caddr);
+
+ if (mvec_is_alloc(core, caddr)) {
+ g_wcursor_pointed = mvec_get_owner(core, caddr);
+ snprintf(cownr, PROC_FIELD_WIDTH, "%#lx", g_wcursor_pointed);
+ } else {
+ g_wcursor_pointed = (u64)(-1);
+ snprintf(cownr, PROC_FIELD_WIDTH, "-");
+ }
+
+ arch_mnemonic(cbyte, cmnem);
+
+ mvprintw(
+ LINES - 1,
+ 1,
+ (
+ "cursor"
+ " | x:%#x"
+ " | y:%#x"
+ " | addr:%#lx"
+ " | isum:%#lx"
+ " | iavr:%#lx"
+ " | mall:%#lx"
+ " | mbst:%#lx"
+ " | mnem:<%s>"
+ " | ownr:%s"
+ ),
+ g_wcursor_x,
+ g_wcursor_y,
+ caddr,
+ g_gfx_inst[cpos],
+ g_gfx_inst[cpos] / g_wrld_zoom,
+ g_gfx_mall[cpos],
+ g_gfx_mbst[cpos],
+ cmnem,
+ cownr
+ );
+}
+
+void ui_print_world(int l) {
+ l++;
+
+ ui_line(false, l++, PAIR_HEADER, A_BOLD, "WORLD");
+ ui_ulx_field(l++, "wrlp", g_wrld_pos);
+ ui_ulx_field(l++, "wrlz", g_wrld_zoom);
+ ui_ulx_field(l++, "psel", g_proc_selected);
+ ui_ulx_field(l++, "pabs", g_proc_selected % g_cores[g_core].pcap);
+ ui_ulx_field(l++, "vrng", g_vsiz_rng);
+ ui_str_field(l++, "curs", g_wcursor_mode ? "on" : "off");
+
+ l++;
+
+ ui_line(false, l++, PAIR_HEADER, A_BOLD, "SELECTED");
+
+ const Proc *psel = proc_get(&g_cores[g_core], g_proc_selected);
+
+#define PROC_FIELD(type, name) ui_ulx_field(l++, #name, psel->name);
+ PROC_FIELDS
+#undef PROC_FIELD
+
+ if (!g_vlin) {
+ return;
+ }
+
+ gfx_render(&g_cores[g_core], g_wrld_pos, g_wrld_zoom, g_proc_selected);
+
+ if (g_wcursor_mode) {
+ int xmax = g_vlin - 1;
+ int ymax = LINES - 2;
+
+ g_wcursor_x = (g_wcursor_x < xmax) ? g_wcursor_x : xmax;
+ g_wcursor_y = (g_wcursor_y < ymax) ? g_wcursor_y : ymax;
+ }
+
+ for (u64 i = 0; i < g_vsiz; ++i) {
+ u64 r = i % g_vlin;
+ u64 x = r + PANE_WIDTH;
+ u64 y = i / g_vlin;
+
+ ui_print_cell(i, r, x, y);
+ }
+
+ if (g_wcursor_mode) {
+ ui_print_wcursor_bar();
+ }
+}
+
+void ui_print_ipc_field(int l, u64 i, int color) {
+ u8 iinst = g_cores[g_core].iviv[i];
+ u64 iaddr = g_cores[g_core].ivav[i];
+
+ ui_field(l, PANE_WIDTH, color, A_NORMAL, "%#18x : %#18x : %#18x", i, iinst, iaddr);
+}
+
+void ui_print_ipc_data() {
+ ui_field(0, PANE_WIDTH, PAIR_NORMAL, A_NORMAL, "%18s : %18s : %18s", "ipci", "inst", "addr");
+
+ int l = 1 - g_ivpt_scroll;
+
+ for (u64 i = 0; i < SYNC_INTERVAL; ++i) {
+ if (i == g_cores[g_core].ivpt) {
+ if (l >= 1) {
+ ui_print_ipc_field(l++, i, PAIR_SELECTED_PROC);
+ }
+
+ continue;
+ }
+
+ u8 iinst = g_cores[g_core].iviv[i];
+
+ if ((iinst & IPCM_FLAG) != 0) {
+ if (l >= 1) {
+ ui_print_ipc_field(l++, i, PAIR_LIVE_PROC);
+ }
+
+ continue;
+ }
+ }
+
+ for (; l < LINES; ++l) {
+ if (l >= 1) {
+ move(l, PANE_WIDTH);
+ clrtoeol();
+ }
+ }
+}
+
+void ui_print_ipc(int l) {
+ l++;
+
+ const Core *core = &g_cores[g_core];
+
+ ui_line(true, l++, PAIR_HEADER, A_BOLD, "IPC [%#lx]", g_ivpt_scroll);
+ ui_ulx_field(l++, "ivpt", core->ivpt);
+ ui_ulx_field(l++, "ivpi", core->iviv[core->ivpt]);
+ ui_ulx_field(l++, "ivpa", core->ivav[core->ivpt]);
+
+ ui_print_ipc_data();
+}
+
+void ui_print() {
+ int l = 1;
+
+ ui_line(false, l++, PAIR_HEADER, A_BOLD, "SALIS [%d:%d]", g_core, CORE_COUNT);
+ ui_str_field(l++, "name", SIM_NAME);
+ ui_ulx_field(l++, "seed", SEED);
+ ui_str_field(l++, "fbit", MUTA_FLIP_BIT ? "yes" : "no");
+ ui_ulx_field(l++, "asav", AUTO_SAVE_INTERVAL);
+ ui_str_field(l++, "arch", ARCHITECTURE);
+ ui_ulx_field(l++, "size", MVEC_SIZE);
+ ui_ulx_field(l++, "syni", SYNC_INTERVAL);
+ ui_ulx_field(l++, "step", g_steps);
+ ui_ulx_field(l++, "sync", g_syncs);
+ ui_ulx_field(l++, "step", g_step_block);
+
+ switch (g_page) {
+ case PAGE_CORE:
+ ui_print_core(l);
+ break;
+ case PAGE_PROCESS:
+ ui_print_process(l);
+ break;
+ case PAGE_WORLD:
+ ui_print_world(l);
+ break;
+ case PAGE_IPC:
+ ui_print_ipc(l);
+ break;
+ default:
+ break;
+ }
+}
+
+void ev_vscroll(int ev) {
+ switch (g_page) {
+ case PAGE_PROCESS:
+ switch (ev) {
+ case 'W':
+ g_proc_scroll += (LINES > PROC_PAGE_LINES) ? LINES - PROC_PAGE_LINES : 0;
+ break;
+ case 'S':
+ g_proc_scroll -= (LINES > PROC_PAGE_LINES) ? LINES - PROC_PAGE_LINES : 0;
+ break;
+ case 'w':
+ g_proc_scroll += 1;
+ break;
+ case 's':
+ g_proc_scroll -= 1;
+ break;
+ case 'q':
+ g_proc_scroll = 0;
+ break;
+ default:
+ break;
+ }
+
+ break;
+ case PAGE_WORLD: {
+ switch (ev) {
+ case 'W':
+ g_wrld_pos += g_vsiz_rng;
+ break;
+ case 'S':
+ g_wrld_pos -= g_vsiz_rng;
+ break;
+ case 'w':
+ g_wrld_pos += g_vlin_rng;
+ break;
+ case 's':
+ g_wrld_pos -= g_vlin_rng;
+ break;
+ case 'q':
+ g_wrld_pos = 0;
+ break;
+ default:
+ break;
+ }
+
+ break;
+ }
+ case PAGE_IPC:
+ switch (ev) {
+ case 'W':
+ g_ivpt_scroll += LINES;
+ break;
+ case 'S':
+ g_ivpt_scroll -= g_ivpt_scroll < (u64)LINES ? g_ivpt_scroll : (u64)LINES;
+ break;
+ case 'w':
+ g_ivpt_scroll += 1;
+ break;
+ case 's':
+ g_ivpt_scroll -= g_ivpt_scroll ? 1 : 0;
+ break;
+ case 'q':
+ g_ivpt_scroll = 0;
+ break;
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+void ev_hscroll(int ev) {
+ switch (g_page) {
+ case PAGE_PROCESS: {
+ u64 *hs_var = g_proc_genes ? &g_proc_gene_scroll : &g_proc_field_scroll;
+
+ switch (ev) {
+ case 'A':
+ *hs_var = 0;
+ break;
+ case 'a':
+ *hs_var -= *hs_var ? 1 : 0;
+ break;
+ case 'd':
+ (*hs_var)++;
+ break;
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ case PAGE_WORLD:
+ switch (ev) {
+ case 'a':
+ g_wrld_pos -= g_wrld_zoom;
+ break;
+ case 'd':
+ g_wrld_pos += g_wrld_zoom;
+ break;
+ default:
+ break;
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+void ev_zoom(int ev) {
+ switch (g_page) {
+ case PAGE_WORLD:
+ switch (ev) {
+ case 'x':
+ g_wrld_zoom *= (g_vlin != 0 && g_vsiz_rng < MVEC_SIZE) ? 2 : 1;
+ ui_world_resize();
+ break;
+ case 'z':
+ g_wrld_zoom /= (g_wrld_zoom != 1) ? 2 : 1;
+ ui_world_resize();
+ break;
+ default:
+ break;
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+void ev_move_wcursor(int ev) {
+ switch (ev) {
+ case KEY_UP:
+ g_wcursor_y -= (g_wcursor_y != 0) ? 1 : 0;
+ break;
+ case KEY_DOWN:
+ g_wcursor_y += (g_wcursor_y < LINES - 2) ? 1 : 0;
+ break;
+ case KEY_LEFT:
+ g_wcursor_x -= (g_wcursor_x != 0) ? 1 : 0;
+ break;
+ case KEY_RIGHT:
+ g_wcursor_x += ((u64)g_wcursor_x < g_vlin - 1) ? 1 : 0;
+ break;
+ default:
+ break;
+ }
+}
+
+void ev_sel_proc(int ev) {
+ if (g_page != PAGE_PROCESS && g_page != PAGE_WORLD) {
+ return;
+ }
+
+ switch (ev) {
+ case 'o':
+ g_proc_selected -= 1;
+ break;
+ case 'p':
+ g_proc_selected += 1;
+ break;
+ case 'f':
+ g_proc_selected = g_cores[g_core].pfst;
+ break;
+ case 'l':
+ g_proc_selected = g_cores[g_core].plst;
+ break;
+ default:
+ break;
+ }
+}
+
+void ev_goto_sel_proc() {
+ switch (g_page) {
+ case PAGE_PROCESS:
+ g_proc_scroll = g_proc_selected;
+ break;
+ case PAGE_WORLD:
+ g_wrld_pos = g_cores[g_core].pvec[g_proc_selected % g_cores[g_core].pcap].mb0a;
+ break;
+ default:
+ break;
+ }
+}
+
+void ev_handle() {
+ int ev = getch();
+
+ if (g_page == PAGE_WORLD && g_wcursor_mode) {
+ switch (ev) {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ ev_move_wcursor(ev);
+ return;
+ case '\n':
+ if (g_wcursor_pointed != (u64)(-1)) {
+ g_proc_selected = g_wcursor_pointed;
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch (ev) {
+ case CTRL('c'):
+ g_exit = true;
+ break;
+ case KEY_SLEFT:
+ clear();
+ g_core = (g_core - 1) % CORE_COUNT;
+ break;
+ case KEY_SRIGHT:
+ clear();
+ g_core = (g_core + 1) % CORE_COUNT;
+ break;
+ case KEY_LEFT:
+ clear();
+ g_page = (g_page - 1) % PAGE_COUNT;
+ break;
+ case KEY_RIGHT:
+ clear();
+ g_page = (g_page + 1) % PAGE_COUNT;
+ break;
+ case KEY_RESIZE:
+ clear();
+ ui_line_buff_resize();
+ ui_world_resize();
+
+ if (g_vlin) {
+ while (g_vsiz_rng >= MVEC_SIZE * 2 && g_wrld_zoom != 1) {
+ g_wrld_zoom /= 2;
+ ui_world_resize();
+ }
+ }
+
+ g_wcursor_mode = false;
+ break;
+ case 'W':
+ case 'S':
+ case 'w':
+ case 's':
+ case 'q':
+ ev_vscroll(ev);
+ break;
+ case 'A':
+ case 'a':
+ case 'd':
+ ev_hscroll(ev);
+ break;
+ case 'z':
+ case 'x':
+ ev_zoom(ev);
+ break;
+ case 'o':
+ case 'p':
+ case 'f':
+ case 'l':
+ ev_sel_proc(ev);
+ break;
+ case 'k':
+ ev_goto_sel_proc();
+ break;
+ case 'g':
+ if (g_page == PAGE_PROCESS) {
+ clear();
+ g_proc_genes = !g_proc_genes;
+ }
+
+ break;
+ case 'c':
+ if (g_page == PAGE_WORLD) {
+ clear();
+
+ if (g_vlin == 0) {
+ g_wcursor_mode = false;
+ } else {
+ g_wcursor_mode = !g_wcursor_mode;
+ }
+ }
+
+ break;
+ case ' ':
+ g_running = !g_running;
+ nodelay(stdscr, g_running);
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+ if (!g_running) {
+ u64 cycles = 1 << (((ev - '0') ? (ev - '0') : 10) - 1);
+ salis_step(cycles);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+void init() {
+ setlocale(LC_ALL, "");
+
+ initscr();
+ raw();
+ noecho();
+ curs_set(0);
+ keypad(stdscr, TRUE);
+
+ start_color();
+ init_color(COLOR_BLACK, 0, 0, 0);
+
+ init_pair(PAIR_NORMAL, COLOR_WHITE, COLOR_BLACK );
+ init_pair(PAIR_HEADER, COLOR_BLUE, COLOR_BLACK );
+ init_pair(PAIR_LIVE_PROC, COLOR_BLUE, COLOR_BLACK );
+ init_pair(PAIR_SELECTED_PROC, COLOR_YELLOW, COLOR_BLACK );
+ init_pair(PAIR_FREE_CELL, COLOR_BLACK, COLOR_BLUE );
+ init_pair(PAIR_ALLOC_CELL, COLOR_BLACK, COLOR_CYAN );
+ init_pair(PAIR_MEM_BLOCK_START, COLOR_BLACK, COLOR_WHITE );
+ init_pair(PAIR_SELECTED_MB1, COLOR_BLACK, COLOR_YELLOW );
+ init_pair(PAIR_SELECTED_MB2, COLOR_BLACK, COLOR_GREEN );
+ init_pair(PAIR_SELECTED_IP, COLOR_BLACK, COLOR_RED );
+ init_pair(PAIR_SELECTED_SP, COLOR_BLACK, COLOR_MAGENTA);
+
+#if ACTION == ACT_NEW
+ salis_init();
+#elif ACTION == ACT_LOAD
+ salis_load();
+#endif
+
+ g_wrld_zoom = 1;
+ g_step_block = 1;
+
+ ui_line_buff_resize();
+ ui_world_resize();
+}
+
+void exec() {
+ while (!g_exit) {
+ if (g_running) {
+ clock_t beg = clock();
+ salis_step(g_step_block - (g_steps % g_step_block));
+ clock_t end = clock();
+
+ if ((end - beg) < (CLOCKS_PER_SEC / 30)) {
+ g_step_block <<= 1;
+ }
+
+ if ((end - beg) >= (CLOCKS_PER_SEC / 60) && g_step_block != 1) {
+ g_step_block >>= 1;
+ }
+ }
+
+ ui_print();
+ ev_handle();
+ }
+}
+
+void quit() {
+ gfx_free();
+ ui_line_buff_free();
+ salis_save(SIM_PATH);
+ salis_free();
+ endwin();
+}
+
+int main() {
+ init();
+ exec();
+ quit();
+
+ return 0;
+}
diff --git a/src/ui/daemon.c b/src/ui/daemon.c
new file mode 100644
index 0000000..f74713d
--- /dev/null
+++ b/src/ui/daemon.c
@@ -0,0 +1,63 @@
+// Project: Salis
+// Author: Paul Oliver
+// Email: contact@pauloliver.dev
+
+/*
+ * Implements a minimal UI for the Salis simulator with minimal output and
+ * interruptible via OS signals. Ideal for running Salis in the background.
+ */
+
+#include <signal.h>
+#include <unistd.h>
+
+volatile bool g_running;
+u64 g_step_block;
+
+void sig_handler(int signo) {
+ switch (signo) {
+ case SIGINT:
+ case SIGTERM:
+ printf("signal received, stopping simulator...\n");
+ g_running = false;
+ break;
+ }
+}
+
+void step_block() {
+ clock_t beg = clock();
+ salis_step(g_step_block - (g_steps % g_step_block));
+ clock_t end = clock();
+
+ if ((end - beg) < (CLOCKS_PER_SEC * 4)) {
+ g_step_block <<= 1;
+ }
+
+ if ((end - beg) >= (CLOCKS_PER_SEC * 2) && g_step_block != 1) {
+ g_step_block >>= 1;
+ }
+
+ printf("simulator running on step '%#lx'\n", g_steps);
+}
+
+int main() {
+#if ACTION == ACT_NEW
+ salis_init();
+#elif ACTION == ACT_LOAD
+ salis_load();
+#endif
+
+ g_running = true;
+ g_step_block = 1;
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ while (g_running) {
+ step_block();
+ }
+
+ salis_save(SIM_PATH);
+ salis_free();
+
+ return 0;
+}