aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md114
-rw-r--r--anc/null/0123.asm4
-rw-r--r--anc/v1/55a.asm74
-rw-r--r--ancs/salis-v1/1n.asm5
-rw-r--r--ancs/salis-v1/55a.asm74
-rw-r--r--ancs/salis-v1/55b.asm74
-rw-r--r--arch/null/arch.c162
-rw-r--r--arch/null/plots.cpp5
-rw-r--r--arch/null/vars.py19
-rw-r--r--arch/v1/arch.c1042
-rw-r--r--arch/v1/plots.cpp59
-rw-r--r--arch/v1/vars.py120
-rw-r--r--core/client.cpp1553
-rw-r--r--core/common.c3
-rw-r--r--core/compress.c79
-rw-r--r--core/logger.c105
-rw-r--r--core/salis.c1139
-rw-r--r--core/server.c418
-rw-r--r--core/sql.c73
-rw-r--r--data.pngbin0 -> 1173583 bytes
-rwxr-xr-xsalis393
-rwxr-xr-xsalis.py471
-rw-r--r--sim.pngbin0 -> 311643 bytes
-rw-r--r--src/arch/dummy.c147
-rw-r--r--src/arch/salis-v1.c875
-rw-r--r--src/bench.c49
-rw-r--r--src/graphics.c223
-rw-r--r--src/salis.c688
-rw-r--r--src/ui/curses.c944
-rw-r--r--src/ui/daemon.c63
-rw-r--r--test/anc/v1/1rops.asm105
-rw-r--r--test/anc/v1/2rops.asm22
-rw-r--r--test/anc/v1/3rops.asm81
-rw-r--r--test/anc/v1/addrs.asm71
-rw-r--r--test/anc/v1/allos.asm27
-rw-r--r--test/anc/v1/ifnzs.asm46
-rw-r--r--test/anc/v1/ioops.asm100
-rw-r--r--test/anc/v1/jumps.asm52
-rw-r--r--test/anc/v1/mbops.asm13
-rw-r--r--test/anc/v1/noops.asm38
-rw-r--r--test/anc/v1/stack.asm47
-rw-r--r--test/test_build.py80
-rw-r--r--test/test_general.py31
-rw-r--r--test/test_v1.py46
-rw-r--r--test/ui/null/ui.c10
-rw-r--r--test/ui/null/vars.py3
-rw-r--r--test/ui/v1/1rops/ui.c64
-rw-r--r--test/ui/v1/1rops/vars.py3
-rw-r--r--test/ui/v1/2rops/ui.c46
-rw-r--r--test/ui/v1/2rops/vars.py3
-rw-r--r--test/ui/v1/3rops/ui.c60
-rw-r--r--test/ui/v1/3rops/vars.py3
-rw-r--r--test/ui/v1/addrs/ui.c47
-rw-r--r--test/ui/v1/addrs/vars.py3
-rw-r--r--test/ui/v1/allos/ui.c90
-rw-r--r--test/ui/v1/allos/vars.py3
-rw-r--r--test/ui/v1/ifnzs/ui.c46
-rw-r--r--test/ui/v1/ifnzs/vars.py3
-rw-r--r--test/ui/v1/ioops/ui.c55
-rw-r--r--test/ui/v1/ioops/vars.py3
-rw-r--r--test/ui/v1/jumps/ui.c21
-rw-r--r--test/ui/v1/jumps/vars.py3
-rw-r--r--test/ui/v1/mbops/ui.c36
-rw-r--r--test/ui/v1/mbops/vars.py3
-rw-r--r--test/ui/v1/noops/ui.c114
-rw-r--r--test/ui/v1/noops/vars.py3
-rw-r--r--test/ui/v1/stack/ui.c75
-rw-r--r--test/ui/v1/stack/vars.py3
-rw-r--r--todo.adoc5
-rw-r--r--ui/curses/ui.c1398
-rw-r--r--ui/curses/vars.py3
-rw-r--r--ui/daemon/ui.c57
-rw-r--r--ui/daemon/vars.py3
73 files changed, 8360 insertions, 3540 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5dadfea
--- /dev/null
+++ b/README.md
@@ -0,0 +1,114 @@
+# SALIS: A-life Simulator
+
+![SALIS simulation](sim.png)
+*SALIS simulation running the V1 architecture with the curses user interface*
+
+## Overview
+*SALIS* is a platform for conducting artificial life experiments. It enables
+the development of Tierra-like virtual machines, with certain limitations. For
+newcomers, I recommend exploring Tierra first. The following resources provide
+valuable context and insight into the motivations and implementation of both
+Tierra and this project:
+
+- [Video about Tierra](https://www.youtube.com/watch?v=Wl5rRGVD0QI)
+- [Read about Tierra](https://tomray.me/pubs/doc/index.html#What)
+
+## SALIS V1 Reimplementation
+A fully functional clone of the V1 architecture for the SALIS virtual machine
+has been implemented using the tools available in this repository. For more
+information on the V1 architecture, including its similarities and differences
+with the original Tierra simulator, check out the following resources:
+
+- [SALIS V1 repository](https://git.pauloliver.dev/salis-v1/about/)
+- [SALIS V1 introductory playlist](https://www.youtube.com/watch?v=jCFmOCvy6po&list=PLrEmYrpTcDJY2NdGL6B7wIRbKGp_6NkxY)
+
+## Usage
+*SALIS* simulations are initialized using the provided `salis.py` python script.
+Use `salis.py new [...]` to start new simulations and `salis.py load [...]` to
+load saved simulations. For a full list of available arguments for each command,
+run `salis.py new --help` and `salis.py load --help`, respectively.
+
+The python script compiles a temporary executable on the fly (compilation
+typically takes less than a second) based on the specified arguments and
+launches it immediately.
+
+Different VM architectures can be implemented as standalone C files, plus an
+associated `vars.py` script, within in the `arch/` directory. When creating a
+new simulation, you can select a specific architecture using the `--arch`
+argument.
+
+Similarly, different user interfaces are implemented as C files, plus an
+associated `vars.py` script, within the `ui/` directory. For example, the
+`curses` UI launches a terminal-based simulation visualizer, allowing easy
+exploration of *SALIS* memory cores and processes. In contrast, the `daemon` UI
+provides minimal output, making it ideal for running *SALIS* as a background
+service. Unlike the `--arch` argument, you can choose a different `--ui` argument
+each time you load a saved simulation.
+
+As an example, the following command will launch a new *SALIS* simulation with 4
+copies of the `55a` ancestor organisms pre-compiled in each memory core. It will
+use the `v1` architecture, run on 8 memory cores, with each core having a size
+of 2^22 bytes. The PRNG seed is set to `123456789`:
+```console
+user@host$ ./salis.py new -A55a -av1 -c8 -C4 -m22 -nworld-1 -s123456789 -o
+```
+
+Use `Ctrl-C` to exit the simulator.
+
+Upon exit, the simulation state will be automatically saved to
+`${HOME}/.salis/world-1/`. As long as the contents of this directory are not
+removed, you can reload the saved simulation with the following command:
+```console
+user@host$ ./salis.py load -n world-1 -o
+```
+
+## Data server and client
+
+![SALIS data client](data.png)
+*SALIS data client showing live simulation data*
+
+This project includes a native data visualizer that plots live simulation data.
+A generic set of plots is configured by default for all VM architectures. Each
+architecture may expand on these by configuring its own set of plots within
+`arch/{arch}/plots.cpp`. Once a simulation has been created (i.e., exists in
+`${HOME}/.salis`), you may launch the data server with:
+```console
+user@host$ ./salis.py server -n world-1 -o
+```
+
+The server may run regardless of whether the simulator itself is running or not.
+Once the server is running, you may attach a client from the local network by
+running:
+```console
+user@host$ ./salis.py client -o -i ${SERVER_IP}
+```
+
+## Dependencies
+- C compiler
+- Python
+- SQLite
+- Zlib
+
+## Dependencies (curses UI)
+- NCurses
+
+## Dependencies (data client)
+- GLFW
+- ImGui
+- ImPlot
+- OpenGL
+
+## Unit tests
+A set of unit tests are included in this repo. Run them using the following command:
+```console
+user@host$ ./salis.py test
+```
+
+Optionally, you may run a single test (or a test subset) using the `-p` argument:
+```console
+user@host$ ./salis.py test -p 'test_v1.*'
+```
+
+Tests will fail in case any needed dependencies are missing, and thus help ensure
+the host system is properly configured. Ensure all tests pass before attempting to run
+new simulations.
diff --git a/anc/null/0123.asm b/anc/null/0123.asm
new file mode 100644
index 0000000..396e572
--- /dev/null
+++ b/anc/null/0123.asm
@@ -0,0 +1,4 @@
+null 00
+null 01
+null 02
+null 03
diff --git a/anc/v1/55a.asm b/anc/v1/55a.asm
new file mode 100644
index 0000000..297d3ee
--- /dev/null
+++ b/anc/v1/55a.asm
@@ -0,0 +1,74 @@
+; index
+; [section] begin gene
+; [section] measure gene
+; [section] allocation gene
+; [section] copy gene
+; [section] split gene
+; [section] end gene
+
+; [section] begin gene
+loka
+
+; [section] measure gene
+adrb
+keya
+adrf
+keya
+nop1
+incn
+nop1
+subn
+nop1
+nop1
+
+; [section] allocation gene
+lokb
+notn
+nop3
+pshn
+nop1
+pshn
+nop3
+ifnz
+nop3
+jmpf
+keyc
+allb
+nop1
+nop2
+jmpf
+keyd
+lokc
+allf
+nop1
+nop2
+
+; [section] copy gene
+lokd
+load
+nop0
+nop3
+wrte
+nop2
+nop3
+incn
+incn
+nop2
+decn
+nop1
+ifnz
+nop1
+jmpb
+keyd
+
+; [section] split gene
+splt
+popn
+nop3
+popn
+nop1
+jmpb
+keyb
+
+; [section] end gene
+loka
diff --git a/ancs/salis-v1/1n.asm b/ancs/salis-v1/1n.asm
deleted file mode 100644
index b441922..0000000
--- a/ancs/salis-v1/1n.asm
+++ /dev/null
@@ -1,5 +0,0 @@
-; Project: Salis
-; Author: Paul Oliver
-; Email: contact@pauloliver.dev
-
-noop
diff --git a/ancs/salis-v1/55a.asm b/ancs/salis-v1/55a.asm
deleted file mode 100644
index 6e060ba..0000000
--- a/ancs/salis-v1/55a.asm
+++ /dev/null
@@ -1,74 +0,0 @@
-; Project: Salis
-; Author: Paul Oliver
-; Email: contact@pauloliver.dev
-
-; Based on the original 55.anc ancestor from salis-v1:
-; https://git.pauloliver.dev/salis-v1/tree/bin/genomes/55.anc
-; This organism replicates bidirectionally.
-
-; begin template
-loka
-
-; measure gene
-adrb
-keya
-adrf
-keya
-nop1
-incn
-nop1
-subn
-nop1
-nop1
-
-; alloc gene
-lokb
-notn
-nop3
-pshn
-nop1
-pshn
-nop3
-ifnz
-nop3
-jmpf
-keyc
-allb
-nop1
-nop2
-jmpf
-keyd
-lokc
-allf
-nop1
-nop2
-
-; copy gene
-lokd
-load
-nop0
-nop3
-wrte
-nop2
-nop3
-incn
-incn
-nop2
-decn
-nop1
-ifnz
-nop1
-jmpb
-keyd
-
-; split gene
-splt
-popn
-nop3
-popn
-nop1
-jmpb
-keyb
-
-; end template
-loka
diff --git a/ancs/salis-v1/55b.asm b/ancs/salis-v1/55b.asm
deleted file mode 100644
index 7c830e7..0000000
--- a/ancs/salis-v1/55b.asm
+++ /dev/null
@@ -1,74 +0,0 @@
-; Project: Salis
-; Author: Paul Oliver
-; Email: contact@pauloliver.dev
-
-; Based on the original 55.anc ancestor from salis-v1:
-; https://git.pauloliver.dev/salis-v1/tree/bin/genomes/55.anc
-; This organism replicates bidirectionally.
-
-; begin template
-lokb
-
-; measure gene
-adrb
-keyb
-adrf
-keyb
-nop1
-incn
-nop1
-subn
-nop1
-nop1
-
-; alloc gene
-lokc
-notn
-nop3
-pshn
-nop1
-pshn
-nop3
-ifnz
-nop3
-jmpf
-keyd
-allb
-nop1
-nop2
-jmpf
-keye
-lokd
-allf
-nop1
-nop2
-
-; copy gene
-loke
-load
-nop0
-nop3
-wrte
-nop2
-nop3
-incn
-incn
-nop2
-decn
-nop1
-ifnz
-nop1
-jmpb
-keye
-
-; split gene
-splt
-popn
-nop3
-popn
-nop1
-jmpb
-keyc
-
-; end template
-lokb
diff --git a/arch/null/arch.c b/arch/null/arch.c
new file mode 100644
index 0000000..7dd521e
--- /dev/null
+++ b/arch/null/arch.c
@@ -0,0 +1,162 @@
+#if defined(COMMAND_NEW)
+void arch_core_init(struct Core *core) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ uint64_t addr = UINT64_HALF;
+#else
+ uint64_t addr = 0;
+#endif
+
+ for (uint64_t i = 0; i < CLONES; ++i) {
+ uint64_t addr_clone = addr + (MVEC_SIZE / CLONES) * i;
+
+ struct Proc *panc = proc_fetch(core, i);
+
+ panc->mb0a = addr_clone;
+ panc->mb0s = ANC_SIZE;
+ panc->ip = addr_clone;
+ panc->sp = addr_clone;
+ }
+}
+#endif
+
+void arch_core_free(struct Core *core) {
+ assert(core);
+
+ (void)core;
+}
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+void arch_core_save(FILE *f, const struct Core *core) {
+ assert(f);
+ assert(core);
+
+ (void)f;
+ (void)core;
+}
+#endif
+
+#if defined(COMMAND_LOAD)
+void arch_core_load(FILE *f, struct Core *core) {
+ assert(f);
+ assert(core);
+
+ (void)f;
+ (void)core;
+}
+#endif
+
+uint64_t arch_proc_mb0_addr(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->mb0a;
+}
+
+uint64_t arch_proc_mb0_size(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->mb0s;
+}
+
+uint64_t arch_proc_mb1_addr(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->mb1a;
+}
+
+uint64_t arch_proc_mb1_size(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->mb1s;
+}
+
+uint64_t arch_proc_ip_addr(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->ip;
+}
+
+uint64_t arch_proc_sp_addr(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->sp;
+}
+
+uint64_t arch_proc_slice(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 1;
+}
+
+void arch_on_proc_kill(struct Core *core) {
+ assert(core);
+ assert(core->pnum > 1);
+
+ (void)core;
+}
+
+void arch_proc_step(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return;
+}
+
+#if !defined(NDEBUG)
+void arch_validate_proc(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ assert(true);
+}
+#endif
+
+wchar_t arch_symbol(uint8_t inst) {
+ switch (inst) {
+#define INST(core, pref, index, label, mnemonic, symbol) case index: return symbol;
+ INST_SET(core, pref)
+#undef INST
+ }
+
+ assert(false);
+ return L'\0';
+}
+
+const char *arch_mnemonic(uint8_t inst) {
+ switch (inst) {
+#define INST(core, pref, index, label, mnemonic, symbol) case index: return mnemonic;
+ INST_SET(core, pref)
+#undef INST
+ }
+
+ assert(false);
+ return NULL;
+}
+
+#if defined(COMMAND_NEW)
+void arch_push_data_header(void) {
+ assert(g_sim_db);
+ log_info("Creating arch table in SQLite database");
+ sql_exec(NULL, NULL, "create table arch (step int not null);");
+}
+#endif
+
+void arch_push_data_line(FILE *eva_file) {
+ assert(g_sim_db);
+ assert(eva_file);
+ (void)eva_file;
+
+ log_info("Pushing row to arch table in SQLite database");
+ sql_exec(NULL, NULL, "insert into arch (step) values (%ld);", g_steps);
+}
diff --git a/arch/null/plots.cpp b/arch/null/plots.cpp
new file mode 100644
index 0000000..8cece2f
--- /dev/null
+++ b/arch/null/plots.cpp
@@ -0,0 +1,5 @@
+std::array<TraceNamed<ImS64>, 0> g_arch_traces = {};
+std::array<TraceHeatmap<ImS64>, 0> g_arch_traces_heatmaps = {};
+std::array<PlotLines, 0> g_arch_plots = {};
+std::array<PlotStacked, 0> g_arch_plots_stacked = {};
+std::array<PlotHeatmap, 0> g_arch_plots_heatmaps = {};
diff --git a/arch/null/vars.py b/arch/null/vars.py
new file mode 100644
index 0000000..edc5b66
--- /dev/null
+++ b/arch/null/vars.py
@@ -0,0 +1,19 @@
+inst_set = [(["null", f"{i:02x}"], symbol) for i, symbol in enumerate(
+ "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟"
+ "⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿"
+ "⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟"
+ "⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿"
+)]
+
+core_data_fields = []
+core_fields = []
+muta_flip = False
+mvec_loop = True
+proc_fields = [
+ ("uint64_t", "ip"),
+ ("uint64_t", "sp"),
+ ("uint64_t", "mb0a"),
+ ("uint64_t", "mb0s"),
+ ("uint64_t", "mb1a"),
+ ("uint64_t", "mb1s"),
+]
diff --git a/arch/v1/arch.c b/arch/v1/arch.c
new file mode 100644
index 0000000..1501b42
--- /dev/null
+++ b/arch/v1/arch.c
@@ -0,0 +1,1042 @@
+// Based on the original salis-v1 VM architecture:
+// https://git.pauloliver.dev/salis-v1/about/
+
+// index
+// [section] macros & enums
+// [section] globals
+// [section] main architecture functions
+// [section] data aggregation functions
+
+// ----------------------------------------------------------------------------
+// [section] macros & enums
+// ----------------------------------------------------------------------------
+#define INST_EVENT_ARRAYS(core) \
+ INST_EVENT_ARRAY(core, 0, pop) /* instruction population */ \
+ INST_EVENT_ARRAY(core, 1, exe) /* instruction executions */ \
+ INST_EVENT_ARRAY(core, 2, wrt) /* instruction writes */
+
+#define ARCH_EVENT_ARRAYS(core) \
+ ARCH_EVENT_ARRAY(core, 0, wev) /* write events array*/ \
+ ARCH_EVENT_ARRAY(core, 1, xev) /* memory block swap events array */
+#define ARCH_EVENT_ARRAYS_COUNT 2
+
+enum {
+#define INST(core, pref, index, label, mnemonic, symbol) label,
+ INST_SET(core, pref)
+#undef INST
+};
+
+// ----------------------------------------------------------------------------
+// [section] globals
+// ----------------------------------------------------------------------------
+thrd_t g_arch_eva_thrds[CORES][ARCH_EVENT_ARRAYS_COUNT];
+struct DeflateParams g_arch_eva_deflate_params[CORES][ARCH_EVENT_ARRAYS_COUNT];
+
+// ----------------------------------------------------------------------------
+// [section] main architecture functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW)
+void arch_core_init(struct Core *core) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ uint64_t addr = UINT64_HALF;
+#else
+ uint64_t addr = 0;
+#endif
+
+ for (uint64_t i = 0; i < CLONES; ++i) {
+ uint64_t addr_clone = addr + (MVEC_SIZE / CLONES) * i;
+
+ struct Proc *panc = proc_fetch(core, i);
+
+ panc->mb0a = addr_clone;
+ panc->mb0s = ANC_SIZE;
+ panc->ip = addr_clone;
+ panc->sp = addr_clone;
+ }
+}
+#endif
+
+void arch_core_free(struct Core *core) {
+ assert(core);
+ (void)core;
+}
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+void arch_core_save(FILE *f, const struct Core *core) {
+ assert(f);
+ assert(core);
+
+ fwrite(core->iexe, sizeof(uint64_t), INST_COUNT, f);
+ fwrite(core->iwrt, sizeof(uint64_t), INST_COUNT, f);
+ fwrite(&core->wmb0, sizeof(uint64_t), 1, f);
+ fwrite(&core->wmb1, sizeof(uint64_t), 1, f);
+ fwrite(&core->wdea, sizeof(uint64_t), 1, f);
+#define ARCH_EVENT_ARRAY(core, index, ev) \
+ fwrite(core->ev##a, sizeof(uint64_t), MVEC_SIZE, f);
+ ARCH_EVENT_ARRAYS(core)
+#undef ARCH_EVENT_ARRAY
+}
+#endif
+
+#if defined(COMMAND_LOAD)
+void arch_core_load(FILE *f, struct Core *core) {
+ assert(f);
+ assert(core);
+
+ fread(core->iexe, sizeof(uint64_t), INST_COUNT, f);
+ fread(core->iwrt, sizeof(uint64_t), INST_COUNT, f);
+ fread(&core->wmb0, sizeof(uint64_t), 1, f);
+ fread(&core->wmb1, sizeof(uint64_t), 1, f);
+ fread(&core->wdea, sizeof(uint64_t), 1, f);
+#define ARCH_EVENT_ARRAY(core, index, ev) \
+ fread(core->ev##a, sizeof(uint64_t), MVEC_SIZE, f);
+ ARCH_EVENT_ARRAYS(core)
+#undef ARCH_EVENT_ARRAY
+}
+#endif
+
+uint64_t arch_proc_mb0_addr(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->mb0a;
+}
+
+uint64_t arch_proc_mb0_size(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->mb0s;
+}
+
+uint64_t arch_proc_mb1_addr(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->mb1a;
+}
+
+uint64_t arch_proc_mb1_size(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->mb1s;
+}
+
+uint64_t arch_proc_ip_addr(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->ip;
+}
+
+uint64_t arch_proc_sp_addr(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return proc_get(core, pix)->sp;
+}
+
+uint64_t arch_proc_slice(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ (void)core;
+ (void)pix;
+
+ return 1;
+}
+
+void _free_memory_block(struct Core *core, uint64_t addr, uint64_t size) {
+ assert(core);
+ assert(size);
+
+ for (uint64_t i = 0; i < size; ++i) {
+ mvec_free(core, addr + i);
+ }
+}
+
+void arch_on_proc_kill(struct Core *core) {
+ assert(core);
+ assert(core->pnum > 1);
+
+ struct Proc *pfst = proc_fetch(core, core->pfst);
+
+ _free_memory_block(core, pfst->mb0a, pfst->mb0s);
+
+ if (pfst->mb1s) {
+ _free_memory_block(core, pfst->mb1a, pfst->mb1s);
+ }
+
+ memcpy(pfst, &g_dead_proc, sizeof(struct Proc));
+}
+
+uint8_t _get_inst(const struct Core *core, uint64_t addr) {
+ assert(core);
+
+ return mvec_get_inst(core, addr) % INST_COUNT;
+}
+
+void _increment_ip(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+
+ proc->ip++;
+ proc->sp = proc->ip;
+}
+
+bool _is_between(uint8_t inst, uint8_t lo, uint8_t hi) {
+ assert(inst < INST_COUNT);
+ assert(lo < INST_COUNT);
+ assert(hi < INST_COUNT);
+ assert(lo < hi);
+
+ return (inst >= lo) && (inst <= hi);
+}
+
+bool _is_key(uint8_t inst) {
+ assert(inst < INST_COUNT);
+
+ return _is_between(inst, keya, keyp);
+}
+
+bool _is_lock(uint8_t inst) {
+ assert(inst < INST_COUNT);
+
+ return _is_between(inst, loka, lokp);
+}
+
+bool _is_rmod(uint8_t inst) {
+ assert(inst < INST_COUNT);
+
+ return _is_between(inst, nop0, nop3);
+}
+
+bool _key_lock_match(uint8_t key, uint8_t lock) {
+ assert(key < INST_COUNT);
+ assert(lock < INST_COUNT);
+ assert(_is_key(key));
+
+ return (key - keya) == (lock - loka);
+}
+
+bool _seek(struct Core *core, uint64_t pix, bool fwrd) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint8_t next = _get_inst(core, proc->ip + 1);
+
+ if (!_is_key(next)) {
+ _increment_ip(core, pix);
+ return false;
+ }
+
+ uint8_t spin = _get_inst(core, proc->sp);
+
+ if (_key_lock_match(next, spin)) {
+ return true;
+ }
+
+ if (fwrd) {
+ proc->sp++;
+ } else {
+ proc->sp--;
+ }
+
+ return false;
+}
+
+void _jump(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+
+#if !defined(NDEBUG)
+ uint8_t next = _get_inst(core, proc->ip + 1);
+ uint8_t spin = _get_inst(core, proc->sp);
+ assert(_is_key(next));
+ assert(_is_lock(spin));
+ assert(_key_lock_match(next, spin));
+#endif
+
+ proc->ip = proc->sp;
+}
+
+void _get_reg_addr_list(struct Core *core, uint64_t pix, uint64_t **rlist, int rcount, bool offset) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ assert(rlist);
+ assert(rcount);
+ assert(rcount < 4);
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint64_t madr = proc->ip + (offset ? 2 : 1);
+
+ for (int i = 0; i < rcount; ++i) {
+ rlist[i] = &proc->r0x;
+ }
+
+ for (int i = 0; i < rcount; ++i) {
+ uint64_t mnxt = madr + i;
+ uint8_t mins = _get_inst(core, mnxt);
+
+ if (!_is_rmod(mins)) {
+ break;
+ }
+
+ switch (mins) {
+ case nop0:
+ rlist[i] = &proc->r0x;
+ break;
+ case nop1:
+ rlist[i] = &proc->r1x;
+ break;
+ case nop2:
+ rlist[i] = &proc->r2x;
+ break;
+ case nop3:
+ rlist[i] = &proc->r3x;
+ break;
+ }
+ }
+}
+
+void _addr(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint64_t *reg;
+
+#if !defined(NDEBUG)
+ uint8_t next = _get_inst(core, proc->ip + 1);
+ uint8_t spin = _get_inst(core, proc->sp);
+ assert(_is_key(next));
+ assert(_is_lock(spin));
+ assert(_key_lock_match(next, spin));
+#endif
+
+ _get_reg_addr_list(core, pix, &reg, 1, true);
+ *reg = proc->sp;
+
+ _increment_ip(core, pix);
+}
+
+void _ifnz(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint64_t *reg;
+
+ _get_reg_addr_list(core, pix, &reg, 1, false);
+
+ uint64_t jmod = _is_rmod(_get_inst(core, proc->ip + 1)) ? 1 : 0;
+ uint64_t rmod = *reg ? 1 : 2;
+
+ proc->ip += jmod + rmod;
+ proc->sp = proc->ip;
+}
+
+void _free_child_memory_of(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+
+ assert(proc->mb1s);
+
+ _free_memory_block(core, proc->mb1a, proc->mb1s);
+
+ proc->mb1a = 0;
+ proc->mb1s = 0;
+}
+
+void _alloc(struct Core *core, uint64_t pix, bool fwrd) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint64_t *regs[2];
+
+ _get_reg_addr_list(core, pix, regs, 2, false);
+
+ uint64_t bsize = *regs[0];
+
+ // Do nothing if block-size is zero
+ if (!bsize) {
+ _increment_ip(core, pix);
+ return;
+ }
+
+ // Do nothing if seek pointer is not adjacent to allocated memory block
+ if (proc->mb1s) {
+ uint64_t exp_addr = proc->mb1a;
+
+ if (fwrd) {
+ exp_addr += proc->mb1s;
+ } else {
+ exp_addr--;
+ }
+
+ if (proc->sp != exp_addr) {
+ _increment_ip(core, pix);
+ return;
+ }
+ }
+
+ // Allocation was successful, store block address on register
+ if (proc->mb1s == bsize) {
+ _increment_ip(core, pix);
+ *regs[1] = proc->mb1a;
+ return;
+ }
+
+ // Seek pointer collided with another allocated block, discard and keep looking
+ if (mvec_is_alloc(core, proc->sp)) {
+ if (proc->mb1s) {
+ _free_child_memory_of(core, pix);
+ }
+
+ if (fwrd) {
+ proc->sp++;
+ } else {
+ proc->sp--;
+ }
+
+ return;
+ }
+
+ // Free (non-allocated) byte found, enlarge child block 1 byte
+ mvec_alloc(core, proc->sp);
+
+ if (!proc->mb1s || !fwrd) {
+ proc->mb1a = proc->sp;
+ }
+
+ proc->mb1s++;
+
+ // Advance seek pointer
+ if (fwrd) {
+ proc->sp++;
+ } else {
+ proc->sp--;
+ }
+}
+
+void _bswap(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+
+ if (proc->mb1s) {
+ uint64_t tmpa = proc->mb0a;
+ uint64_t tmps = proc->mb0s;
+
+ proc->mb0a = proc->mb1a;
+ proc->mb0s = proc->mb1s;
+ proc->mb1a = tmpa;
+ proc->mb1s = tmps;
+
+ // Memory block swap events mark all addresses within both blocks
+ for (uint64_t i = 0; i < proc->mb0s; i++) {
+ uint64_t addr = proc->mb0a + i;
+
+#if defined(MVEC_LOOP)
+ ++core->xeva[mvec_loop(addr)];
+#else
+ ++core->xeva[addr];
+#endif
+ }
+
+ for (uint64_t i = 0; i < proc->mb1s; i++) {
+ uint64_t addr = proc->mb1a + i;
+
+#if defined(MVEC_LOOP)
+ ++core->xeva[mvec_loop(addr)];
+#else
+ ++core->xeva[addr];
+#endif
+ }
+ }
+
+ _increment_ip(core, pix);
+}
+
+void _bclear(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+
+ if (proc->mb1s) {
+ _free_child_memory_of(core, pix);
+ }
+
+ _increment_ip(core, pix);
+}
+
+void _split(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+
+ if (proc->mb1s) {
+ struct Proc child = {0};
+
+ child.ip = proc->mb1a;
+ child.sp = proc->mb1a;
+ child.mb0a = proc->mb1a;
+ child.mb0s = proc->mb1s;
+
+ proc->mb1a = 0;
+ proc->mb1s = 0;
+
+ // A new organism is born :)
+ proc_new(core, &child);
+ } else {
+ assert(!proc->mb1a);
+ }
+
+ _increment_ip(core, pix);
+}
+
+void _3rop(struct Core *core, uint64_t pix, uint8_t inst) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ uint64_t *regs[3];
+
+ _get_reg_addr_list(core, pix, regs, 3, false);
+
+ // Organisms can do arithmetic using any sequence of 3 registers
+ switch (inst) {
+ case addn:
+ *regs[0] = *regs[1] + *regs[2];
+ break;
+ case subn:
+ *regs[0] = *regs[1] - *regs[2];
+ break;
+ case muln:
+ *regs[0] = *regs[1] * *regs[2];
+ break;
+ case divn:
+ // Division by zero
+ // Do nothing
+ if (*regs[2]) {
+ *regs[0] = *regs[1] / *regs[2];
+ }
+
+ break;
+ default:
+ assert(false);
+ }
+
+ _increment_ip(core, pix);
+}
+
+void _1rop(struct Core *core, uint64_t pix, uint8_t inst) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ uint64_t *reg;
+
+ _get_reg_addr_list(core, pix, &reg, 1, false);
+
+ switch (inst) {
+ case incn:
+ (*reg)++;
+ break;
+ case decn:
+ (*reg)--;
+ break;
+ case notn:
+ *reg = !(*reg);
+ break;
+ case shfl:
+ *reg <<= 1;
+ break;
+ case shfr:
+ *reg >>= 1;
+ break;
+ case zero:
+ *reg = 0;
+ break;
+ case unit:
+ *reg = 1;
+ break;
+ default:
+ assert(false);
+ }
+
+ _increment_ip(core, pix);
+}
+
+void _push(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint64_t *reg;
+
+ _get_reg_addr_list(core, pix, &reg, 1, false);
+
+ proc->s7 = proc->s6;
+ proc->s6 = proc->s5;
+ proc->s5 = proc->s4;
+ proc->s4 = proc->s3;
+ proc->s3 = proc->s2;
+ proc->s2 = proc->s1;
+ proc->s1 = proc->s0;
+ proc->s0 = *reg;
+
+ _increment_ip(core, pix);
+}
+
+void _pop(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint64_t *reg;
+
+ _get_reg_addr_list(core, pix, &reg, 1, false);
+
+ *reg = proc->s0;
+ proc->s0 = proc->s1;
+ proc->s1 = proc->s2;
+ proc->s2 = proc->s3;
+ proc->s3 = proc->s4;
+ proc->s4 = proc->s5;
+ proc->s5 = proc->s6;
+ proc->s6 = proc->s7;
+ proc->s7 = 0;
+
+ _increment_ip(core, pix);
+}
+
+int _sp_dir(uint64_t src, uint64_t dst) {
+ if (src == dst) {
+ return 0;
+ } else if (src - dst <= dst - src) {
+ return -1;
+ } else {
+ return 1;
+ }
+}
+
+void _load(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint64_t *regs[2];
+
+ _get_reg_addr_list(core, pix, regs, 2, false);
+
+ int sp_dir = _sp_dir(proc->sp, *regs[0]);
+
+ if (sp_dir == 1) {
+ proc->sp++;
+ } else if (sp_dir == -1) {
+ proc->sp--;
+ } else {
+ *regs[1] = mvec_get_inst(core, *regs[0]);
+ _increment_ip(core, pix);
+ }
+}
+
+bool _is_writeable_by(const struct Core *core, uint64_t addr, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ return !mvec_is_alloc(core, addr) || mvec_is_proc_owner(core, addr, pix);
+}
+
+void _write(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint64_t *regs[2];
+
+ _get_reg_addr_list(core, pix, regs, 2, false);
+
+ int sp_dir = _sp_dir(proc->sp, *regs[0]);
+
+ if (sp_dir == 1) {
+ proc->sp++;
+ } else if (sp_dir == -1) {
+ proc->sp--;
+ } else {
+ if (_is_writeable_by(core, *regs[0], pix)) {
+ uint64_t addr = *regs[0];
+ uint8_t inst = *regs[1] % INST_COUNT;
+
+ // Store write event
+ ++core->iwrt[inst];
+ ++core->weva[addr];
+
+ if (mvec_is_in_mb0_of_proc(core, addr, pix)) {
+ ++core->wmb0;
+ } else if (mvec_is_in_mb1_of_proc(core, addr, pix)) {
+ ++core->wmb1;
+ } else {
+ ++core->wdea;
+ }
+
+ // Write instruction
+ uint8_t inst_rep = *regs[1] % INST_CAP;
+ mvec_set_inst(core, addr, inst_rep);
+ }
+
+ _increment_ip(core, pix);
+ }
+}
+
+void _2rop(struct Core *core, uint64_t pix, uint8_t inst) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ uint64_t *regs[2];
+
+ _get_reg_addr_list(core, pix, regs, 2, false);
+
+ switch (inst) {
+ case dupl:
+ *regs[1] = *regs[0];
+ break;
+ case swap:
+ {
+ uint64_t tmp = *regs[0];
+ *regs[0] = *regs[1];
+ *regs[1] = tmp;
+ }
+
+ break;
+ default:
+ assert(false);
+ }
+
+ _increment_ip(core, pix);
+}
+
+void arch_proc_step(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ struct Proc *proc = proc_fetch(core, pix);
+ uint8_t inst = _get_inst(core, proc->ip);
+
+ // Store specific instruction execution event in database
+ ++core->iexe[inst];
+
+ // Execute instruction
+ switch (inst) {
+ case jmpb:
+ if (_seek(core, pix, false)) {
+ _jump(core, pix);
+ }
+
+ break;
+ case jmpf:
+ if (_seek(core, pix, true)) {
+ _jump(core, pix);
+ }
+
+ break;
+ case adrb:
+ if (_seek(core, pix, false)) {
+ _addr(core, pix);
+ }
+
+ break;
+ case adrf:
+ if (_seek(core, pix, true)) {
+ _addr(core, pix);
+ }
+
+ break;
+ case ifnz:
+ _ifnz(core, pix);
+ break;
+ case allb:
+ _alloc(core, pix, false);
+ break;
+ case allf:
+ _alloc(core, pix, true);
+ break;
+ case bswp:
+ _bswap(core, pix);
+ break;
+ case bclr:
+ _bclear(core, pix);
+ break;
+ case splt:
+ _split(core, pix);
+ break;
+ case addn:
+ case subn:
+ case muln:
+ case divn:
+ _3rop(core, pix, inst);
+ break;
+ case incn:
+ case decn:
+ case notn:
+ case shfl:
+ case shfr:
+ case zero:
+ case unit:
+ _1rop(core, pix, inst);
+ break;
+ case pshn:
+ _push(core, pix);
+ break;
+ case popn:
+ _pop(core, pix);
+ break;
+ case load:
+ _load(core, pix);
+ break;
+ case wrte:
+ _write(core, pix);
+ break;
+ case dupl:
+ case swap:
+ _2rop(core, pix, inst);
+ break;
+ default:
+ _increment_ip(core, pix);
+ break;
+ }
+
+ return;
+}
+
+#if !defined(NDEBUG)
+void arch_validate_proc(const struct Core *core, uint64_t pix) {
+ assert(core);
+
+ const struct Proc *proc = proc_get(core, pix);
+
+ assert(proc->mb0s);
+
+ if (proc->mb1a) {
+ assert(proc->mb1s);
+ }
+
+ for (uint64_t i = 0; i < proc->mb0s; ++i) {
+ uint64_t addr = proc->mb0a + i;
+ assert(mvec_is_alloc(core, addr));
+ assert(mvec_is_proc_owner(core, addr, pix));
+ }
+
+ for (uint64_t i = 0; i < proc->mb1s; ++i) {
+ uint64_t addr = proc->mb1a + i;
+ assert(mvec_is_alloc(core, addr));
+ assert(mvec_is_proc_owner(core, addr, pix));
+ }
+}
+#endif
+
+wchar_t arch_symbol(uint8_t inst) {
+ switch (inst % INST_COUNT) {
+#define INST(core, pref, index, label, mnemonic, symbol) case index: return symbol;
+ INST_SET(core, pref)
+#undef INST
+ }
+
+ assert(false);
+ return L'\0';
+}
+
+const char *arch_mnemonic(uint8_t inst) {
+ switch (inst % INST_COUNT) {
+#define INST(core, pref, index, label, mnemonic, symbol) case index: return mnemonic;
+ INST_SET(core, pref)
+#undef INST
+ }
+
+ assert(false);
+ return NULL;
+}
+
+// ----------------------------------------------------------------------------
+// [section] data aggregation functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW)
+void arch_push_data_header(void) {
+ assert(g_sim_db);
+
+ log_info("Creating arch table in SQLite database");
+ sql_exec(
+ NULL, NULL,
+ "create table arch ("
+#define INST(core, pref, index, label, mnemonic, symbol) \
+ #label "_" #pref "_" #core " int not null, "
+#define INST_EVENT_ARRAY(core, index, iv) \
+ INST_SET(core, iv)
+#define ARCH_EVENT_ARRAY(core, index, ev) \
+ #ev EVENT_ARRAYS_SIZE_COL_MARKER #core " int not null, " \
+ #ev "_" #core " blob, "
+#define FOR_CORE(i) \
+ "wmb0_" #i " int not null, " \
+ "wmb1_" #i " int not null, " \
+ "wdea_" #i " int not null, " \
+ INST_EVENT_ARRAYS(i) \
+ ARCH_EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_EVENT_ARRAY
+#undef INST_EVENT_ARRAY
+#undef INST
+ "step int not null"
+ ");"
+ );
+}
+#endif
+
+void arch_push_data_line(FILE *eva_file) {
+ assert(g_sim_db);
+ assert(eva_file);
+
+ // Measure instruction population
+ for (int i = 0; i < CORES; ++i) {
+ struct Core *core = &g_cores[i];
+
+ for (uint64_t j = 0; j < MVEC_SIZE; ++j) {
+ ++core->ipop[_get_inst(core, j)];
+ }
+
+#if !defined(NDEBUG)
+ uint64_t pop_tot = 0;
+
+ for (int j = 0; j < INST_COUNT; ++j) {
+ pop_tot += core->ipop[j];
+ }
+
+ assert(pop_tot == MVEC_SIZE);
+#endif
+ }
+
+ // Compress event arrays
+ // Compression (deflation) is CPU intensive so it is done in parallel
+ memset(&g_arch_eva_deflate_params, 0, sizeof(struct DeflateParams) * CORES * ARCH_EVENT_ARRAYS_COUNT);
+ uint64_t blob_sizes[CORES][ARCH_EVENT_ARRAYS_COUNT];
+
+ for (int i = 0; i < CORES; ++i) {
+ for (int j = 0; j < ARCH_EVENT_ARRAYS_COUNT; ++j) {
+ uint64_t *in = NULL;
+
+ switch (j) {
+#define ARCH_EVENT_ARRAY(core, index, ev) \
+ case index: in = g_cores[i].ev##a; break;
+ ARCH_EVENT_ARRAYS(core)
+#undef ARCH_EVENT_ARRAY
+ default: assert(false);
+ }
+
+ // Compress event data
+ struct DeflateParams *params = &g_arch_eva_deflate_params[i][j];
+ params->size = EVENT_ARRAYS_SIZE;
+ params->in = (Bytef *)in;
+ params->out = (Bytef *)malloc(EVENT_ARRAYS_SIZE);
+ thrd_create(&g_arch_eva_thrds[i][j], (thrd_start_t)comp_deflate, params);
+ }
+ }
+
+ for (int i = 0; i < CORES; ++i) {
+ for (int j = 0; j < ARCH_EVENT_ARRAYS_COUNT; ++j) {
+ thrd_join(g_arch_eva_thrds[i][j], NULL);
+ struct DeflateParams *params = &g_arch_eva_deflate_params[i][j];
+ blob_sizes[i][j] = params->strm.total_out;
+ fwrite(params->out, sizeof(char), blob_sizes[i][j], eva_file);
+ comp_deflate_end(params);
+ free(params->out);
+ }
+ }
+
+ log_info("Pushing row to arch table in SQLite database");
+ sql_exec(
+ NULL, NULL,
+ "insert into arch ("
+#define INST(core, pref, index, label, mnemonic, symbol) \
+ #label "_" #pref "_" #core ", "
+#define INST_EVENT_ARRAY(core, index, iv) \
+ INST_SET(core, iv)
+#define ARCH_EVENT_ARRAY(core, index, ev) \
+ #ev EVENT_ARRAYS_SIZE_COL_MARKER #core ", "
+#define FOR_CORE(i) \
+ "wmb0_" #i ", " \
+ "wmb1_" #i ", " \
+ "wdea_" #i ", " \
+ INST_EVENT_ARRAYS(i) \
+ ARCH_EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_EVENT_ARRAY
+#undef INST_EVENT_ARRAY
+#undef INST
+ "step"
+ ") values ("
+#define INST(core, pref, index, label, mnemonic, symbol) \
+ "%ld, "
+#define INST_EVENT_ARRAY(core, index, iv) \
+ INST_SET(core, iv)
+#define ARCH_EVENT_ARRAY(core, index, ev) \
+ "%ld, "
+#define FOR_CORE(i) \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ INST_EVENT_ARRAYS(i) \
+ ARCH_EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_EVENT_ARRAY
+#undef INST_EVENT_ARRAY
+#undef INST
+ "%ld"
+ ");",
+#define INST(core, pref, index, label, mnemonic, symbol) \
+ g_cores[core].i##pref[index],
+#define INST_EVENT_ARRAY(core, index, iv) \
+ INST_SET(core, iv)
+#define ARCH_EVENT_ARRAY(core, index, ev) \
+ blob_sizes[core][index],
+#define FOR_CORE(i) \
+ g_cores[i].wmb0, \
+ g_cores[i].wmb1, \
+ g_cores[i].wdea, \
+ INST_EVENT_ARRAYS(i) \
+ ARCH_EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_EVENT_ARRAY
+#undef INST_EVENT_ARRAY
+#undef INST
+ g_steps
+ );
+
+ // Reset arch-specific data aggregation fields
+ for (int i = 0; i < CORES; ++i) {
+ struct Core *core = &g_cores[i];
+
+ memset(core->ipop, 0, sizeof(uint64_t) * INST_COUNT);
+ memset(core->iexe, 0, sizeof(uint64_t) * INST_COUNT);
+ memset(core->iwrt, 0, sizeof(uint64_t) * INST_COUNT);
+
+ core->wmb0 = 0;
+ core->wmb1 = 0;
+ core->wdea = 0;
+
+#define ARCH_EVENT_ARRAY(core, index, ev) \
+ memset(core->ev##a, 0, EVENT_ARRAYS_SIZE);
+ ARCH_EVENT_ARRAYS(core)
+#undef ARCH_EVENT_ARRAY
+ }
+}
diff --git a/arch/v1/plots.cpp b/arch/v1/plots.cpp
new file mode 100644
index 0000000..d9147cb
--- /dev/null
+++ b/arch/v1/plots.cpp
@@ -0,0 +1,59 @@
+std::array g_arch_traces = std::to_array<TraceNamed<ImS64>>({
+#define INST(core, pref, index, label, mnemonic, symbol) \
+ {#label "_" #pref "_" #core, #label},
+#define FOR_CORE(i) \
+ {"wmb0_" #i, "wmb0_" #i}, \
+ {"wmb1_" #i, "wmb1_" #i}, \
+ {"wdea_" #i, "wdea_" #i}, \
+ INST_SET(i, pop) \
+ INST_SET(i, exe) \
+ INST_SET(i, wrt)
+ FOR_CORES
+#undef FOR_CORE
+#undef INST
+});
+
+std::array g_arch_traces_heatmaps = std::to_array<TraceHeatmap<ImS64>>({
+#define FOR_CORE(i) \
+ {"wev_" #i, "wev_" #i}, \
+ {"xev_" #i, "xev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+});
+
+std::array g_arch_plots = std::to_array<PlotLines>({
+ {"wevs", "general", {
+#define FOR_CORE(i) "wmb0_" #i, "wmb1_" #i, "wdea_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+});
+
+std::array g_arch_plots_stacked = std::to_array<PlotStacked>({
+#define INST(core, pref, index, label, mnemonic, symbol) \
+ #label "_" #pref "_" #core,
+#define FOR_CORE(i) \
+ {"ipop%_" #i, "population", { INST_SET(i, pop) }},
+ FOR_CORES
+#undef FOR_CORE
+#define FOR_CORE(i) \
+ {"iexe%_" #i, "population", { INST_SET(i, exe) }},
+ FOR_CORES
+#undef FOR_CORE
+#define FOR_CORE(i) \
+ {"iwrt%_" #i, "population", { INST_SET(i, wrt) }},
+ FOR_CORES
+#undef FOR_CORE
+#undef INST
+});
+
+std::array g_arch_plots_heatmaps = std::to_array<PlotHeatmap>({
+#define FOR_CORE(i) \
+ {"wev_" #i, "heatmaps", "wev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+#define FOR_CORE(i) \
+ {"xev_" #i, "heatmaps", "xev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+});
diff --git a/arch/v1/vars.py b/arch/v1/vars.py
new file mode 100644
index 0000000..3eb0880
--- /dev/null
+++ b/arch/v1/vars.py
@@ -0,0 +1,120 @@
+# index
+# [section] instruction set
+# [section] core data fields
+# [section] other fields
+# [section] process fields
+
+# ------------------------------------------------------------------------------
+# [section] instruction set
+# ------------------------------------------------------------------------------
+inst_set = [
+ (["noop"], " "),
+ (["nop0"], "0"),
+ (["nop1"], "1"),
+ (["nop2"], "2"),
+ (["nop3"], "3"),
+ (["jmpb"], "("),
+ (["jmpf"], ")"),
+ (["adrb"], "["),
+ (["adrf"], "]"),
+ (["ifnz"], "?"),
+ (["allb"], "{"),
+ (["allf"], "}"),
+ (["bswp"], "%"),
+ (["bclr"], "|"),
+ (["splt"], "$"),
+ (["addn"], "+"),
+ (["subn"], "-"),
+ (["muln"], "*"),
+ (["divn"], "/"),
+ (["incn"], "^"),
+ (["decn"], "v"),
+ (["notn"], "!"),
+ (["shfl"], "<"),
+ (["shfr"], ">"),
+ (["zero"], "z"),
+ (["unit"], "u"),
+ (["pshn"], "#"),
+ (["popn"], "~"),
+ (["load"], "."),
+ (["wrte"], ":"),
+ (["dupl"], "="),
+ (["swap"], "x"),
+ (["keya"], "a"),
+ (["keyb"], "b"),
+ (["keyc"], "c"),
+ (["keyd"], "d"),
+ (["keye"], "e"),
+ (["keyf"], "f"),
+ (["keyg"], "g"),
+ (["keyh"], "h"),
+ (["keyi"], "i"),
+ (["keyj"], "j"),
+ (["keyk"], "k"),
+ (["keyl"], "l"),
+ (["keym"], "m"),
+ (["keyn"], "n"),
+ (["keyo"], "o"),
+ (["keyp"], "p"),
+ (["loka"], "A"),
+ (["lokb"], "B"),
+ (["lokc"], "C"),
+ (["lokd"], "D"),
+ (["loke"], "E"),
+ (["lokf"], "F"),
+ (["lokg"], "G"),
+ (["lokh"], "H"),
+ (["loki"], "I"),
+ (["lokj"], "J"),
+ (["lokk"], "K"),
+ (["lokl"], "L"),
+ (["lokm"], "M"),
+ (["lokn"], "N"),
+ (["loko"], "O"),
+ (["lokp"], "P"),
+]
+
+# ------------------------------------------------------------------------------
+# [section] core data fields
+# ------------------------------------------------------------------------------
+core_data_fields = [
+ ("uint64_t", f"ipop[{len(inst_set)}]"), # instruction population counter
+ ("uint64_t", f"iexe[{len(inst_set)}]"), # instruction execution counter
+ ("uint64_t", f"iwrt[{len(inst_set)}]"), # instruction write counter
+ ("uint64_t", "wmb0"), # writes within mb0 counter
+ ("uint64_t", "wmb1"), # writes within mb1 counter
+ ("uint64_t", "wdea"), # writes within dead code counter
+ ("uint64_t", f"weva[{2 ** globals()["args"].mvec_pow}]"), # write events array
+ ("uint64_t", f"xeva[{2 ** globals()["args"].mvec_pow}]"), # memory block swap events array
+]
+
+# ------------------------------------------------------------------------------
+# [section] other fields
+# ------------------------------------------------------------------------------
+core_fields = []
+muta_flip = False
+mvec_loop = False
+
+# ------------------------------------------------------------------------------
+# [section] process fields
+# ------------------------------------------------------------------------------
+proc_fields = [
+ ("uint64_t", "ip"),
+ ("uint64_t", "sp"),
+ ("uint64_t", "mb0a"),
+ ("uint64_t", "mb0s"),
+ ("uint64_t", "mb1a"),
+ ("uint64_t", "mb1s"),
+ ("uint64_t", "r0x"),
+ ("uint64_t", "r1x"),
+ ("uint64_t", "r2x"),
+ ("uint64_t", "r3x"),
+ ("uint64_t", "s0"),
+ ("uint64_t", "s1"),
+ ("uint64_t", "s2"),
+ ("uint64_t", "s3"),
+ ("uint64_t", "s4"),
+ ("uint64_t", "s5"),
+ ("uint64_t", "s6"),
+ ("uint64_t", "s7"),
+]
diff --git a/core/client.cpp b/core/client.cpp
new file mode 100644
index 0000000..771f626
--- /dev/null
+++ b/core/client.cpp
@@ -0,0 +1,1553 @@
+// index
+// [section] includes
+// [section] macros & enums
+// [section] string comparator declaration
+// [section] trace declarations
+// [section] plot declarations
+// [section] plots
+// [section] global variables
+// [section] string comparator definition
+// [section] Trace (base) definition
+// [section] TraceNamed definition
+// [section] TraceHeatmap definition
+// [section] Plot (base) definition
+// [section] PlotLines definition
+// [section] PlotStacked definition
+// [section] PlotHeatmap definition
+// [section] data functions
+// [section] gui functions
+// [section] main functions
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <arpa/inet.h>
+#include <GLFW/glfw3.h>
+#include <imgui_impl_glfw.h>
+#include <imgui_impl_opengl3.h>
+#include <implot.h>
+#include <implot_internal.h>
+#include <json-c/json.h>
+#include <signal.h>
+#include <threads.h>
+
+#include <algorithm>
+#include <array>
+#include <map>
+#include <vector>
+
+#include "common.c"
+#include "logger.c"
+
+// ----------------------------------------------------------------------------
+// [section] macros & enums
+// ----------------------------------------------------------------------------
+#define COLOR_BLACK ImVec4(0.f, 0.f, 0.f, 1.f)
+#define FONT_SIZE 12.f
+#define GLSL_VERSION "#version 130"
+#define PLOT_MIN_COLS 1
+#define PLOT_MAX_COLS 8
+#define PLOT_MIN_HEIGHT 100.f
+#define PLOT_MAX_HEIGHT 800.f
+#define PLOT_HEIGHT_INTERVAL 50.f
+#define PLOT_SCROLL_MARGIN 28.f
+#define HM_SCALE_POW_MIN -1.f
+#define HM_SCALE_POW_MAX 64.f
+#define HM_SCALE_POW_INTERVAL 1.f
+#define HM_COLORSCALE_WIDTH 80.f
+
+#define IMGUI_WINDOW_FLAGS ( \
+ ImGuiWindowFlags_NoBackground | \
+ ImGuiWindowFlags_NoDecoration | \
+ ImGuiWindowFlags_NoMove | \
+ ImGuiWindowFlags_NoSavedSettings \
+)
+
+#define DATA_FETCH_INTERVAL 10
+#define DATA_FETCH_INTERVAL_SUBDIV 1000
+
+#define DEFVAL_ENTRIES 0x800l
+#define DEFVAL_NTH 1l
+#define DEFVAL_X_AXIS 0
+#define DEFVAL_X_LOW 0l
+#define DEFVAL_X_HIGH INT64_MAX
+#define DEFVAL_HM_LEFT 0l
+
+enum Status {
+ STATUS_STOPPED,
+ STATUS_RUNNING,
+ STATUS_FETCHING,
+ STATUS_STOPPING,
+};
+
+// ----------------------------------------------------------------------------
+// [section] string comparator declaration
+// ----------------------------------------------------------------------------
+struct StringComparator {
+ bool operator()(const char *a, const char *b) const;
+};
+
+// ----------------------------------------------------------------------------
+// [section] trace declarations
+// ----------------------------------------------------------------------------
+template <class T>
+struct Trace : public std::vector<T> {
+ Trace();
+ virtual ~Trace();
+ virtual size_t start_offset() const;
+ virtual void trim();
+#if !defined(NDEBUG)
+ virtual void validate() const;
+#endif
+ void clear();
+ void push_back(T value);
+ T *start();
+ T &operator[](size_t n);
+ T operator[](size_t n) const;
+protected:
+ void trim_spec(int64_t multiplier = 1);
+};
+
+template <class T>
+struct TraceNamed : public Trace<T> {
+ TraceNamed(const char *name, const char *label);
+ const char *get_name() const;
+ const char *get_label() const;
+private:
+ const char *m_name;
+ const char *m_label;
+};
+
+template <class T>
+struct TraceHeatmap : public TraceNamed<T> {
+ TraceHeatmap(const char *name, const char *label);
+ size_t start_offset() const;
+ void trim();
+#if !defined(NDEBUG)
+ void validate() const;
+#endif
+};
+
+// ----------------------------------------------------------------------------
+// [section] plot declarations
+// ----------------------------------------------------------------------------
+typedef int (*AxisFormatter)(double value, char *buff, int size, void *data);
+
+struct Plot {
+ Plot(const char *name, const char *section);
+ virtual ~Plot();
+ virtual void handle_input(int key, int mods);
+ virtual void flag_for_reset();
+ const char *get_name() const;
+ const char *get_section() const;
+ void render(const ImVec2 &frame_size);
+ bool m_visible;
+private:
+ static int hex_formatter(double value, char *buff, int size, void *data);
+ virtual AxisFormatter x_formatter() const;
+ virtual AxisFormatter y_formatter() const;
+ virtual float right_margin() const;
+ virtual int flags() const;
+ virtual void render_internal(const ImVec2 &frame_size) = 0;
+ virtual void render_post(const ImVec2 &frame_size);
+ const char *m_name;
+ const char *m_section;
+};
+
+struct PlotLines : public Plot {
+ PlotLines(const char *name, const char *section, std::vector<const char *> trace_keys);
+private:
+ void render_internal(const ImVec2 &frame_size);
+ std::vector<const char *> m_trace_keys;
+};
+
+struct PlotStacked : public Plot {
+ PlotStacked(const char *name, const char *section, std::vector<const char *> trace_keys);
+ void flag_for_reset();
+private:
+ static int percent_formatter(double value, char *buff, int size, void *data);
+ AxisFormatter y_formatter() const;
+ void render_internal(const ImVec2 &frame_size);
+ std::vector<const char *> m_trace_keys;
+ std::vector<bool> m_trace_states;
+ Trace<ImS64> m_trace_totals;
+ std::vector<Trace<double>> m_trace_normals;
+ bool m_needs_reset;
+};
+
+struct PlotHeatmap : public Plot {
+ PlotHeatmap(const char *name, const char *section, const char *trace_key);
+ void handle_input(int key, int mods);
+ void flag_for_reset();
+private:
+ float right_margin() const;
+ int flags() const;
+ void render_internal(const ImVec2 &frame_size);
+ void render_end(const ImVec2 &frame_size);
+ void render_post(const ImVec2 &frame_size);
+ const char *m_trace_key;
+ float m_tex_scale_pow;
+ float m_tex_scale_high;
+ size_t m_tex_rendered_last;
+ size_t m_rows_rendered;
+ GLuint m_tex_id;
+ std::vector<uint8_t> m_tex_render;
+ bool m_needs_reset;
+};
+
+// ----------------------------------------------------------------------------
+// [section] plots
+// ----------------------------------------------------------------------------
+#include "plots.cpp"
+
+std::array g_core_traces = std::to_array<TraceNamed<ImS64>>({
+ {"rowid", "rowid"},
+ {"step", "step"},
+#define FOR_CORE(i) \
+ {"cycl_" #i, "cycl_" #i}, \
+ {"mall_" #i, "mall_" #i}, \
+ {"pnum_" #i, "pnum_" #i}, \
+ {"pfst_" #i, "pfst_" #i}, \
+ {"plst_" #i, "plst_" #i}, \
+ {"amb0_" #i, "amb0_" #i}, \
+ {"amb1_" #i, "amb1_" #i}, \
+ {"emb0_" #i, "emb0_" #i}, \
+ {"emb1_" #i, "emb1_" #i}, \
+ {"eliv_" #i, "eliv_" #i}, \
+ {"edea_" #i, "edea_" #i},
+ FOR_CORES
+#undef FOR_CORE
+});
+
+std::array g_core_traces_heatmaps = std::to_array<TraceHeatmap<ImS64>>({
+#define FOR_CORE(i) \
+ {"aev_" #i, "aev_" #i}, \
+ {"eev_" #i, "eev_" #i}, \
+ {"bev_" #i, "bev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+});
+
+std::array g_core_plots = std::to_array<PlotLines>({
+ {"cycl", "general", {
+#define FOR_CORE(i) "cycl_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"mall", "general", {
+#define FOR_CORE(i) "mall_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"pnum", "general", {
+#define FOR_CORE(i) "pnum_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"ppop", "general", {
+#define FOR_CORE(i) "pfst_" #i, "plst_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"ambs", "general", {
+#define FOR_CORE(i) "amb0_" #i, "amb1_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+ {"eevs", "general", {
+#define FOR_CORE(i) "emb0_" #i, "emb1_" #i, "eliv_" #i, "edea_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }},
+});
+
+std::array g_core_plots_heatmaps = std::to_array<PlotHeatmap>({
+#define FOR_CORE(i) \
+ {"aev_" #i, "heatmaps", "aev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+#define FOR_CORE(i) \
+ {"eev_" #i, "heatmaps", "eev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+#define FOR_CORE(i) \
+ {"bev_" #i, "heatmaps", "bev_" #i},
+ FOR_CORES
+#undef FOR_CORE
+});
+
+// ----------------------------------------------------------------------------
+// [section] global variables
+// ----------------------------------------------------------------------------
+GLFWwindow *g_window;
+ImGuiIO *g_imgui_io;
+ImGuiStyle *g_imgui_style;
+ImPlotStyle *g_implot_style;
+
+std::array g_x_axes = std::to_array<const char *>({
+ "rowid",
+ "step",
+#define FOR_CORE(i) "cycl_" #i,
+ FOR_CORES
+#undef FOR_CORE
+});
+
+int g_status;
+int g_x_axis = DEFVAL_X_AXIS;
+int64_t g_entries = DEFVAL_ENTRIES;
+int64_t g_nth = DEFVAL_NTH;
+int64_t g_x_low = DEFVAL_X_LOW;
+int64_t g_x_high = DEFVAL_X_HIGH;
+int64_t g_hm_left = DEFVAL_HM_LEFT;
+int64_t g_hm_pixel_count = DEFVAL_HM_PIXEL_COUNT;
+int64_t g_hm_pixel_pow; // calculate on init
+int64_t g_x_current = -1l;
+int64_t g_trace_len;
+int64_t g_trace_offset;
+
+thrd_t g_fetching_thread;
+mtx_t g_fetching_mutex;
+
+bool g_data_col_visible = true;
+bool g_plot_maximized;
+bool g_plot_scroll;
+float g_plot_scroll_current;
+float g_plot_scroll_to;
+float g_data_col_width;
+std::vector<Plot *> g_plot_cells;
+std::vector<float> g_plot_cells_top;
+std::vector<float> g_plot_cells_bottom;
+std::vector<bool> g_plots_covered;
+Plot *g_plot_selected;
+Plot *g_plot_hovered;
+int g_plot_stride;
+int g_downsampled_trace_len;
+int g_plot_cols = 2;
+size_t g_plot_col_selected;
+size_t g_plot_row_selected;
+float g_plot_height = 300.f;
+ImPlotColormap g_hm_colormap_id;
+
+std::map<const char *, TraceNamed<ImS64> *, StringComparator> g_trace_map;
+std::vector<TraceNamed<ImS64> *> g_traces;
+std::vector<Plot *> g_plots;
+Trace<double> g_x_axis_double;
+Trace<double> g_zero_trace;
+
+std::array g_hm_colormap = std::to_array<ImVec4>({
+ {0.000f, 0.000f, 0.016f, 1.f},
+ {0.106f, 0.047f, 0.255f, 1.f},
+ {0.290f, 0.047f, 0.420f, 1.f},
+ {0.471f, 0.110f, 0.427f, 1.f},
+ {0.647f, 0.173f, 0.376f, 1.f},
+ {0.812f, 0.267f, 0.275f, 1.f},
+ {0.929f, 0.412f, 0.145f, 1.f},
+ {0.984f, 0.608f, 0.024f, 1.f},
+ {0.969f, 0.820f, 0.239f, 1.f},
+ {0.988f, 1.000f, 0.643f, 1.f},
+});
+
+// ----------------------------------------------------------------------------
+// [section] string comparator definition
+// ----------------------------------------------------------------------------
+bool StringComparator::operator()(const char *a, const char *b) const {
+ return strcmp(a, b) < 0;
+}
+
+// ----------------------------------------------------------------------------
+// [section] Trace (base) definition
+// ----------------------------------------------------------------------------
+template <class T>
+Trace<T>::Trace() : std::vector<T>() {}
+
+template <class T>
+Trace<T>::~Trace() {}
+
+template <class T>
+size_t Trace<T>::start_offset() const {
+ return (size_t)g_trace_offset;
+}
+
+template <class T>
+void Trace<T>::trim() {
+ trim_spec();
+}
+
+#if !defined(NDEBUG)
+template <class T>
+void Trace<T>::validate() const {
+ assert(this->size() == g_traces[0]->size());
+}
+#endif
+
+template <class T>
+void Trace<T>::clear() {
+ std::vector<T>::clear();
+}
+
+template <class T>
+void Trace<T>::push_back(T value) {
+ std::vector<T>::push_back(value);
+}
+
+template <class T>
+T *Trace<T>::start() {
+ return this->size() ? &operator[](start_offset()) : nullptr;
+}
+
+template <class T>
+T &Trace<T>::operator[](size_t n) {
+#if !defined(NDEBUG)
+ return this->at(n);
+#else
+ return std::vector<T>::operator[](n);
+#endif
+}
+
+template <class T>
+T Trace<T>::operator[](size_t n) const {
+#if !defined(NDEBUG)
+ return this->at(n);
+#else
+ return std::vector<T>::operator[](n);
+#endif
+}
+
+template <class T>
+void Trace<T>::trim_spec(int64_t multiplier) {
+ assert((int64_t)this->size() >= g_entries * multiplier * 2);
+ this->erase(this->begin(), this->end() - (g_entries * multiplier));
+}
+
+// ----------------------------------------------------------------------------
+// [section] TraceNamed definition
+// ----------------------------------------------------------------------------
+template <class T>
+TraceNamed<T>::TraceNamed(const char *name, const char *label) : m_name(name), m_label(label) {}
+
+template <class T>
+const char *TraceNamed<T>::get_name() const {
+ return m_name;
+}
+
+template <class T>
+const char *TraceNamed<T>::get_label() const {
+ return m_label;
+}
+
+// ----------------------------------------------------------------------------
+// [section] TraceHeatmap definition
+// ----------------------------------------------------------------------------
+template <class T>
+TraceHeatmap<T>::TraceHeatmap(const char *name, const char *name_fmt) : TraceNamed<T>(name, name_fmt) {}
+
+template <class T>
+size_t TraceHeatmap<T>::start_offset() const {
+ return (size_t)(g_trace_offset * g_hm_pixel_count);
+}
+
+template <class T>
+void TraceHeatmap<T>::trim() {
+ Trace<T>::trim_spec(g_hm_pixel_count);
+}
+
+#if !defined(NDEBUG)
+template <class T>
+void TraceHeatmap<T>::validate() const {
+ assert(this->size() == g_traces[0]->size() * g_hm_pixel_count);
+}
+#endif
+
+// ----------------------------------------------------------------------------
+// [section] Plot (base) definition
+// ----------------------------------------------------------------------------
+Plot::Plot(const char *name, const char *section) : m_visible(true), m_name(name), m_section(section) {}
+
+Plot::~Plot() {}
+
+void Plot::handle_input(int key, int mods) {
+ (void)key;
+ (void)mods;
+}
+
+void Plot::flag_for_reset() {}
+
+const char *Plot::get_name() const {
+ return m_name;
+}
+
+const char *Plot::get_section() const {
+ return m_section;
+}
+
+void Plot::render(const ImVec2 &frame_size) {
+ if (ImPlot::BeginPlot(m_name, ImVec2(frame_size.x - right_margin(), frame_size.y), flags())) {
+ int axis_flags = ImPlotAxisFlags_Foreground | (g_status != STATUS_STOPPED ? ImPlotAxisFlags_AutoFit : 0);
+ ImPlot::SetupAxes(nullptr, nullptr, axis_flags, axis_flags);
+ ImPlot::SetupAxisFormat(ImAxis_X1, x_formatter());
+ ImPlot::SetupAxisFormat(ImAxis_Y1, y_formatter());
+ if (ImPlot::IsPlotHovered()) g_plot_hovered = this;
+ render_internal(frame_size);
+ ImPlot::EndPlot();
+ }
+
+ render_post(frame_size);
+}
+
+int Plot::hex_formatter(double value, char *buff, int size, void *data) {
+ (void)data;
+ snprintf(buff, size, "%s%#lx", value < 0. ? "-" : "", abs((int64_t)value));
+ return 0;
+}
+
+AxisFormatter Plot::x_formatter() const {
+ return Plot::hex_formatter;
+}
+
+AxisFormatter Plot::y_formatter() const {
+ return Plot::hex_formatter;
+}
+
+float Plot::right_margin() const {
+ return 0.f;
+}
+
+int Plot::flags() const {
+ return 0;
+}
+
+void Plot::render_post(const ImVec2 &frame_size) {
+ (void)frame_size;
+}
+
+// ----------------------------------------------------------------------------
+// [section] PlotLines definition
+// ----------------------------------------------------------------------------
+PlotLines::PlotLines(const char *name, const char *section, std::vector<const char *> trace_keys) : Plot(name, section), m_trace_keys(trace_keys) {}
+
+void PlotLines::render_internal(const ImVec2 &frame_size) {
+ (void)frame_size;
+
+ ImS64 *x = g_trace_map[g_x_axes[g_x_axis]]->start();
+ ImPlotSpec spec = ImPlotSpec(ImPlotProp_Stride, sizeof(ImS64) * g_plot_stride);
+
+ for (auto &trace : m_trace_keys) {
+ TraceNamed<ImS64> *trace_obj = g_trace_map[trace];
+ ImS64 *y = trace_obj->start();
+ ImPlot::PlotLine(trace_obj->get_label(), x, y, g_downsampled_trace_len, spec);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] PlotStacked definition
+// ----------------------------------------------------------------------------
+PlotStacked::PlotStacked(const char *name, const char *section, std::vector<const char *> trace_keys) : Plot(name, section), m_trace_keys(trace_keys), m_trace_states(trace_keys.size(), true), m_trace_totals(), m_trace_normals(trace_keys.size()), m_needs_reset(true) {}
+
+void PlotStacked::flag_for_reset() {
+ m_needs_reset = true;
+}
+
+int PlotStacked::percent_formatter(double value, char *buff, int size, void *data) {
+ (void)data;
+ snprintf(buff, size, "%3.0f%%", value * 100.);
+ return 0;
+}
+
+AxisFormatter PlotStacked::y_formatter() const {
+ return PlotStacked::percent_formatter;
+}
+
+void PlotStacked::render_internal(const ImVec2 &frame_size) {
+ (void)frame_size;
+
+ for (size_t i = 0; i < m_trace_keys.size(); i++) {
+ ImPlot::PlotDummy(g_trace_map[m_trace_keys[i]]->get_label());
+ }
+
+ for (size_t i = 0; i < m_trace_keys.size(); i++) {
+ bool trace_visible = GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show;
+
+ if (m_trace_states[i] != trace_visible) {
+ m_trace_states[i] = trace_visible;
+ m_needs_reset = true;
+ }
+ }
+
+ if (m_needs_reset) {
+ m_trace_totals.clear();
+ for (auto &trace_normal : m_trace_normals) trace_normal.clear();
+ m_needs_reset = false;
+ }
+
+ size_t trace_size = m_trace_totals.size();
+
+ for (size_t i = trace_size; i < g_traces[0]->size(); i++) {
+ m_trace_totals.push_back(0l);
+
+ for (size_t j = 0; j < m_trace_keys.size(); j++) {
+ if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) {
+ m_trace_totals.back() += g_trace_map[m_trace_keys[j]]->operator[](i);
+ }
+ }
+ }
+
+ for (size_t i = trace_size; i < g_traces[0]->size(); i++) {
+ if (m_trace_totals[i]) {
+ double normal = 0.;
+
+ for (size_t j = 0; j < m_trace_keys.size(); j++) {
+ if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) {
+ normal += (double)g_trace_map[m_trace_keys[j]]->operator[](i) / (double)m_trace_totals[i];
+ m_trace_normals[j].push_back(normal);
+ }
+ }
+ } else {
+ for (size_t j = 0; j < m_trace_keys.size(); j++) {
+ if (GImPlot->CurrentPlot->Items.GetLegendItem(j)->Show) {
+ m_trace_normals[j].push_back(0.);
+ }
+ }
+ }
+ }
+
+#if !defined(NDEBUG)
+ m_trace_totals.validate();
+
+ for (size_t i = 0; i < m_trace_keys.size(); i++) {
+ if (GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show) {
+ m_trace_normals[i].validate();
+ } else {
+ assert(m_trace_normals[i].empty());
+ }
+ }
+#endif
+
+ Trace<double> *prev_trace = &g_zero_trace;
+
+ for (size_t i = 0; i < m_trace_keys.size(); i++) {
+ assert(m_trace_states[i] == GImPlot->CurrentPlot->Items.GetLegendItem(i)->Show);
+
+ if (m_trace_states[i]) {
+ bool hovered = GImPlot->CurrentPlot->Items.GetLegendItem(i)->LegendHovered;
+ ImPlotSpec spec = ImPlotSpec(ImPlotProp_FillAlpha, hovered ? 1.f : 0.9f, ImPlotProp_Stride, sizeof(double) * g_plot_stride);
+ const char *trace_name = g_trace_map[m_trace_keys[i]]->get_label();
+ Trace<double> *current_trace = &m_trace_normals[i];
+ ImPlot::PlotShaded(trace_name, g_x_axis_double.start(), prev_trace->start(), current_trace->start(), g_downsampled_trace_len, spec);
+ ImPlot::PlotLine(trace_name, g_x_axis_double.start(), prev_trace->start(), g_downsampled_trace_len, spec);
+ if (hovered) ImPlot::PlotLine(trace_name, g_x_axis_double.start(), current_trace->start(), g_downsampled_trace_len, spec);
+ prev_trace = current_trace;
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] PlotHeatmap definition
+// ----------------------------------------------------------------------------
+PlotHeatmap::PlotHeatmap(const char *name, const char *section, const char *trace_key) : Plot(name, section), m_trace_key(trace_key), m_tex_scale_pow(-1.f), m_tex_scale_high(0.f), m_tex_rendered_last(0), m_rows_rendered(0), m_tex_id(0), m_tex_render(), m_needs_reset(true) {}
+
+void PlotHeatmap::handle_input(int key, int mods) {
+ switch (mods) {
+ case GLFW_MOD_CONTROL:
+ switch (key) {
+ case GLFW_KEY_K:
+ m_tex_scale_pow = std::clamp(m_tex_scale_pow - 1, HM_SCALE_POW_MIN, HM_SCALE_POW_MAX);
+ m_needs_reset = true;
+ break;
+ case GLFW_KEY_L:
+ m_tex_scale_pow = std::clamp(m_tex_scale_pow + 1, HM_SCALE_POW_MIN, HM_SCALE_POW_MAX);
+ m_needs_reset = true;
+ break;
+ }
+ }
+}
+
+void PlotHeatmap::flag_for_reset() {
+ m_needs_reset = true;
+}
+
+float PlotHeatmap::right_margin() const {
+ return HM_COLORSCALE_WIDTH;
+}
+
+int PlotHeatmap::flags() const {
+ return ImPlotFlags_NoLegend;
+}
+
+void PlotHeatmap::render_internal(const ImVec2 &frame_size) {
+ Trace<ImS64> *trace = g_trace_map[m_trace_key];
+
+ if (m_needs_reset) {
+ m_tex_scale_high = 0.f;
+ m_tex_rendered_last = trace->start_offset();
+ m_rows_rendered = 0;
+ glDeleteTextures(1, &m_tex_id);
+ m_tex_id = 0;
+ m_needs_reset = false;
+ }
+
+ if (m_tex_rendered_last == trace->size()) {
+ render_end(frame_size);
+ return;
+ }
+
+ assert(m_tex_render.empty());
+ assert((trace->size() - m_tex_rendered_last) % g_hm_pixel_count == 0);
+ size_t rows_to_append = (trace->size() - m_tex_rendered_last) / (size_t)g_hm_pixel_count;
+ size_t rows_to_chop_off = (m_rows_rendered + rows_to_append) > (size_t)g_trace_len ? (m_rows_rendered + rows_to_append) - (size_t)g_trace_len : 0;
+
+ if (rows_to_chop_off > m_rows_rendered) {
+ m_needs_reset = true;
+ render_internal(frame_size);
+ return;
+ }
+
+ if (m_tex_scale_pow == HM_SCALE_POW_MIN) {
+ ImS64 max = 0;
+
+ for (size_t i = trace->start_offset(); i < trace->size(); i++) {
+ max = std::max(max, trace->operator[](i));
+ }
+
+ m_tex_scale_high = (float)max;
+ } else {
+ m_tex_scale_high = pow(2.f, m_tex_scale_pow);
+ }
+
+ m_tex_render.reserve((trace->size() - m_tex_rendered_last) * 3);
+
+ if (m_tex_scale_high != 0.f) {
+ for (size_t i = m_tex_rendered_last; i < trace->size(); i++) {
+ ImS64 value = trace->operator[](i);
+ float normal = std::min((float)value / m_tex_scale_high, 1.f);
+ assert(normal >= 0.f && normal <= 1.f);
+ ImVec4 color = ImPlot::SampleColormap(normal, g_hm_colormap_id);
+ m_tex_render.push_back((uint8_t)(color.x * 255.f)); // red
+ m_tex_render.push_back((uint8_t)(color.y * 255.f)); // green
+ m_tex_render.push_back((uint8_t)(color.z * 255.f)); // blue
+ }
+ } else {
+ for (size_t i = m_tex_rendered_last; i < trace->size(); i++) {
+ m_tex_render.push_back(0);
+ m_tex_render.push_back(0);
+ m_tex_render.push_back(0);
+ }
+ }
+
+ GLuint new_tex_id;
+ glGenTextures(1, &new_tex_id);
+ glBindTexture(GL_TEXTURE_2D, new_tex_id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, g_hm_pixel_count, g_trace_len, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
+ assert(glGetError() == GL_NO_ERROR);
+ assert(new_tex_id);
+
+ if (m_tex_id) {
+ ((PFNGLCOPYIMAGESUBDATAPROC)glfwGetProcAddress("glCopyImageSubData"))(
+ m_tex_id, GL_TEXTURE_2D, 0, 0, rows_to_chop_off, 0,
+ new_tex_id, GL_TEXTURE_2D, 0, 0, 0, 0,
+ g_hm_pixel_count, m_rows_rendered - rows_to_chop_off, 1
+ );
+ assert(glGetError() == GL_NO_ERROR);
+ glDeleteTextures(1, &m_tex_id);
+ }
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, m_rows_rendered - rows_to_chop_off, g_hm_pixel_count, rows_to_append, GL_RGB, GL_UNSIGNED_BYTE, m_tex_render.data());
+ assert(glGetError() == GL_NO_ERROR);
+
+ render_end(frame_size);
+
+ m_tex_rendered_last = trace->size();
+ m_rows_rendered += rows_to_append - rows_to_chop_off;
+ assert(m_rows_rendered <= (size_t)g_trace_len);
+ m_tex_id = new_tex_id;
+ m_tex_render.clear();
+}
+
+void PlotHeatmap::render_end(const ImVec2 &frame_size) {
+ if (g_x_axis_double.empty()) return;
+
+ ImVec2 bmin(g_hm_left, (float)*g_x_axis_double.start());
+ ImVec2 bmax(g_hm_left + g_hm_pixel_count * pow(2.f, g_hm_pixel_pow), (float)g_x_axis_double.back());
+ ImVec2 uv0(0.f, 1.f);
+ ImVec2 uv1(1.f, 0.f);
+ double scale_max = m_tex_scale_pow == HM_SCALE_POW_MIN ? (double)m_tex_scale_high : pow(2., (double)m_tex_scale_pow);
+
+ ImPlot::PlotImage(get_name(), (ImTextureID)m_tex_id, bmin, bmax, uv0, uv1);
+ ImGui::SameLine();
+
+ ImPlot::PushColormap(g_hm_colormap_id);
+ ImPlot::ColormapScale("##hm-scale", 0., scale_max, ImVec2(HM_COLORSCALE_WIDTH, frame_size.y), "%.1e");
+ ImPlot::PopColormap();
+}
+
+void PlotHeatmap::render_post(const ImVec2 &frame_size) {
+ (void)frame_size;
+}
+
+// ----------------------------------------------------------------------------
+// [section] data functions
+// ----------------------------------------------------------------------------
+int64_t data_max_hm_pixel_pow(void) {
+ return (int64_t)floor(log2((double)(MVEC_SIZE - g_hm_left) / (double)g_hm_pixel_count));
+}
+
+void data_update_stride(void) {
+ if (g_plot_maximized) {
+ g_plot_stride = 1;
+ g_downsampled_trace_len = g_trace_len;
+ return;
+ }
+
+ int win_height = 0;
+ glfwGetWindowSize(g_window, nullptr, &win_height);
+
+ int max_stride = g_plot_cols * ((win_height / (int)g_plot_height) + 1);
+ int max_points = DEFVAL_ENTRIES / max_stride;
+ g_plot_stride = 1;
+
+ while (g_trace_len / g_plot_stride > max_points) {
+ g_plot_stride++;
+ }
+
+ g_downsampled_trace_len = g_trace_len / g_plot_stride;
+
+ log_info("Updated plot stride: %d", g_plot_stride);
+ log_info("Updated downsampled trace-len: %d", g_downsampled_trace_len);
+}
+
+void data_on_field_change(void) {
+ g_entries = std::clamp(g_entries, 1l, DEFVAL_ENTRIES);
+ g_nth = std::clamp(g_nth, DEFVAL_NTH, INT64_MAX);
+ g_x_low = std::clamp(g_x_low, DEFVAL_X_LOW, INT64_MAX);
+ g_x_high = std::clamp(g_x_high, g_x_low + 1l, DEFVAL_X_HIGH);
+#if !defined(MVEC_LOOP)
+ g_hm_left = std::clamp(g_hm_left, DEFVAL_HM_LEFT, (int64_t)MVEC_SIZE);
+#endif
+ g_hm_pixel_count = std::clamp(g_hm_pixel_count, 1l, DEFVAL_HM_PIXEL_COUNT);
+ g_hm_pixel_pow = std::clamp(g_hm_pixel_pow, 0l, data_max_hm_pixel_pow());
+ g_x_current = -1l;
+
+ g_trace_len = 0l;
+ g_trace_offset = 0;;
+ g_plot_stride = 0;
+ g_downsampled_trace_len = 0;
+
+ for (auto &trace : g_traces) trace->clear();
+ for (auto &plot: g_plots) plot->flag_for_reset();
+ g_x_axis_double.clear();
+ g_zero_trace.clear();
+}
+
+void data_reset_fields(void) {
+ g_entries = DEFVAL_ENTRIES;
+ g_nth = DEFVAL_NTH;
+ g_x_axis = DEFVAL_X_AXIS;
+ g_x_low = DEFVAL_X_LOW;
+ g_x_high = DEFVAL_X_HIGH;
+ g_hm_left = DEFVAL_HM_LEFT;
+ g_hm_pixel_count = DEFVAL_HM_PIXEL_COUNT;
+ g_hm_pixel_pow = data_max_hm_pixel_pow();
+
+ data_on_field_change();
+}
+
+void data_reset_plot_cells(void) {
+ std::fill(g_plot_cells.begin(), g_plot_cells.end(), nullptr);
+ std::fill(g_plot_cells_top.begin(), g_plot_cells_top.end(), 0.f);
+ std::fill(g_plot_cells_bottom.begin(), g_plot_cells_bottom.end(), 0.f);
+}
+
+void data_fetch(void) {
+ assert(g_status == STATUS_RUNNING);
+ g_status = STATUS_FETCHING;
+
+ json_object *request = json_object_new_object();
+ json_object_object_add(request, "request", json_object_new_string("data"));
+ json_object_object_add(request, "entries", json_object_new_int64(g_entries));
+ json_object_object_add(request, "nth", json_object_new_int64(g_nth));
+ json_object_object_add(request, "x-axis", json_object_new_string(g_x_axes[g_x_axis]));
+ json_object_object_add(request, "x-low", json_object_new_int64(g_x_low));
+ json_object_object_add(request, "x-high", json_object_new_int64(g_x_high));
+ json_object_object_add(request, "hm-left", json_object_new_int64(g_hm_left));
+ json_object_object_add(request, "hm-pixel-count", json_object_new_int64(g_hm_pixel_count));
+ json_object_object_add(request, "hm-pixel-pow", json_object_new_int64(g_hm_pixel_pow));
+ json_object_object_add(request, "x-current", json_object_new_int64(g_x_current));
+ const char *request_str = json_object_to_json_string(request);
+
+ log_info("Sending request to server: %s", request_str);
+ int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+ sockaddr_in socket_addr;
+ memset(&socket_addr, 0, sizeof(sockaddr_in));
+ socket_addr.sin_family = AF_INET;
+ socket_addr.sin_port = htons(PORT);
+ inet_pton(AF_INET, IP, &socket_addr.sin_addr);
+ if (connect(socket_fd, (sockaddr *)&socket_addr, sizeof(sockaddr_in))) assert(false);
+ json_object_to_fd(socket_fd, request, 0);
+ shutdown(socket_fd, SHUT_WR);
+
+ json_object *response = json_object_from_fd(socket_fd);
+
+ mtx_lock(&g_fetching_mutex);
+
+ json_object_object_foreach(response, key, value) {
+ size_t new_rows = json_object_array_length(value);
+
+ if (!strcmp(key, g_x_axes[g_x_axis])) {
+ log_info("Received %lu rows of data from server", new_rows);
+
+ for (size_t i = 0; i < new_rows; i++) {
+ ImS64 point = json_object_get_int64(json_object_array_get_idx(value, i));
+ g_x_axis_double.push_back((double)point);
+ g_zero_trace.push_back(0.);
+ }
+ }
+
+ if (g_trace_map.contains(key)) {
+ for (size_t i = 0; i < new_rows; i++) {
+ ImS64 point = json_object_get_int64(json_object_array_get_idx(value, i));
+ g_trace_map[key]->push_back(point);
+ }
+ }
+ }
+
+ g_x_current = g_trace_map[g_x_axes[g_x_axis]]->back();
+ json_object_put(request);
+ json_object_put(response);
+
+#if !defined(NDEBUG)
+ for (auto &trace : g_traces) trace->validate();
+ g_x_axis_double.validate();
+ g_zero_trace.validate();
+#endif
+
+ if ((int64_t)g_traces[0]->size() >= g_entries * 2) {
+ log_info("Trimming traces & flagging plots for reset");
+ for (auto &trace : g_traces) trace->trim();
+ for (auto &plot : g_plots) plot->flag_for_reset();
+ g_x_axis_double.trim();
+ g_zero_trace.trim();
+ }
+
+#if !defined(NDEBUG)
+ for (auto &trace : g_traces) trace->validate();
+ g_x_axis_double.validate();
+ g_zero_trace.validate();
+#endif
+
+ int64_t current_size = g_traces[0]->size();
+ g_trace_len = std::min(current_size, g_entries);
+ g_trace_offset = current_size > g_entries ? current_size - g_entries : 0l;
+ data_update_stride();
+
+ mtx_unlock(&g_fetching_mutex);
+ g_status = STATUS_RUNNING;
+}
+
+int data_fetching_thread(void *data) {
+ (void)data;
+
+ assert(!data);
+ assert(g_status == STATUS_RUNNING);
+
+ while (g_status == STATUS_RUNNING) {
+ data_fetch();
+
+ for (int i = 0; i < DATA_FETCH_INTERVAL_SUBDIV && g_status == STATUS_RUNNING; i++) {
+ usleep((DATA_FETCH_INTERVAL * 1000000) / DATA_FETCH_INTERVAL_SUBDIV);
+ }
+ }
+
+ assert(g_status == STATUS_STOPPING);
+ g_status = STATUS_STOPPED;
+ return 0;
+}
+
+void data_start_fetching(void) {
+ log_info("Starting data fetching thread");
+ g_status = STATUS_RUNNING;
+ thrd_create(&g_fetching_thread, (thrd_start_t)data_fetching_thread, nullptr);
+}
+
+void data_stop_fetching(void) {
+ assert(g_status == STATUS_RUNNING || g_status == STATUS_FETCHING);
+ log_info("Stopping data fetching thread");
+ g_status = STATUS_STOPPING;
+}
+
+// ----------------------------------------------------------------------------
+// [section] gui functions
+// ----------------------------------------------------------------------------
+void gui_render_data_input(const char *label, int64_t *target) {
+ assert(target);
+
+ if (ImGui::InputScalar(label, ImGuiDataType_U64, target, nullptr, nullptr, "%#lx")) {
+ data_on_field_change();
+ }
+}
+
+void gui_render_data_col(void) {
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ const ImVec2 win_pos = viewport->Pos;
+ const ImVec2 win_size = ImVec2(-1.f, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(win_pos);
+ ImGui::SetNextWindowSize(win_size);
+ ImGui::Begin("data-col", nullptr, IMGUI_WINDOW_FLAGS);
+ g_data_col_width = ImGui::GetWindowWidth();
+
+ ImGui::SeparatorText("SALIS data client");
+ ImGui::LabelText("name", NAME);
+ ImGui::LabelText("seed", "%#lx", SEED);
+ ImGui::LabelText("server", IP ":" PORT_STR);
+ ImGui::LabelText("arch", ARCH);
+ ImGui::LabelText("cores", "%d", CORES);
+ ImGui::LabelText("mvec-size", "%#lx", MVEC_SIZE);
+ #if defined(MVEC_LOOP)
+ ImGui::LabelText("mvec-loop", "true");
+ #else
+ ImGui::LabelText("mvec-loop", "false");
+ #endif
+ ImGui::LabelText("data-push", "%#lx", DATA_PUSH_INTERVAL);
+ ImGui::LabelText("fps", "%.1f", g_imgui_io->Framerate);
+
+ ImGui::SeparatorText("Data fields");
+
+ switch (g_status) {
+ case STATUS_STOPPED:
+ gui_render_data_input("entries", &g_entries);
+ gui_render_data_input("nth", &g_nth);
+
+ if (ImGui::BeginCombo("x-axis", g_x_axes[g_x_axis])) {
+ for (int i = 0; i < CORES + 2; i++) {
+ if (ImGui::Selectable(g_x_axes[i], g_x_axis == i)) {
+ data_reset_fields();
+ g_x_axis = i;
+ }
+ }
+
+ ImGui::EndCombo();
+ }
+
+ gui_render_data_input("x-low", &g_x_low);
+ gui_render_data_input("x-high", &g_x_high);
+ gui_render_data_input("hm-left", &g_hm_left);
+ gui_render_data_input("hm-pxl-count", &g_hm_pixel_count);
+ gui_render_data_input("hm-pxl-pow", &g_hm_pixel_pow);
+ break;
+ case STATUS_RUNNING:
+ case STATUS_FETCHING:
+ case STATUS_STOPPING:
+ ImGui::LabelText("entries", "%#lx", g_entries);
+ ImGui::LabelText("nth", "%#lx", g_nth);
+ ImGui::LabelText("x-axis", "%s", g_x_axes[g_x_axis]);
+ ImGui::LabelText("x-low", "%#lx", g_x_low);
+ ImGui::LabelText("x-high", "%#lx", g_x_high);
+ ImGui::LabelText("hm-left", "%#lx", g_hm_left);
+ ImGui::LabelText("hm-pxl-count", "%#lx", g_hm_pixel_count);
+ ImGui::LabelText("hm-pxl-pow", "%#lx", g_hm_pixel_pow);
+ }
+
+ switch (g_status) {
+ case STATUS_STOPPED:
+ if (ImGui::Button("Run", ImVec2(-1.f, 0.f))) data_start_fetching();
+ if (ImGui::Button("Reset", ImVec2(-1.f, 0.f))) data_reset_fields();
+ break;
+ case STATUS_RUNNING:
+ if (ImGui::Button("Stop", ImVec2(-1.f, 0.f))) data_stop_fetching();
+ ImGui::LabelText("##", "Running");
+ break;
+ case STATUS_FETCHING:
+ if (ImGui::Button("Stop", ImVec2(-1.f, 0.f))) data_stop_fetching();
+ ImGui::LabelText("##", "Fetching ...");
+ break;
+ case STATUS_STOPPING:
+ ImGui::BeginDisabled();
+ ImGui::Button("Stop", ImVec2(-1.f, 0.f));
+ ImGui::EndDisabled();
+ ImGui::LabelText("##", "Stopping");
+ break;
+ }
+
+ ImGui::SeparatorText("Layout");
+ ImGui::DragInt("cols", &g_plot_cols, 1, PLOT_MIN_COLS, PLOT_MAX_COLS);
+ ImGui::DragFloat("plot-height", &g_plot_height, PLOT_HEIGHT_INTERVAL, PLOT_MIN_HEIGHT, PLOT_MAX_HEIGHT, "%.0f");
+
+ ImGui::SeparatorText("Plots");
+ if (ImGui::Button("Show all", ImVec2(-1.f, 0.f))) for (auto &plot : g_plots) plot->m_visible = true;
+ if (ImGui::Button("Hide all", ImVec2(-1.f, 0.f))) for (auto &plot : g_plots) plot->m_visible = false;
+ ImGui::BeginTable("plot-visibility-table", 3);
+
+ for (auto &plot : g_plots) {
+ ImGui::TableNextColumn();
+ ImGui::Checkbox(plot->get_name(), &plot->m_visible);
+ }
+
+ ImGui::EndTable();
+ ImGui::End();
+}
+
+size_t gui_plot_cell_index(size_t row, size_t col) {
+ size_t index = row * PLOT_MAX_COLS + col;
+ assert(index < g_plot_cells.size());
+ assert(index < g_plot_cells_top.size());
+ assert(index < g_plot_cells_bottom.size());
+ return index;
+}
+
+size_t gui_plot_cell_row_up() {
+ for (size_t row = g_plot_row_selected - 1; row < g_plot_row_selected; row--) {
+ if (g_plot_cells[gui_plot_cell_index(row, g_plot_col_selected)]) {
+ return row;
+ }
+ }
+
+ return g_plot_row_selected;
+}
+
+size_t gui_plot_cell_row_down() {
+ for (size_t row = g_plot_row_selected + 1; row < g_plots.size(); row++) {
+ if (g_plot_cells[gui_plot_cell_index(row, g_plot_col_selected)]) {
+ return row;
+ }
+ }
+
+ return g_plot_row_selected;
+}
+
+void gui_render_plots(void) {
+ const char *section_current = g_plots[0]->get_section();
+ const char *section_next = nullptr;
+
+ g_plots_covered.clear();
+ g_plots_covered.resize(g_plots.size(), false);
+
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ const ImVec2 win_pos = g_data_col_visible ? ImVec2(g_data_col_width, viewport->Pos.y) : viewport->Pos;
+ const ImVec2 win_size = g_data_col_visible ? ImVec2(viewport->Size.x - g_data_col_width, -1.f) : ImVec2(viewport->Size.x, -1.f);
+
+ if (g_plot_scroll) {
+ ImGui::SetNextWindowScroll(ImVec2(-1.f, g_plot_scroll_to));
+ g_plot_scroll = false;
+ g_plot_scroll_to = 0.f;
+ }
+
+ ImGui::SetNextWindowPos(win_pos);
+ ImGui::SetNextWindowSize(win_size);
+ ImGui::Begin("plots", nullptr, IMGUI_WINDOW_FLAGS);
+ g_plot_scroll_current = ImGui::GetScrollY();
+ g_plot_hovered = nullptr;
+
+ if (!g_plot_selected->m_visible) {
+ g_plot_selected = g_plots[0];
+ g_plot_col_selected = 0;
+ g_plot_row_selected = 0;
+
+ for (auto &plot : g_plots) {
+ if (plot->m_visible) {
+ g_plot_selected = plot;
+ }
+ }
+ }
+
+ size_t row = 0;
+ size_t col = 0;
+
+ mtx_lock(&g_fetching_mutex);
+
+ while (section_current) {
+ ImGui::SeparatorText(section_current);
+ ImGui::BeginTable("plots-table", g_plot_cols);
+
+ for (size_t i = 0; i < g_plots.size(); i++) {
+ if (strcmp(g_plots[i]->get_section(), section_current)) {
+ section_next = (!section_next && !g_plots_covered[i]) ? g_plots[i]->get_section() : section_next;
+ continue;
+ }
+
+ if (g_plots[i]->m_visible) {
+ ImGui::TableNextColumn();
+ ImVec2 frame_size = ImVec2(ImGui::GetContentRegionAvail().x, g_plot_height);
+ g_plot_cells[gui_plot_cell_index(row, col)] = g_plots[i];
+ g_plot_cells_top[gui_plot_cell_index(row, col)] = ImGui::GetCursorPosY();
+
+ if (g_plots[i] == g_plot_selected) {
+ g_plot_col_selected = col;
+ g_plot_row_selected = row;
+ g_implot_style->Colors[ImPlotCol_FrameBg] = g_imgui_style->Colors[ImGuiCol_FrameBg];
+ }
+
+ g_plots[i]->render(frame_size);
+
+ if (g_plots[i] == g_plot_selected) {
+ g_implot_style->Colors[ImPlotCol_FrameBg] = COLOR_BLACK;
+ }
+
+ g_plot_cells_bottom[gui_plot_cell_index(row, col)] = ImGui::GetCursorPosY();
+ col = (col + 1) % g_plot_cols;
+ row += col ? 0 : 1;
+ }
+
+ g_plots_covered[i] = true;
+ }
+
+ section_current = section_next;
+ section_next = nullptr;
+ ImGui::EndTable();
+ row += col ? 1 : 0;
+ col = 0;
+ }
+
+ mtx_unlock(&g_fetching_mutex);
+ ImGui::End();
+}
+
+void gui_render_plot_maximized(void) {
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ ImGui::SetNextWindowPos(viewport->Pos);
+ ImGui::SetNextWindowSize(viewport->Size);
+ ImGui::Begin("plot-fullscreen", nullptr, IMGUI_WINDOW_FLAGS);
+
+ ImVec2 frame_size = {
+ viewport->Size.x - g_imgui_style->WindowPadding.x * 2,
+ viewport->Size.y - g_imgui_style->WindowPadding.y * 2,
+ };
+
+ mtx_lock(&g_fetching_mutex);
+ g_plot_selected->render(frame_size);
+ mtx_unlock(&g_fetching_mutex);
+ ImGui::End();
+}
+
+void gui_plot_queue_scroll_to_position(bool increased_plot_height) {
+ size_t row = 0;
+ float selected_plot_top = 0.f;
+
+ for (row = 0; row < g_plots.size(); row++) {
+ for (size_t col = 0; col < PLOT_MAX_COLS; col++) {
+ if (g_plot_selected == g_plot_cells[gui_plot_cell_index(row, col)]) {
+ selected_plot_top = g_plot_cells_top[gui_plot_cell_index(row, col)];
+ goto loop_exit;
+ }
+ }
+ }
+
+ loop_exit:
+ g_plot_scroll_to = selected_plot_top + (PLOT_HEIGHT_INTERVAL * (float)row * (increased_plot_height ? 1.f : -1.f)) - PLOT_SCROLL_MARGIN;
+ g_plot_scroll = true;
+}
+
+void gui_plot_queue_scroll_to_selected() {
+ const ImGuiViewport *viewport = ImGui::GetMainViewport();
+ float plot_top = g_plot_cells_top[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ float plot_bottom = g_plot_cells_bottom[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ float win_bottom = g_plot_scroll_current + viewport->Size.y;
+
+ if (plot_bottom > win_bottom) {
+ g_plot_scroll_to = g_plot_scroll_current + (plot_bottom - win_bottom);
+ g_plot_scroll = true;
+ }
+
+ if (plot_top < g_plot_scroll_current) {
+ g_plot_scroll_to = plot_top - PLOT_SCROLL_MARGIN;
+ g_plot_scroll = true;
+ }
+}
+
+void gui_render(void) {
+ if (g_plot_maximized) {
+ gui_render_plot_maximized();
+ return;
+ }
+
+ if (g_data_col_visible) gui_render_data_col();
+ gui_render_plots();
+}
+
+// ----------------------------------------------------------------------------
+// [section] main functions
+// ----------------------------------------------------------------------------
+void app_sig_handler(int signo) {
+ (void)signo;
+
+ log_warn("Signal received, will stop SALIS data client...");
+
+ if (g_status == STATUS_RUNNING || g_status == STATUS_FETCHING) {
+ data_stop_fetching();
+ }
+
+ glfwSetWindowShouldClose(g_window, GLFW_TRUE);
+}
+
+void app_error_callback(int error, const char* description) {
+ log_warn("GLFW error %d: %s", error, description);
+}
+
+void app_toggle_state() {
+ switch (g_status) {
+ case STATUS_STOPPED:
+ data_start_fetching();
+ break;
+ case STATUS_RUNNING:
+ case STATUS_FETCHING:
+ data_stop_fetching();
+ break;
+ }
+}
+
+void app_key_callback_plot_maximized(int key, int mods) {
+ switch (mods) {
+ case GLFW_MOD_CONTROL:
+ switch (key) {
+ case GLFW_KEY_C:
+ glfwSetWindowShouldClose(g_window, GLFW_TRUE);
+ break;
+ }
+
+ break;
+
+ case 0:
+ switch (key) {
+ case GLFW_KEY_F:
+ g_plot_maximized = false;
+ data_update_stride();
+ break;
+ case GLFW_KEY_SPACE:
+ app_toggle_state();
+ break;
+ }
+
+ break;
+ }
+
+ if (g_plot_selected->m_visible) {
+ g_plot_selected->handle_input(key, mods);
+ }
+}
+
+void app_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
+ (void)window;
+ (void)scancode;
+
+ if (ImGui::IsAnyItemActive()) {
+ return;
+ }
+
+ if (action != GLFW_PRESS && action != GLFW_REPEAT) {
+ return;
+ }
+
+ if (g_plot_maximized) {
+ app_key_callback_plot_maximized(key, mods);
+ return;
+ }
+
+ switch (mods) {
+ case GLFW_MOD_CONTROL:
+ switch (key) {
+ case GLFW_KEY_C:
+ glfwSetWindowShouldClose(g_window, GLFW_TRUE);
+ break;
+ case GLFW_KEY_N:
+ g_data_col_visible = !g_data_col_visible;
+ break;
+ case GLFW_KEY_LEFT:
+ g_plot_cols = std::max(g_plot_cols - 1, PLOT_MIN_COLS);
+ data_reset_plot_cells();
+ data_update_stride();
+ break;
+ case GLFW_KEY_RIGHT:
+ g_plot_cols = std::min(g_plot_cols + 1, PLOT_MAX_COLS);
+ data_reset_plot_cells();
+ data_update_stride();
+ break;
+ case GLFW_KEY_UP:
+ g_plot_height = std::min(g_plot_height + PLOT_HEIGHT_INTERVAL, PLOT_MAX_HEIGHT);
+ gui_plot_queue_scroll_to_position(true);
+ data_update_stride();
+ break;
+ case GLFW_KEY_DOWN:
+ g_plot_height = std::max(g_plot_height - PLOT_HEIGHT_INTERVAL, PLOT_MIN_HEIGHT);
+ gui_plot_queue_scroll_to_position(false);
+ data_update_stride();
+ break;
+ }
+
+ break;
+
+ case 0:
+ switch (key) {
+ case GLFW_KEY_LEFT:
+ g_plot_col_selected -= g_plot_col_selected ? 1 : 0;
+ g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ break;
+ case GLFW_KEY_RIGHT:
+ g_plot_col_selected += (g_plot_col_selected < PLOT_MAX_COLS - 1 && g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected + 1)]) ? 1 : 0;
+ g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ break;
+ case GLFW_KEY_UP:
+ g_plot_row_selected = gui_plot_cell_row_up();
+ g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ gui_plot_queue_scroll_to_selected();
+ break;
+ case GLFW_KEY_DOWN:
+ g_plot_row_selected = gui_plot_cell_row_down();
+ g_plot_selected = g_plot_cells[gui_plot_cell_index(g_plot_row_selected, g_plot_col_selected)];
+ gui_plot_queue_scroll_to_selected();
+ break;
+ case GLFW_KEY_F:
+ if (g_plot_selected->m_visible) {
+ g_plot_maximized = !g_plot_maximized;
+ data_update_stride();
+ }
+
+ break;
+ case GLFW_KEY_SPACE:
+ app_toggle_state();
+ break;
+ }
+
+ break;
+ }
+
+ if (g_plot_selected->m_visible) {
+ g_plot_selected->handle_input(key, mods);
+ }
+}
+
+void app_window_size_callback(GLFWwindow* window, int width, int height) {
+ (void)window;
+ (void)width;
+ (void)height;
+
+ data_update_stride();
+}
+
+void app_mouse_button_callback(GLFWwindow* window, int button, int action, int mods) {
+ (void)window;
+ (void)mods;
+
+ if (button != GLFW_MOUSE_BUTTON_LEFT) return;
+
+ switch (action) {
+ case GLFW_PRESS:
+ g_plot_selected = g_plot_hovered ? g_plot_hovered : g_plot_selected;
+ break;
+ }
+}
+
+void init() {
+ signal(SIGINT, app_sig_handler);
+ signal(SIGTERM, app_sig_handler);
+
+ log_info("Starting SALIS data client");
+
+ log_info("Initializing GLFW");
+ glfwSetErrorCallback(app_error_callback);
+ glfwInitHint(GLFW_WAYLAND_LIBDECOR, GLFW_WAYLAND_DISABLE_LIBDECOR);
+ if (!glfwInit()) assert(false);
+
+ float scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor());
+ g_window = glfwCreateWindow((int)(800 * scale), (int)(600 * scale), "SALIS data client", nullptr, nullptr);
+ assert(g_window);
+ glfwSetKeyCallback(g_window, app_key_callback);
+ glfwSetMouseButtonCallback(g_window, app_mouse_button_callback);
+ glfwSetWindowSizeCallback(g_window, app_window_size_callback);
+ glfwMakeContextCurrent(g_window);
+#if defined(VSYNC)
+ glfwSwapInterval(1);
+#else
+ glfwSwapInterval(0);
+#endif
+
+ log_info("Initializing ImGui");
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImPlot::CreateContext();
+
+ g_imgui_io = &ImGui::GetIO();
+ g_imgui_io->Fonts->AddFontFromFileTTF(FONT_SOURCE, FONT_SIZE);
+ g_imgui_io->IniFilename = nullptr;
+
+ g_imgui_style = &ImGui::GetStyle();
+ g_imgui_style->Colors[ImGuiCol_WindowBg] = COLOR_BLACK;
+ g_imgui_style->FontScaleDpi = scale;
+ g_imgui_style->FontSizeBase = FONT_SIZE;
+ g_imgui_style->ItemSpacing = ImVec2(g_imgui_style->ItemSpacing.x, 2.f);
+ g_imgui_style->ScaleAllSizes(scale);
+
+ g_implot_style = &ImPlot::GetStyle();
+ g_implot_style->Colors[ImPlotCol_FrameBg] = COLOR_BLACK;
+ g_hm_colormap_id = ImPlot::AddColormap("heatmap", g_hm_colormap.data(), g_hm_colormap.size(), false);
+
+ ImGui_ImplGlfw_InitForOpenGL(g_window, true);
+ ImGui_ImplOpenGL3_Init(GLSL_VERSION);
+
+ for (auto &trace : g_core_traces) g_traces.push_back(&trace);
+ for (auto &trace : g_arch_traces) g_traces.push_back(&trace);
+ for (auto &trace : g_core_traces_heatmaps) g_traces.push_back(&trace);
+ for (auto &trace : g_arch_traces_heatmaps) g_traces.push_back(&trace);
+ for (auto &plot : g_core_plots) g_plots.push_back(&plot);
+ for (auto &plot : g_arch_plots) g_plots.push_back(&plot);
+ for (auto &plot : g_arch_plots_stacked) g_plots.push_back(&plot);
+ for (auto &plot : g_core_plots_heatmaps) g_plots.push_back(&plot);
+ for (auto &plot : g_arch_plots_heatmaps) g_plots.push_back(&plot);
+ for (auto &i : g_traces) g_trace_map[i->get_name()] = i;
+
+ g_plot_cells = std::vector<Plot *>(g_plots.size() * PLOT_MAX_COLS);
+ g_plot_cells_top = std::vector<float>(g_plots.size() * PLOT_MAX_COLS);
+ g_plot_cells_bottom = std::vector<float>(g_plots.size() * PLOT_MAX_COLS);
+ g_plot_selected = g_plots[0];
+
+ g_hm_pixel_pow = data_max_hm_pixel_pow();
+ data_update_stride();
+
+ mtx_init(&g_fetching_mutex, mtx_plain);
+}
+
+void exec() {
+ while (!glfwWindowShouldClose(g_window)) {
+ glfwPollEvents();
+
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ gui_render();
+
+ ImGui::Render();
+ int display_w;
+ int display_h;
+ glfwGetFramebufferSize(g_window, &display_w, &display_h);
+ glViewport(0, 0, display_w, display_h);
+ glClearColor(0.f, 0.f, 0.f, 1.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ glfwSwapBuffers(g_window);
+ }
+}
+
+void quit() {
+ mtx_destroy(&g_fetching_mutex);
+
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplGlfw_Shutdown();
+ ImPlot::DestroyContext();
+ ImGui::DestroyContext();
+
+ log_info("Stopping SALIS data client");
+ glfwDestroyWindow(g_window);
+ glfwTerminate();
+}
+
+int main(int argc, char **argv) {
+ (void)argc;
+ (void)argv;
+
+ init();
+ exec();
+ quit();
+
+ return 0;
+}
diff --git a/core/common.c b/core/common.c
new file mode 100644
index 0000000..786ab38
--- /dev/null
+++ b/core/common.c
@@ -0,0 +1,3 @@
+#define DEFVAL_HM_PIXEL_COUNT 0x400l
+#define EVENT_ARRAYS_SIZE (sizeof(uint64_t) * MVEC_SIZE)
+#define EVENT_ARRAYS_SIZE_COL_MARKER "_evasize_"
diff --git a/core/compress.c b/core/compress.c
new file mode 100644
index 0000000..e457de5
--- /dev/null
+++ b/core/compress.c
@@ -0,0 +1,79 @@
+struct DeflateParams {
+ z_stream strm;
+ size_t size;
+ Bytef *in;
+ Bytef *out;
+#if defined(THREAD_GAP)
+ uint8_t tgap[THREAD_GAP];
+#endif
+};
+
+struct InflateParams {
+ z_stream strm;
+ size_t avail_in;
+ size_t size;
+ Bytef *in;
+ Bytef *out;
+#if defined(THREAD_GAP)
+ uint8_t tgap[THREAD_GAP];
+#endif
+};
+
+int comp_deflate(struct DeflateParams *params) {
+ assert(params);
+ assert(params->size);
+ assert(params->in);
+ assert(params->out);
+
+ params->strm.zalloc = NULL;
+ params->strm.zfree = NULL;
+ params->strm.opaque = NULL;
+
+ deflateInit(&params->strm, Z_DEFAULT_COMPRESSION);
+
+ params->strm.avail_in = params->size;
+ params->strm.avail_out = params->size;
+ params->strm.next_in = params->in;
+ params->strm.next_out = params->out;
+
+ deflate(&params->strm, Z_FINISH);
+
+ return 0;
+}
+
+void comp_deflate_end(struct DeflateParams *params) {
+ assert(params);
+ deflateEnd(&params->strm);
+}
+
+int comp_inflate(struct InflateParams *params) {
+ assert(params);
+ assert(params->avail_in);
+ assert(params->size);
+ assert(params->in);
+ assert(params->out);
+
+ params->strm.next_in = params->in;
+ params->strm.avail_in = params->avail_in;
+ params->strm.zalloc = NULL;
+ params->strm.zfree = NULL;
+ params->strm.opaque = NULL;
+
+ inflateInit(&params->strm);
+
+ params->strm.avail_out = params->size;
+ params->strm.next_out = params->out;
+
+#if defined(NDEBUG)
+ inflate(&params->strm, Z_FINISH);
+#else
+ assert(inflate(&params->strm, Z_FINISH));
+#endif
+
+ return 0;
+}
+
+void comp_inflate_end(struct InflateParams *params) {
+ assert(params);
+ inflateEnd(&params->strm);
+}
diff --git a/core/logger.c b/core/logger.c
new file mode 100644
index 0000000..af41dcc
--- /dev/null
+++ b/core/logger.c
@@ -0,0 +1,105 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+#define LOG_LINE_SIZE 1024
+
+enum LogLevel {
+ INFO,
+ WARN,
+};
+
+void log_msg_to_buff(char *out, int size, enum LogLevel level, bool colored, const char *format, va_list args) {
+ assert(out);
+ assert(size);
+ assert(level == INFO || level == WARN);
+ assert(format);
+
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ long msec = ts.tv_nsec / 1000000;
+ struct tm tm = *localtime(&ts.tv_sec);
+ pid_t pid = getpid();
+ const char *level_str = NULL;
+
+ switch (level) {
+ case INFO:
+ level_str = "INFO";
+ break;
+ case WARN:
+ level_str = "WARN";
+ break;
+ default:
+ assert(false);
+ }
+
+ const char *color_code = NULL;
+ if (colored) {
+ switch (level) {
+ case INFO:
+ color_code = "\033[1;32m";
+ break;
+ case WARN:
+ color_code = "\033[1;33m";
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ // Imitate formatting style configured in 'salis.py'
+ int col = snprintf(
+ out,
+ size,
+ "%s%d-%02d-%02d %02d:%02d:%02d,%03ld %07d [%s]%s ",
+ colored ? color_code : "",
+ tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ msec,
+ pid,
+ level_str,
+ colored ? "\033[0m" : ""
+ );
+
+ vsnprintf(out + col, size - col, format, args);
+}
+
+void log_msg(enum LogLevel level, bool colored, const char *format, va_list args) {
+ assert(level == INFO || level == WARN);
+ assert(format);
+
+ char buff[LOG_LINE_SIZE];
+ log_msg_to_buff(buff, LOG_LINE_SIZE, level, colored, format, args);
+ printf("\r%s\n", buff);
+ fflush(stdout);
+}
+
+void log_info_default(const char *format, ...) {
+ assert(format);
+
+ va_list args;
+ va_start(args, format);
+ log_msg(INFO, true, format, args);
+ va_end(args);
+}
+
+void log_warn_default(const char *format, ...) {
+ assert(format);
+
+ va_list args;
+ va_start(args, format);
+ log_msg(WARN, true, format, args);
+ va_end(args);
+}
+
+// Client may install their own loggers
+void (*log_info)(const char *fmt, ...) = log_info_default;
+void (*log_warn)(const char *fmt, ...) = log_warn_default;
diff --git a/core/salis.c b/core/salis.c
new file mode 100644
index 0000000..11c6efb
--- /dev/null
+++ b/core/salis.c
@@ -0,0 +1,1139 @@
+// index
+// [section] includes
+// [section] macros & enums
+// [section] structs
+// [section] globals
+// [section] architecture forward declarations
+// [section] memory vector functions
+// [section] mutator functions
+// [section] process functions
+// [section] core functions
+// [section] salis functions
+// [section] architecture & ui includes
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <assert.h>
+#include <sqlite3.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <threads.h>
+#include <zlib.h>
+
+#include "common.c"
+#include "compress.c"
+#include "logger.c"
+#include "sql.c"
+
+// ----------------------------------------------------------------------------
+// [section] macros & enums
+// ----------------------------------------------------------------------------
+#define INST_CAP 0x80
+#define INST_MASK 0x7f
+#define IPC_FLAG 0x80
+#define MALL_FLAG 0x80
+#define UINT64_HALF 0x8000000000000000ul
+
+#define EVENT_ARRAYS(core) \
+ EVENT_ARRAY(core, 0, aev) /* allocation events array */ \
+ EVENT_ARRAY(core, 1, eev) /* executions events array */ \
+ EVENT_ARRAY(core, 2, bev) /* birth events array */
+#define EVENT_ARRAYS_COUNT 3
+
+// ----------------------------------------------------------------------------
+// [section] structs
+// ----------------------------------------------------------------------------
+struct Proc {
+#define PROC_FIELD(type, name) type name;
+ PROC_FIELDS
+#undef PROC_FIELD
+};
+
+struct Core {
+ uint64_t cycl;
+ uint64_t mall;
+ uint64_t muta[4];
+
+ uint64_t pnum;
+ uint64_t pcap;
+ uint64_t pfst;
+ uint64_t plst;
+ uint64_t pcur;
+ uint64_t psli;
+
+ thrd_t thrd;
+ uint64_t thrd_steps;
+
+ uint64_t ivpt;
+ uint64_t *ivav;
+ uint8_t *iviv;
+
+ uint64_t emb0; // executions within mb0 counter
+ uint64_t emb1; // executions within mb1 counter
+ uint64_t eliv; // executions within not-owned live code counter (parasites)
+ uint64_t edea; // executions within dead code counter
+
+#define EVENT_ARRAY(core, index, ev) uint64_t ev##a[MVEC_SIZE];
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+
+#define CORE_DATA_FIELD(type, name) type name;
+ CORE_DATA_FIELDS
+#undef CORE_DATA_FIELD
+
+#define CORE_FIELD(type, name) type name;
+ CORE_FIELDS
+#undef CORE_FIELD
+
+ struct Proc *pvec;
+ uint8_t mvec[MVEC_SIZE];
+ uint8_t tgap[THREAD_GAP];
+};
+
+// ----------------------------------------------------------------------------
+// [section] globals
+// ----------------------------------------------------------------------------
+struct Core g_cores[CORES];
+uint64_t g_steps;
+uint64_t g_syncs;
+const struct Proc g_dead_proc;
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+char g_asav_pbuf[AUTOSAVE_NAME_LEN];
+char g_eva_pbuf[EVA_SAVE_NAME_LEN];
+#endif
+
+thrd_t g_eva_thrds[CORES][EVENT_ARRAYS_COUNT];
+struct DeflateParams g_eva_deflate_params[CORES][EVENT_ARRAYS_COUNT];
+
+// ----------------------------------------------------------------------------
+// [section] architecture forward declarations
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW)
+void arch_core_init(struct Core *core);
+#endif
+
+void arch_core_free(struct Core *core);
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+void arch_core_save(FILE *f, const struct Core *core);
+#endif
+
+#if defined(COMMAND_LOAD)
+void arch_core_load(FILE *f, struct Core *core);
+#endif
+
+uint64_t arch_proc_mb0_addr(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_mb0_size(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_mb1_addr(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_mb1_size(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_ip_addr(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_sp_addr(const struct Core *core, uint64_t pix);
+uint64_t arch_proc_slice(const struct Core *core, uint64_t pix);
+void arch_on_proc_kill(struct Core *core);
+void arch_proc_step(struct Core *core, uint64_t pix);
+
+#if !defined(NDEBUG)
+void arch_validate_proc(const struct Core *core, uint64_t pix);
+#endif
+
+wchar_t arch_symbol(uint8_t inst);
+const char *arch_mnemonic(uint8_t inst);
+
+#if defined(COMMAND_NEW)
+void arch_push_data_header(void);
+#endif
+void arch_push_data_line(FILE *eva_file);
+
+// ----------------------------------------------------------------------------
+// [section] memory vector functions
+// ----------------------------------------------------------------------------
+#if defined(MVEC_LOOP)
+uint64_t mvec_loop(uint64_t addr) {
+ return addr % MVEC_SIZE;
+}
+#endif
+
+bool mvec_is_alloc(const struct Core *core, uint64_t addr) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ return core->mvec[mvec_loop(addr)] & MALL_FLAG ? true : false;
+#else
+ if (addr < MVEC_SIZE) {
+ return core->mvec[addr] & MALL_FLAG ? true : false;
+ } else {
+ return true;
+ }
+#endif
+}
+
+void mvec_alloc(struct Core *core, uint64_t addr) {
+ assert(core);
+ assert(!mvec_is_alloc(core, addr));
+
+#if defined(MVEC_LOOP)
+ core->mvec[mvec_loop(addr)] |= MALL_FLAG;
+
+ // Record deallocation event
+ ++core->aeva[mvec_loop(addr)];
+#else
+ assert(addr < MVEC_SIZE);
+ core->mvec[addr] |= MALL_FLAG;
+
+ // Record deallocation event
+ ++core->aeva[addr];
+#endif
+ core->mall++;
+}
+
+void mvec_free(struct Core *core, uint64_t addr) {
+ assert(core);
+ assert(mvec_is_alloc(core, addr));
+
+#if defined(MVEC_LOOP)
+ core->mvec[mvec_loop(addr)] ^= MALL_FLAG;
+
+ // Record deallocation event
+ ++core->aeva[mvec_loop(addr)];
+#else
+ assert(addr < MVEC_SIZE);
+ core->mvec[addr] ^= MALL_FLAG;
+
+ // Record deallocation event
+ ++core->aeva[addr];
+#endif
+ core->mall--;
+}
+
+uint8_t mvec_get_byte(const struct Core *core, uint64_t addr) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ return core->mvec[mvec_loop(addr)];
+#else
+ if (addr < MVEC_SIZE) {
+ return core->mvec[addr];
+ } else {
+ return 0;
+ }
+#endif
+}
+
+uint8_t mvec_get_inst(const struct Core *core, uint64_t addr) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ return core->mvec[mvec_loop(addr)] & INST_MASK;
+#else
+ if (addr < MVEC_SIZE) {
+ return core->mvec[addr] & INST_MASK;
+ } else {
+ return 0;
+ }
+#endif
+}
+
+void mvec_set_inst(struct Core *core, uint64_t addr, uint8_t inst) {
+ assert(core);
+ assert(inst < INST_CAP);
+
+#if defined(MVEC_LOOP)
+ core->mvec[mvec_loop(addr)] &= MALL_FLAG;
+ core->mvec[mvec_loop(addr)] |= inst;
+#else
+ assert(addr < MVEC_SIZE);
+ core->mvec[addr] &= MALL_FLAG;
+ core->mvec[addr] |= inst;
+#endif
+}
+
+#if defined(MUTA_FLIP)
+void mvec_flip_bit(struct Core *core, uint64_t addr, int bit) {
+ assert(core);
+ assert(bit < 8);
+ core->mvec[addr] ^= (1 << bit) & INST_MASK;
+}
+#endif
+
+bool mvec_proc_is_live(const struct Core *core, uint64_t pix) {
+ assert(core);
+
+ return pix >= core->pfst && pix <= core->plst;
+}
+
+bool mvec_is_in_mb0_of_proc(const struct Core *core, uint64_t addr, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ uint64_t mb0a = arch_proc_mb0_addr(core, pix);
+ uint64_t mb0s = arch_proc_mb0_size(core, pix);
+
+ return ((addr - mb0a) % MVEC_SIZE) < mb0s;
+}
+
+bool mvec_is_in_mb1_of_proc(const struct Core *core, uint64_t addr, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ uint64_t mb1a = arch_proc_mb1_addr(core, pix);
+ uint64_t mb1s = arch_proc_mb1_size(core, pix);
+
+ return ((addr - mb1a) % MVEC_SIZE) < mb1s;
+}
+
+bool mvec_is_proc_owner(const struct Core *core, uint64_t addr, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return mvec_is_in_mb0_of_proc(core, addr, pix) || mvec_is_in_mb1_of_proc(core, addr, pix);
+}
+
+uint64_t mvec_get_owner(const struct Core *core, uint64_t addr) {
+ assert(core);
+#if !defined(MVEC_LOOP)
+ assert(addr < MVEC_SIZE);
+#endif
+ assert(mvec_is_alloc(core, addr));
+
+ for (uint64_t pix = core->pfst; pix <= core->plst; ++pix) {
+ if (mvec_is_proc_owner(core, addr, pix)) {
+ return pix;
+ }
+ }
+
+ assert(false);
+ return -1;
+}
+
+// ----------------------------------------------------------------------------
+// [section] mutator functions
+// ----------------------------------------------------------------------------
+#if SEED != 0
+#if defined(COMMAND_NEW)
+uint64_t muta_smix(uint64_t *seed) {
+ assert(seed);
+
+ uint64_t next = (*seed += 0x9e3779b97f4a7c15);
+ next = (next ^ (next >> 30)) * 0xbf58476d1ce4e5b9;
+ next = (next ^ (next >> 27)) * 0x94d049bb133111eb;
+
+ return next ^ (next >> 31);
+}
+#endif
+
+uint64_t muta_ro64(uint64_t x, int k) {
+ return (x << k) | (x >> (64 - k));
+}
+
+uint64_t muta_next(struct Core *core) {
+ assert(core);
+
+ uint64_t r = muta_ro64(core->muta[1] * 5, 7) * 9;
+ uint64_t 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(struct Core *core) {
+ assert(core);
+
+ uint64_t a = muta_next(core) % MUTA_RANGE;
+ uint64_t b = muta_next(core);
+
+ if (a < MVEC_SIZE) {
+#if defined(MUTA_FLIP)
+ mvec_flip_bit(core, a, (int)(b % 8));
+#else
+ mvec_set_inst(core, a, b & INST_MASK);
+#endif
+ }
+}
+#endif
+
+// ----------------------------------------------------------------------------
+// [section] process functions
+// ----------------------------------------------------------------------------
+void proc_new(struct Core *core, const struct Proc *proc) {
+ assert(core);
+ assert(proc);
+
+ if (core->pnum == core->pcap) {
+ // Reallocate dynamic array
+ uint64_t new_pcap = core->pcap * 2;
+ struct Proc *new_pvec = calloc(new_pcap, sizeof(struct Proc));
+
+ for (uint64_t pix = core->pfst; pix <= core->plst; ++pix) {
+ uint64_t iold = pix % core->pcap;
+ uint64_t inew = pix % new_pcap;
+ memcpy(&new_pvec[inew], &core->pvec[iold], sizeof(struct 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(struct Proc));
+
+ // Store birth event in database
+ uint64_t child_addr = arch_proc_mb0_addr(core, core->plst);
+ uint64_t child_size = arch_proc_mb0_size(core, core->plst);
+
+ for (uint64_t i = 0; i < child_size; i++) {
+ uint64_t addr = child_addr + i;
+#if defined(MVEC_LOOP)
+ ++core->beva[mvec_loop(addr)];
+#else
+ ++core->beva[addr];
+#endif
+ }
+}
+
+void proc_kill(struct Core *core) {
+ assert(core);
+ assert(core->pnum > 1);
+
+ arch_on_proc_kill(core);
+
+ core->pcur++;
+ core->pfst++;
+ core->pnum--;
+}
+
+const struct Proc *proc_get(const struct Core *core, uint64_t pix) {
+ assert(core);
+
+ if (mvec_proc_is_live(core, pix)) {
+ return &core->pvec[pix % core->pcap];
+ } else {
+ return &g_dead_proc;
+ }
+}
+
+struct Proc *proc_fetch(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+
+ return &core->pvec[pix % core->pcap];
+}
+
+// ----------------------------------------------------------------------------
+// [section] core functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+void core_save(FILE *f, const struct Core *core) {
+ assert(f);
+ assert(core);
+
+ fwrite(&core->cycl, sizeof(uint64_t), 1, f);
+ fwrite(&core->mall, sizeof(uint64_t), 1, f);
+ fwrite(core->muta, sizeof(uint64_t), 4, f);
+ fwrite(&core->pnum, sizeof(uint64_t), 1, f);
+ fwrite(&core->pcap, sizeof(uint64_t), 1, f);
+ fwrite(&core->pfst, sizeof(uint64_t), 1, f);
+ fwrite(&core->plst, sizeof(uint64_t), 1, f);
+ fwrite(&core->pcur, sizeof(uint64_t), 1, f);
+ fwrite(&core->psli, sizeof(uint64_t), 1, f);
+ fwrite(&core->ivpt, sizeof(uint64_t), 1, f);
+ fwrite(&core->emb0, sizeof(uint64_t), 1, f);
+ fwrite(&core->emb1, sizeof(uint64_t), 1, f);
+ fwrite(&core->eliv, sizeof(uint64_t), 1, f);
+ fwrite(&core->edea, sizeof(uint64_t), 1, f);
+
+ fwrite(core->iviv, sizeof(uint8_t), SYNC_INTERVAL, f);
+ fwrite(core->ivav, sizeof(uint64_t), SYNC_INTERVAL, f);
+ fwrite(core->pvec, sizeof(struct Proc), core->pcap, f);
+ fwrite(core->mvec, sizeof(uint8_t), MVEC_SIZE, f);
+#define EVENT_ARRAY(core, index, ev) \
+ fwrite(core->ev##a, sizeof(uint64_t), MVEC_SIZE, f);
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+
+ arch_core_save(f, core);
+}
+#endif
+
+#if defined(COMMAND_NEW)
+#if defined(ANC_BYTES)
+void core_assemble_ancestor(struct Core *core) {
+ assert(core);
+
+#if defined(MVEC_LOOP)
+ uint64_t addr = UINT64_HALF;
+#else
+ uint64_t addr = 0;
+#endif
+
+ uint8_t anc_bytes[] = ANC_BYTES;
+
+ for (uint64_t i = 0; i < sizeof(anc_bytes); ++i, ++addr) {
+ for (uint64_t j = 0; j < CLONES; ++j) {
+ uint64_t addr_clone = addr + (MVEC_SIZE / CLONES) * j;
+
+ mvec_alloc(core, addr_clone);
+ mvec_set_inst(core, addr_clone, anc_bytes[i]);
+ }
+ }
+}
+#endif
+
+void core_init(struct Core *core, uint64_t *seed) {
+ assert(core);
+ assert(seed);
+
+#if SEED != 0
+ assert(*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);
+#else
+ (void)seed;
+#endif
+
+ core->pnum = CLONES;
+ core->pcap = CLONES;
+ core->plst = CLONES - 1;
+ core->pcur = CLONES - 1;
+ core->iviv = calloc(SYNC_INTERVAL, sizeof(uint8_t));
+ core->ivav = calloc(SYNC_INTERVAL, sizeof(uint64_t));
+ core->pvec = calloc(core->pcap, sizeof(struct Proc));
+
+ assert(core->iviv);
+ assert(core->ivav);
+ assert(core->pvec);
+
+#if defined(ANC_BYTES)
+ core_assemble_ancestor(core);
+ arch_core_init(core);
+#endif
+}
+#endif
+
+#if defined(COMMAND_LOAD)
+void core_load(FILE *f, struct Core *core) {
+ assert(f);
+ assert(core);
+
+ fread(&core->cycl, sizeof(uint64_t), 1, f);
+ fread(&core->mall, sizeof(uint64_t), 1, f);
+ fread(core->muta, sizeof(uint64_t), 4, f);
+ fread(&core->pnum, sizeof(uint64_t), 1, f);
+ fread(&core->pcap, sizeof(uint64_t), 1, f);
+ fread(&core->pfst, sizeof(uint64_t), 1, f);
+ fread(&core->plst, sizeof(uint64_t), 1, f);
+ fread(&core->pcur, sizeof(uint64_t), 1, f);
+ fread(&core->psli, sizeof(uint64_t), 1, f);
+ fread(&core->ivpt, sizeof(uint64_t), 1, f);
+ fread(&core->emb0, sizeof(uint64_t), 1, f);
+ fread(&core->emb1, sizeof(uint64_t), 1, f);
+ fread(&core->eliv, sizeof(uint64_t), 1, f);
+ fread(&core->edea, sizeof(uint64_t), 1, f);
+
+ core->iviv = calloc(SYNC_INTERVAL, sizeof(uint8_t));
+ core->ivav = calloc(SYNC_INTERVAL, sizeof(uint64_t));
+ core->pvec = calloc(core->pcap, sizeof(struct Proc));
+
+ assert(core->iviv);
+ assert(core->ivav);
+ assert(core->pvec);
+
+ fread(core->iviv, sizeof(uint8_t), SYNC_INTERVAL, f);
+ fread(core->ivav, sizeof(uint64_t), SYNC_INTERVAL, f);
+ fread(core->pvec, sizeof(struct Proc), core->pcap, f);
+ fread(core->mvec, sizeof(uint8_t), MVEC_SIZE, f);
+#define EVENT_ARRAY(core, index, ev) \
+ fread(core->ev##a, sizeof(uint64_t), MVEC_SIZE, f);
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+
+ arch_core_load(f, core);
+}
+#endif
+
+void core_pull_ipcm(struct Core *core) {
+ assert(core);
+ assert(core->ivpt < SYNC_INTERVAL);
+
+ uint8_t *iinst = &core->iviv[core->ivpt];
+ uint64_t *iaddr = &core->ivav[core->ivpt];
+
+ if ((*iinst & IPC_FLAG) != 0) {
+ mvec_set_inst(core, *iaddr, *iinst & INST_MASK);
+
+ *iinst = 0;
+ *iaddr = 0;
+ }
+
+ assert(*iinst == 0);
+ assert(*iaddr == 0);
+}
+
+void core_push_ipcm(struct Core *core, uint8_t inst, uint64_t addr) {
+ assert(core);
+ assert(core->ivpt < SYNC_INTERVAL);
+ assert((inst & IPC_FLAG) == 0);
+
+ uint8_t *iinst = &core->iviv[core->ivpt];
+ uint64_t *iaddr = &core->ivav[core->ivpt];
+
+ assert(*iinst == 0);
+ assert(*iaddr == 0);
+
+ *iinst = inst | IPC_FLAG;
+ *iaddr = addr;
+}
+
+void core_step(struct Core *core) {
+ assert(core);
+
+ if (core->psli != 0) {
+ core_pull_ipcm(core);
+
+ // Save execution event locations in database
+ assert(mvec_proc_is_live(core, core->pcur));
+
+ uint64_t pcur_ip = arch_proc_ip_addr(core, core->pcur);
+
+ if (mvec_is_in_mb0_of_proc(core, pcur_ip, core->pcur)) {
+ ++core->emb0;
+ } else if (mvec_is_in_mb1_of_proc(core, pcur_ip, core->pcur)) {
+ ++core->emb1;
+ } else if (mvec_is_alloc(core, pcur_ip)) {
+ ++core->eliv;
+ } else {
+ ++core->edea;
+ }
+
+#if defined(MVEC_LOOP)
+ core->eeva[mvec_loop(pcur_ip)]++;
+#else
+ if (pcur_ip < MVEC_SIZE) {
+ core->eeva[pcur_ip]++;
+ }
+#endif
+
+ 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->cycl++;
+
+ // TODO: Implement a day-night cycle
+ while (core->mall > MVEC_SIZE / 2 && core->pnum > 1) {
+ proc_kill(core);
+ }
+
+#if SEED != 0
+ muta_cosmic_ray(core);
+#endif
+ core_step(core);
+}
+
+// ----------------------------------------------------------------------------
+// [section] salis functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+void salis_save(const char *path) {
+ size_t size = 0;
+ char *in = NULL;
+ FILE *f = open_memstream(&in, &size);
+
+ assert(f);
+
+ for (int i = 0; i < CORES; ++i) {
+ core_save(f, &g_cores[i]);
+ }
+
+ fwrite(&g_steps, sizeof(uint64_t), 1, f);
+ fwrite(&g_syncs, sizeof(uint64_t), 1, f);
+ fclose(f);
+
+ assert(size);
+ char *out = malloc(size);
+ assert(out);
+
+ struct DeflateParams params = {
+ .size = size,
+ .in = (Bytef *)in,
+ .out = (Bytef *)out,
+ };
+
+ comp_deflate(&params);
+
+ FILE *fx = fopen(path, "wb");
+ assert(fx);
+
+ fwrite(&size, sizeof(size_t), 1, fx);
+ fwrite(out, sizeof(char), params.strm.total_out, fx);
+ fclose(fx);
+
+ comp_deflate_end(&params);
+
+ free(in);
+ free(out);
+}
+
+void salis_auto_save(void) {
+#if defined(NDEBUG)
+ snprintf(
+#else
+ int rem = snprintf(
+#endif
+ g_asav_pbuf,
+ AUTOSAVE_NAME_LEN,
+ "%s-%#018lx",
+ SIM_PATH,
+ g_steps
+ );
+
+ assert(rem >= 0);
+ assert(rem < AUTOSAVE_NAME_LEN);
+
+ log_info("Saving simulation state on step %#lx", g_steps);
+ salis_save(g_asav_pbuf);
+}
+#endif
+
+#if defined(COMMAND_NEW)
+void salis_push_data_header(void) {
+ assert(g_sim_db);
+
+ log_info("Creating core table in SQLite database");
+ sql_exec(
+ NULL, NULL,
+ "create table core ("
+#define EVENT_ARRAY(core, index, ev) \
+ #ev EVENT_ARRAYS_SIZE_COL_MARKER #core " int not null, " \
+ #ev "_" #core " blob, "
+#define FOR_CORE(i) \
+ "cycl_" #i " int not null, " \
+ "mall_" #i " int not null, " \
+ "pnum_" #i " int not null, " \
+ "pfst_" #i " int not null, " \
+ "plst_" #i " int not null, " \
+ "amb0_" #i " real not null, " \
+ "amb1_" #i " real not null, " \
+ "emb0_" #i " int not null, " \
+ "emb1_" #i " int not null, " \
+ "eliv_" #i " int not null, " \
+ "edea_" #i " int not null, " \
+ EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef EVENT_ARRAY
+ "step int not null"
+ ");"
+ );
+
+ arch_push_data_header();
+}
+#endif
+
+void salis_push_data_line(void) {
+ assert(g_sim_db);
+
+ // Measure average membory block sizes
+ double amb0[CORES] = { 0 };
+ double amb1[CORES] = { 0 };
+
+ for (int i = 0; i < CORES; ++i) {
+ struct Core *core = &g_cores[i];
+
+ for (uint64_t j = core->pfst; j <= core->plst; ++j) {
+ amb0[i] += (double)arch_proc_mb0_size(core, j);
+ amb1[i] += (double)arch_proc_mb1_size(core, j);
+ }
+
+ amb0[i] /= core->pnum;
+ amb1[i] /= core->pnum;
+ }
+
+ // Compress event arrays
+ // Compression (deflation) is CPU intensive so it's done in parallel
+ memset(&g_eva_deflate_params, 0, sizeof(struct DeflateParams) * CORES * EVENT_ARRAYS_COUNT);
+ uint64_t blob_sizes[CORES][EVENT_ARRAYS_COUNT];
+
+ for (int i = 0; i < CORES; ++i) {
+ for (int j = 0; j < EVENT_ARRAYS_COUNT; ++j) {
+ uint64_t *in = NULL;
+
+ switch (j) {
+#define EVENT_ARRAY(core, index, ev) \
+ case index: in = g_cores[i].ev##a; break;
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+ default: assert(false);
+ }
+
+ // Compress event data
+ struct DeflateParams *params = &g_eva_deflate_params[i][j];
+ params->size = EVENT_ARRAYS_SIZE,
+ params->in = (Bytef *)in,
+ params->out = (Bytef *)malloc(EVENT_ARRAYS_SIZE),
+ thrd_create(&g_eva_thrds[i][j], (thrd_start_t)comp_deflate, params);
+ }
+ }
+
+ int rem = snprintf(
+ g_eva_pbuf,
+ EVA_SAVE_NAME_LEN,
+ "%s/evas-%#018lx",
+ SIM_EDIR,
+ g_steps
+ );
+
+ assert(rem >= 0);
+ assert(rem < EVA_SAVE_NAME_LEN);
+ (void)rem;
+
+ log_info("Saving event-array data to file: %s", g_eva_pbuf);
+ FILE *eva_file = fopen(g_eva_pbuf, "wb");
+
+ for (int i = 0; i < CORES; ++i) {
+ for (int j = 0; j < EVENT_ARRAYS_COUNT; ++j) {
+ thrd_join(g_eva_thrds[i][j], NULL);
+ struct DeflateParams *params = &g_eva_deflate_params[i][j];
+ blob_sizes[i][j] = params->strm.total_out;
+ fwrite(params->out, sizeof(char), blob_sizes[i][j], eva_file);
+ comp_deflate_end(params);
+ free(params->out);
+ }
+ }
+
+ log_info("Pushing row to core table in SQLite database");
+ sql_exec(
+ NULL, NULL,
+ "insert into core ("
+#define EVENT_ARRAY(core, index, ev) \
+ #ev EVENT_ARRAYS_SIZE_COL_MARKER #core ", "
+#define FOR_CORE(i) \
+ "cycl_" #i ", " \
+ "mall_" #i ", " \
+ "pnum_" #i ", " \
+ "pfst_" #i ", " \
+ "plst_" #i ", " \
+ "amb0_" #i ", " \
+ "amb1_" #i ", " \
+ "emb0_" #i ", " \
+ "emb1_" #i ", " \
+ "eliv_" #i ", " \
+ "edea_" #i ", " \
+ EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef EVENT_ARRAY
+ "step"
+ ") values ("
+#define EVENT_ARRAY(core, index, ev) \
+ "%ld, "
+#define FOR_CORE(i) \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%f, " \
+ "%f, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef EVENT_ARRAY
+ "%ld"
+ ");",
+#define EVENT_ARRAY(core, index, ev) \
+ blob_sizes[core][index],
+#define FOR_CORE(i) \
+ g_cores[i].cycl, \
+ g_cores[i].mall, \
+ g_cores[i].pnum, \
+ g_cores[i].pfst, \
+ g_cores[i].plst, \
+ amb0[i], \
+ amb1[i], \
+ g_cores[i].emb0, \
+ g_cores[i].emb1, \
+ g_cores[i].eliv, \
+ g_cores[i].edea, \
+ EVENT_ARRAYS(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef EVENT_ARRAY
+ g_steps
+ );
+
+ // Reset data aggregation fields
+ for (int i = 0; i < CORES; ++i) {
+ struct Core *core = &g_cores[i];
+
+ core->emb0 = 0;
+ core->emb1 = 0;
+ core->eliv = 0;
+ core->edea = 0;
+
+#define EVENT_ARRAY(core, index, ev) \
+ memset(core->ev##a, 0, EVENT_ARRAYS_SIZE);
+ EVENT_ARRAYS(core)
+#undef EVENT_ARRAY
+ }
+
+ // Push arch-specific data
+ arch_push_data_line(eva_file);
+ fclose(eva_file);
+}
+
+#if defined(COMMAND_NEW)
+void salis_init(void) {
+ uint64_t seed = SEED;
+
+ for (int i = 0; i < CORES; ++i) {
+ core_init(&g_cores[i], &seed);
+ }
+
+#if defined(COMMAND_NEW)
+ salis_auto_save();
+#endif
+
+ // Initialize database
+ sql_open();
+ salis_push_data_header();
+ salis_push_data_line();
+}
+#endif
+
+#if defined(COMMAND_LOAD)
+void salis_load(void) {
+ FILE *fx = fopen(SIM_PATH, "rb");
+ assert(fx);
+
+ fseek(fx, 0, SEEK_END);
+ size_t x_size = ftell(fx) - sizeof(size_t);
+ char *in = malloc(x_size);
+ rewind(fx);
+ assert(x_size);
+ assert(in);
+
+ size_t size = 0;
+ fread(&size, sizeof(size_t), 1, fx);
+ fread(in, 1, x_size, fx);
+ fclose(fx);
+ assert(size);
+
+ char *out = malloc(size);
+ assert(out);
+
+ struct InflateParams params = {
+ .avail_in = x_size,
+ .size = size,
+ .in = (Bytef *)in,
+ .out = (Bytef *)out,
+ };
+
+ comp_inflate(&params);
+ comp_inflate_end(&params);
+
+ FILE *f = fmemopen(out, size, "rb");
+
+ assert(f);
+
+ for (int i = 0; i < CORES; ++i) {
+ core_load(f, &g_cores[i]);
+ }
+
+ fread(&g_steps, sizeof(uint64_t), 1, f);
+ fread(&g_syncs, sizeof(uint64_t), 1, f);
+ fclose(f);
+ free(in);
+ free(out);
+
+ sql_open();
+}
+#endif
+
+int salis_thread(struct Core *core) {
+ assert(core);
+
+ for (uint64_t i = 0; i < core->thrd_steps; ++i) {
+ core_step(core);
+ }
+
+ return 0;
+}
+
+void salis_run_thread(uint64_t ns) {
+ for (int i = 0; i < CORES; ++i) {
+ g_cores[i].thrd_steps = ns;
+
+ thrd_create(
+ &g_cores[i].thrd,
+ (thrd_start_t)salis_thread,
+ &g_cores[i]
+ );
+ }
+
+ for (int i = 0; i < CORES; ++i) {
+ thrd_join(g_cores[i].thrd, NULL);
+ }
+
+ g_steps += ns;
+}
+
+void salis_sync(void) {
+#if !defined(NDEBUG)
+ for (int i = 0; i < CORES; ++i) {
+ assert(g_cores[i].ivpt == SYNC_INTERVAL);
+ }
+#endif
+
+ uint8_t *iviv0 = g_cores[0].iviv;
+ uint64_t *ivav0 = g_cores[0].ivav;
+
+ for (int i = 1; i < CORES; ++i) {
+ g_cores[i - 1].iviv = g_cores[i].iviv;
+ g_cores[i - 1].ivav = g_cores[i].ivav;
+ }
+
+ g_cores[CORES - 1].iviv = iviv0;
+ g_cores[CORES - 1].ivav = ivav0;
+
+ for (int i = 0; i < CORES; ++i) {
+ g_cores[i].ivpt = 0;
+ }
+
+ g_syncs++;
+}
+
+void salis_loop(uint64_t ns, uint64_t dt) {
+ assert(dt);
+
+ if (ns < dt) {
+ salis_run_thread(ns);
+ return;
+ }
+
+ salis_run_thread(dt);
+ salis_sync();
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+ if (g_steps % AUTOSAVE_INTERVAL == 0) {
+ salis_auto_save();
+ }
+#endif
+
+ if (g_steps % DATA_PUSH_INTERVAL == 0) {
+ salis_push_data_line();
+ }
+
+ salis_loop(ns - dt, SYNC_INTERVAL);
+}
+
+#if !defined(NDEBUG)
+void salis_validate_core(const struct Core *core) {
+ assert(core->cycl <= g_steps);
+ 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);
+
+ uint64_t mall = 0;
+
+ for (uint64_t i = 0; i < MVEC_SIZE; ++i) {
+ mall += mvec_is_alloc(core, i) ? 1 : 0;
+ }
+
+ assert(core->mall == mall);
+
+ for (uint64_t i = core->pfst; i <= core->plst; ++i) {
+ arch_validate_proc(core, i);
+ }
+
+ for (uint64_t i = 0; i < SYNC_INTERVAL; ++i) {
+ uint8_t iinst = core->iviv[i];
+
+ if ((iinst & IPC_FLAG) == 0) {
+ uint64_t iaddr = core->ivav[i];
+
+ assert(iinst == 0);
+ assert(iaddr == 0);
+ }
+ }
+
+ assert(core->ivpt == g_steps % SYNC_INTERVAL);
+}
+
+void salis_validate(void) {
+ assert(g_steps / SYNC_INTERVAL == g_syncs);
+
+ for (int i = 0; i < CORES; ++i) {
+ salis_validate_core(&g_cores[i]);
+ }
+}
+#endif
+
+void salis_step(uint64_t ns) {
+ assert(ns);
+ salis_loop(ns, SYNC_INTERVAL - (g_steps % SYNC_INTERVAL));
+
+#if !defined(NDEBUG)
+ salis_validate();
+#endif
+}
+
+void salis_free(void) {
+ sql_close();
+
+ for (int i = 0; i < CORES; ++i) {
+ arch_core_free(&g_cores[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;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] architecture & ui includes
+// ----------------------------------------------------------------------------
+#include "arch.c"
+
+#if defined(COMMAND_NEW) || defined(COMMAND_LOAD)
+#include "ui.c"
+#endif
diff --git a/core/server.c b/core/server.c
new file mode 100644
index 0000000..6848cf6
--- /dev/null
+++ b/core/server.c
@@ -0,0 +1,418 @@
+// index
+// [section] includes
+// [section] macros
+// [section] structs
+// [section] globals
+// [section] event array render function
+// [section] sql callbacks
+// [section] main functions
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <arpa/inet.h>
+#include <assert.h>
+#include <json-c/json.h>
+#include <signal.h>
+#include <sqlite3.h>
+#include <string.h>
+#include <threads.h>
+#include <zlib.h>
+
+#include "common.c"
+#include "compress.c"
+#include "logger.c"
+#include "sql.c"
+
+// ----------------------------------------------------------------------------
+// [section] macros
+// ----------------------------------------------------------------------------
+#define BACKLOG 10
+
+// ----------------------------------------------------------------------------
+// [section] structs
+// ----------------------------------------------------------------------------
+struct Socket {
+ int fd;
+ struct sockaddr_in addr;
+};
+
+struct CallbackContext {
+ struct json_object *response;
+ int64_t response_rows;
+ int64_t hm_left;
+ int64_t hm_pixel_count;
+ int64_t hm_pixel_pow;
+};
+
+struct RenderContext {
+ const struct CallbackContext *callback_context;
+ void *blob;
+ size_t blob_size;
+ uint64_t eva[EVENT_ARRAYS_SIZE];
+ int64_t out[DEFVAL_HM_PIXEL_COUNT];
+};
+
+// ----------------------------------------------------------------------------
+// [section] globals
+// ----------------------------------------------------------------------------
+struct json_object *g_response_header;
+size_t g_eva_count;
+char g_eva_pbuf[EVA_SAVE_NAME_LEN];
+
+// ----------------------------------------------------------------------------
+// [section] event array render function
+// ----------------------------------------------------------------------------
+int eva_render(void *data) {
+ assert(data);
+
+ struct RenderContext *render_context = (struct RenderContext *)data;
+ int64_t hm_left = render_context->callback_context->hm_left;
+ int64_t hm_pixel_count = render_context->callback_context->hm_pixel_count;
+ int64_t hm_pixel_pow = render_context->callback_context->hm_pixel_pow;
+ int64_t hm_pixel_res = 1 << hm_pixel_pow;
+ const void *blob = render_context->blob;
+ size_t blob_size = render_context->blob_size;
+
+ assert(blob);
+
+#if defined(MVEC_LOOP)
+ hm_left %= MVEC_SIZE;
+#endif
+
+#if !defined(MVEC_LOOP)
+#if !defined(NDEBUG)
+ int64_t hm_right = hm_left + hm_pixel_res * hm_pixel_count;
+#endif
+ assert(hm_left < (int64_t)MVEC_SIZE);
+ assert(hm_right <= (int64_t)MVEC_SIZE);
+#endif
+
+ // Inflate blob
+ struct InflateParams params = {
+ .avail_in = blob_size,
+ .size = EVENT_ARRAYS_SIZE,
+ .in = (Bytef *)blob,
+ .out = (Bytef *)render_context->eva,
+ };
+
+ comp_inflate(&params);
+ comp_inflate_end(&params);
+
+ for (int64_t i = 0; i < hm_pixel_count; i++) {
+ render_context->out[i] = 0l;
+
+ for (int64_t j = 0; j < hm_pixel_res; j++) {
+ int64_t coord = hm_left + (i * hm_pixel_res) + j;
+#if defined(MVEC_LOOP)
+ coord %= MVEC_SIZE;
+#endif
+ render_context->out[i] += render_context->eva[coord];
+ }
+ }
+
+ return 0;
+}
+
+// ----------------------------------------------------------------------------
+// [section] sql callbacks
+// ----------------------------------------------------------------------------
+void sql_callback_add_column_name(sqlite3_stmt *sql_stmt, void *data) {
+ assert(sql_stmt);
+ assert(data);
+ assert(sqlite3_column_type(sql_stmt, 0) == SQLITE_TEXT);
+ assert(sqlite3_column_type(sql_stmt, 1) == SQLITE_TEXT);
+ assert(!strcmp(sqlite3_column_name(sql_stmt, 0), "name"));
+ assert(!strcmp(sqlite3_column_name(sql_stmt, 1), "type"));
+
+ const char *col_name = (const char *)sqlite3_column_text(sql_stmt, 0);
+ struct json_object *response_header = (struct json_object *)data;
+
+ if (!json_object_object_get_ex(response_header, col_name, NULL)) {
+ json_object_object_add(response_header, col_name, json_object_new_array());
+ }
+
+ if (strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER)) {
+ g_eva_count++;
+ }
+}
+
+void sql_callback_add_data(sqlite3_stmt *sql_stmt, void *data) {
+ assert(sql_stmt);
+ assert(data);
+
+ FILE *eva_file = NULL;
+ struct CallbackContext *callback_context = (struct CallbackContext *)data;
+ struct RenderContext *render_contexts = calloc(g_eva_count, sizeof(struct RenderContext));
+ thrd_t *threads = calloc(g_eva_count, sizeof(thrd_t));
+ size_t tid = 0;
+
+ for (int i = 0; i < sqlite3_column_count(sql_stmt); i++) {
+ assert(i < sqlite3_column_count(sql_stmt));
+ assert(sqlite3_column_type(sql_stmt, i) == SQLITE_INTEGER || sqlite3_column_type(sql_stmt, i) == SQLITE_FLOAT);
+
+ const char *col_name = sqlite3_column_name(sql_stmt, i);
+ struct json_object *col_data = json_object_object_get(callback_context->response, col_name);
+ assert(col_name);
+
+ if (!col_data) continue;
+
+ int64_t col_value = sqlite3_column_int64(sql_stmt, i);
+ json_object_array_add(col_data, json_object_new_int64(col_value));
+
+ if (i == 1) {
+ assert(!strcmp(col_name, "step"));
+
+ int rem = snprintf(
+ g_eva_pbuf,
+ EVA_SAVE_NAME_LEN,
+ "%s/evas-%#018lx",
+ SIM_EDIR,
+ col_value
+ );
+
+ assert(rem >= 0);
+ assert(rem < EVA_SAVE_NAME_LEN);
+ (void)rem;
+
+ eva_file = fopen(g_eva_pbuf, "rb");
+ assert(eva_file);
+ }
+
+ if (sqlite3_column_type(sql_stmt, i + 1) == SQLITE_NULL) {
+ assert(eva_file);
+ assert(strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER));
+
+ render_contexts[tid].callback_context = callback_context;
+ render_contexts[tid].blob = malloc(col_value);
+ render_contexts[tid].blob_size = col_value;
+ assert(render_contexts[tid].blob);
+
+ size_t red = fread(render_contexts[tid].blob, 1, col_value, eva_file);
+ assert(red == (size_t)col_value);
+ (void)red;
+
+ thrd_create(&threads[tid], (thrd_start_t)eva_render, &render_contexts[tid]);
+
+ tid++;
+ i++;
+ }
+ }
+
+ assert(tid == g_eva_count);
+ tid = 0;
+
+ for (int i = 0; i < sqlite3_column_count(sql_stmt); i++) {
+ const char *col_name = sqlite3_column_name(sql_stmt, i);
+ struct json_object *col_data = json_object_object_get(callback_context->response, col_name);
+ assert(col_name);
+
+ if (!col_data) continue;
+
+ if (sqlite3_column_type(sql_stmt, i + 1) == SQLITE_NULL) {
+ assert(strstr(col_name, EVENT_ARRAYS_SIZE_COL_MARKER));
+ assert(render_contexts[tid].blob);
+
+ const char *eva_col_name = sqlite3_column_name(sql_stmt, i + 1);
+ struct json_object *eva_col_data = json_object_object_get(callback_context->response, eva_col_name);
+ assert(eva_col_name);
+ assert(eva_col_data);
+
+ thrd_join(threads[tid], NULL);
+
+ for (int64_t j = 0; j < callback_context->hm_pixel_count; j++) {
+ json_object_array_add(eva_col_data, json_object_new_int64(render_contexts[tid].out[j]));
+ }
+
+ free(render_contexts[tid].blob);
+
+ tid++;
+ i++;
+ }
+ }
+
+ assert(tid == g_eva_count);
+
+ callback_context->response_rows++;
+ log_info("Processed row #%ld", callback_context->response_rows);
+
+ assert(eva_file);
+ fclose(eva_file);
+ free(render_contexts);
+ free(threads);
+}
+
+// ----------------------------------------------------------------------------
+// [section] main functions
+// ----------------------------------------------------------------------------
+void sig_handler(int signo) {
+ (void)signo;
+
+ log_warn("Signal received, will stop SALIS data server");
+ json_object_put(g_response_header);
+ sql_close();
+ exit(0);
+}
+
+void respond_name(int socket_fd) {
+ log_info("Client requested simulation name");
+
+ struct json_object *sim_name = json_object_new_object();
+ json_object_object_add(sim_name, "name", json_object_new_string(NAME));
+ json_object_to_fd(socket_fd, sim_name, 0);
+ json_object_put(sim_name);
+}
+
+void respond_opts(int socket_fd) {
+ log_info("Client requested simulation options");
+
+ struct json_object *sim_opts = json_object_from_file(SIM_OPTS);
+ json_object_to_fd(socket_fd, sim_opts, 0);
+ json_object_put(sim_opts);
+}
+
+void respond_hash(int socket_fd) {
+ log_info("Client requested git hash");
+
+ char buff[41] = { 0 };
+ FILE *pipe = popen("git rev-parse HEAD", "r");
+ fread(buff, sizeof(char), 40, pipe);
+ pclose(pipe);
+
+ struct json_object *git_hash = json_object_new_object();
+ json_object_object_add(git_hash, "hash", json_object_new_string(buff));
+ json_object_to_fd(socket_fd, git_hash, 0);
+ json_object_put(git_hash);
+}
+
+void respond_data(int socket_fd, struct json_object *request) {
+ assert(request);
+
+ const char *request_str = json_object_to_json_string(request);
+ log_info("Client requested simulation data with the following parameters: %s", request_str);
+ const char *x_axis = json_object_get_string(json_object_object_get(request, "x-axis"));
+ int64_t x_current = json_object_get_int64(json_object_object_get(request, "x-current"));
+ int64_t x_high = json_object_get_int64(json_object_object_get(request, "x-high"));
+ int64_t nth = json_object_get_int64(json_object_object_get(request, "nth"));
+ int64_t entries = json_object_get_int64(json_object_object_get(request, "entries"));
+
+ struct CallbackContext callback_context = {
+ .response = NULL,
+ .response_rows = 0l,
+ .hm_left = json_object_get_int64(json_object_object_get(request, "hm-left")),
+ .hm_pixel_count = json_object_get_int64(json_object_object_get(request, "hm-pixel-count")),
+ .hm_pixel_pow = json_object_get_int64(json_object_object_get(request, "hm-pixel-pow")),
+ };
+
+ json_object_deep_copy(g_response_header, &callback_context.response, NULL);
+
+ const char *x_axis_pref = (!strcmp(x_axis, "rowid") || !strcmp(x_axis, "step")) ? "core." : "";
+
+ sql_exec(
+ sql_callback_add_data,
+ &callback_context,
+ "select * from ("
+ "select core.rowid, core.step, * from core inner join arch "
+ "where core.rowid = arch.rowid and %s%s > %ld and %s%s <= %ld and core.rowid %% %ld == 0 "
+ "order by %s%s desc limit %ld"
+ ") order by %s asc;",
+ x_axis_pref,
+ x_axis,
+ x_current,
+ x_axis_pref,
+ x_axis,
+ x_high,
+ nth,
+ x_axis_pref,
+ x_axis,
+ entries,
+ x_axis
+ );
+
+ log_info("Sending client %ld rows of data", callback_context.response_rows);
+ json_object_to_fd(socket_fd, callback_context.response, 0);
+ json_object_put(callback_context.response);
+
+ shutdown(socket_fd, SHUT_WR);
+}
+
+int handle_client(struct Socket *socket) {
+ assert(socket);
+
+ char socket_ip[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, &socket->addr.sin_addr, socket_ip, INET_ADDRSTRLEN);
+ log_info("Client connected: %s:%d", socket_ip, ntohs(socket->addr.sin_port));
+
+ struct json_object *request_json = json_object_from_fd(socket->fd);
+ struct json_object *request_str = NULL;
+
+ if (!json_object_object_get_ex(request_json, "request", &request_str)) assert(false);
+
+ const char *request = json_object_get_string(request_str);
+ assert(request);
+
+ if (!strcmp(request, "name")) {
+ respond_name(socket->fd);
+ } else if (!strcmp(request, "opts")) {
+ respond_opts(socket->fd);
+ } else if (!strcmp(request, "hash")) {
+ respond_hash(socket->fd);
+ } else if (!strcmp(request, "data")) {
+ respond_data(socket->fd, request_json);
+ } else {
+ assert(false);
+ }
+
+ json_object_put(request_json);
+
+ log_info("Client disconnected: %s:%d", socket_ip, ntohs(socket->addr.sin_port));
+ close(socket->fd);
+
+ free(socket);
+ return 0;
+}
+
+int main(void) {
+ log_info("Initializing salis data server");
+ log_info("Connecting to database in: %s", DATA_PUSH_PATH);
+ sql_open();
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+ signal(SIGPIPE, SIG_IGN); // ignore broken pipes
+
+ log_info("Creating response header");
+ g_response_header = json_object_new_object();
+ json_object_object_add(g_response_header, "rowid", json_object_new_array());
+ sql_exec(
+ sql_callback_add_column_name,
+ g_response_header,
+ "select name, type from pragma_table_info('core') union select name, type from pragma_table_info('arch');"
+ );
+ log_info("Found %lu eva-size columns in database", g_eva_count);
+
+ log_info("Binding to port: %d", PORT);
+ int opt = 1;
+ int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+ setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ struct sockaddr_in socket_addr = { 0 };
+ socket_addr.sin_family = AF_INET;
+ socket_addr.sin_addr.s_addr = INADDR_ANY;
+ socket_addr.sin_port = htons(PORT);
+ bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(struct sockaddr_in));
+
+ listen(socket_fd, BACKLOG);
+ log_info("Listening...");
+
+ while (true) {
+ struct Socket *socket = calloc(1, sizeof(struct Socket));
+ socklen_t socket_len = sizeof(struct sockaddr_in);
+ socket->fd = accept(socket_fd, (struct sockaddr *)&socket->addr, &socket_len);
+
+ thrd_t thread;
+ thrd_create(&thread, (thrd_start_t)handle_client, socket);
+ thrd_detach(thread);
+ }
+
+ return 0;
+}
diff --git a/core/sql.c b/core/sql.c
new file mode 100644
index 0000000..760adc4
--- /dev/null
+++ b/core/sql.c
@@ -0,0 +1,73 @@
+#define DATA_PUSH_BUSY_TIMEOUT 600000
+
+sqlite3 *g_sim_db;
+
+void sql_exec(void (*callback)(sqlite3_stmt *sql_stmt, void *data), void *data, const char *sql_format, ...) {
+ assert(sql_format);
+
+ va_list args;
+ va_start(args, sql_format);
+ int sql_len = vsnprintf(NULL, 0, sql_format, args) + 1;
+ char *sql_str = malloc(sql_len);
+ assert(sql_str);
+ va_end(args);
+
+ va_start(args, sql_format);
+ vsprintf(sql_str, sql_format, args);
+ va_end(args);
+
+ int sql_res;
+ sqlite3_stmt *sql_stmt;
+
+ sql_res = sqlite3_prepare_v2(g_sim_db, sql_str, -1, &sql_stmt, NULL);
+ assert(sql_res == SQLITE_OK);
+ free(sql_str);
+
+ while (true) {
+ sql_res = sqlite3_step(sql_stmt);
+
+ if (sql_res == SQLITE_ROW) {
+ if (callback) {
+ callback(sql_stmt, data);
+ }
+
+ continue;
+ }
+
+ if (sql_res == SQLITE_DONE) {
+ break;
+ }
+
+ log_warn("SQLite database returned error %d with message:", sql_res);
+ log_warn(sqlite3_errmsg(g_sim_db));
+
+ // Only handle SQLITE_BUSY error, in which case we retry the query.
+ // Setting 'journal_mode=wal;' should help prevent busy database errors.
+ if (sql_res == SQLITE_BUSY) {
+ log_info("Will retry query...");
+ continue;
+ }
+
+ assert(false);
+ }
+
+ sqlite3_finalize(sql_stmt);
+}
+
+void sql_open(void) {
+ sqlite3_open(DATA_PUSH_PATH, &g_sim_db);
+ assert(g_sim_db);
+
+ // Install busy handler to retry transactions if DB is locked
+ sqlite3_busy_timeout(g_sim_db, DATA_PUSH_BUSY_TIMEOUT);
+
+ // Enable Write-Ahead Logging (WAL)
+ // This seems to help prevent DB locks when displaying live data.
+ // See: https://sqlite.org/wal.html
+ sql_exec(NULL, NULL, "pragma journal_mode=wal;");
+}
+
+void sql_close(void) {
+ assert(g_sim_db);
+ sqlite3_close(g_sim_db);
+}
diff --git a/data.png b/data.png
new file mode 100644
index 0000000..17d30f4
--- /dev/null
+++ b/data.png
Binary files differ
diff --git a/salis b/salis
deleted file mode 100755
index a493861..0000000
--- a/salis
+++ /dev/null
@@ -1,393 +0,0 @@
-#!/bin/bash
-
-# Project: Salis
-# Author: Paul Oliver
-# Email: contact@pauloliver.dev
-
-# Salis simulator launcher script. Builds salis binary (according to passed
-# arguments) and launches it. Binary is purged on exit. This JIT compilation
-# scheme allows to quickly switch between available architectures and UIs.
-
-set -euo pipefail
-
-headline="Salis: Simple A-Life Simulator."
-help_msg="show help and exit"
-
-usage() {
-cat << EOF
-${headline}
-Usage: ${0} [-h|--help] COMMAND [args...]
-
-Options:
- -h, --help ${help_msg}
-
-Commands:
- bench run benchmark test
- load load saved simulation
- new create a new simulation
-
-Use '-h' to list arguments for each command.
-Example: ${0} bench -h
-EOF
-}
-
-case ${1:-} in
-bench|load|new)
- ;;
--h|--help)
- usage | less -CQS~
- exit 0
- ;;
-"")
- echo "${0}: please specify command -- 'bench|load|new'"
- exit 1
- ;;
-*)
- echo "${0}: invalid command -- '${1}'"
- exit 1
- ;;
-esac
-
-cmd=${1}
-
-shift
-
-falter() {
- find src/${1}/ -type f | sed 's|.*/||; s|\.c||' | paste -sd: -
-}
-
-arches=`falter arch`
-uis=`falter ui`
-
-anc_def_desc() {
- echo "default ancestor file name without extension, "
- echo "to be compiled on all cores "
- echo "('ANC' points to file 'ancs/<ARCH>/<ANC>.asm')"
-}
-
-anc_spec_def() {
- echo "core specific ancestor file names separated by commas, "
- echo "using same convention as with default ancestor (<ANC0>,<ANC1>,...), "
- echo "when provided will override default ancestor on specified cores"
-}
-
-options=(
- "A|anc-def|ANC|`anc_def_desc`|||bench:new"
- "a|arch|ARCH|VM architecture|${arches}|dummy|bench:new"
- "b|steps|N|number of steps to run in benchmark||0x1000000|bench"
- "C|clones|N|number of ancestor clones on each core||1|bench:new"
- "c|cores|N|number of simulator cores||2|bench:new"
- "F|muta-flip||cosmic rays flip bits instead of randomizing whole bytes||false|bench:new"
- "f|force||overwrite existing simulation of given name||false|new"
- "H|half||compile ancestor at the middle of the memory buffer||false|bench:new"
- "h|help||${help_msg}|||bench:load:new"
- "M|muta-pow|POW|mutator range exponent (range == 2^POW)||32|bench:new"
- "m|mvec-pow|POW|memory vector size exponent (size == 2^POW)||20|bench:new"
- "n|name|NAME|name of new or loaded simulation||def.sim|load:new"
- "o|optimized||build Salis binary with optimizations||false|bench:load:new"
- "p|pre-cmd|CMD|shell command to wrap executable (e.g. gdb)|||bench:load:new"
- "S|anc-spec|ANC0,ANC1,...|`anc_spec_def`|||bench:new"
- "s|seed|SEED|seed value for new simulation||0|bench:new"
- "t|thread-gap|N|memory gap between cores (in bytes)||0x100|bench:load:new"
- "u|ui|UI|user interface|${uis}|curses|load:new"
- "y|sync-pow|POW|core sync interval exponent (interval == 2^POW)||20|bench:new"
- "z|auto-save-pow|POW|auto save interval exponent (interval == 2^POW)||36|new"
-)
-
-field() {
- echo ${1} | cut -d'|' -f${2}
-}
-
-flist() {
- sopt=`field "${1}" 1`
- lopt=`field "${1}" 2`
- meta=`field "${1}" 3`
-
- echo -n "[-${sopt}|--${lopt}`[[ -n ${meta} ]] && echo " ${meta}"`] "
-}
-
-fhelp() {
- sopt=`field "${1}" 1`
- lopt=`field "${1}" 2`
- meta=`field "${1}" 3`
-
- printf %-32s " -${sopt}, --${lopt}`[[ -n ${meta} ]] && echo " ${meta}"`"
-
- help=`field "${1}" 4`
- choi=`field "${1}" 5`
- defv=`field "${1}" 6`
- copt=`[[ -n ${choi} ]] && echo " (choices: ${choi/:/, })"`
- dopt=`[[ -n ${defv} ]] && echo " (default: ${defv})"`
-
- echo ${help}${copt}${dopt}
-}
-
-fshort() {
- sopt=`field "${1}" 1`
- meta=`field "${1}" 3`
-
- echo -n "${sopt}`[[ -n ${meta} ]] && echo :`"
-}
-
-flong() {
- lopt=`field "${1}" 2`
- meta=`field "${1}" 3`
-
- echo -n "${lopt}`[[ -n ${meta} ]] && echo :`,"
-}
-
-fdefaults() {
- lopt=`field "${1}" 2`
-
- [[ ${lopt} == help ]] && return
-
- defv=`field "${1}" 6`
- nopt=opt_${lopt//-/_}
-
- eval ${nopt}=${defv}
-}
-
-fshow() {
- lopt=`field "${1}" 2`
-
- [[ ${lopt} == help ]] && return
-
- nopt=opt_${lopt//-/_}
-
- echo "${nopt}=${!nopt}"
-}
-
-fiter() {
- for ((i = 0; i < ${#options[@]}; i++)) ; do
- if [[ `field "${options[${i}]}" 7` =~ ${cmd} ]] ; then
- ${1} "${options[${i}]}" || true
- fi
- done
-}
-
-usage() {
-cat << EOF
-${headline}
-Usage: ${0} ${cmd} `fiter flist`
-
-Options:
-`fiter fhelp`
-EOF
-}
-
-fiter fdefaults
-
-sopts=`fiter fshort`
-lopts=`fiter flong`
-popts=`getopt -n "${0} ${cmd}" -o ${sopts} -l ${lopts::-1} -- "${@}"`
-
-eval set -- ${popts}
-
-parse_next() {
- for ((i = 0; i < ${#options[@]}; i++)) ; do
- vopt="${options[${i}]}"
-
- sopt=`field "${vopt}" 1`
- lopt=`field "${vopt}" 2`
-
- [[ ${1} != -${sopt} ]] && [[ ${1} != --${lopt} ]] && continue
-
- meta=`field "${vopt}" 3`
- nopt=opt_${lopt//-/_}
-
- if [[ -z ${meta} ]] ; then
- eval ${nopt}=true
- shift_next=1
- else
- eval ${nopt}=${2}
- shift_next=2
- fi
- done
-}
-
-while true ; do
- case ${1} in
- -h|--help)
- usage | less -CQS~
- exit 0
- ;;
- --)
- shift
- break
- ;;
- *)
- parse_next ${@}
- shift ${shift_next}
- ;;
- esac
-done
-
-if [[ -n ${1:-} ]] ; then
- while [[ -n ${1:-} ]] ; do
- echo "${0} ${cmd}: unrecognized option -- '${1}'"
- shift
- done
-
- exit 1
-fi
-
-blue() {
- echo -e "\033[1;34m${1}\033[0m"
-}
-
-red() {
- echo -e "\033[1;31m${1}\033[0m"
-}
-
-blue "${headline}"
-blue "Called '${cmd}' command with the following options:"
-fiter fshow
-
-case ${cmd} in
-load|new)
- sim_dir=${HOME}/.salis/${opt_name}
- sim_path=${sim_dir}/${opt_name}
- sim_opts=${sim_dir}/opts
- ;;
-esac
-
-case ${cmd} in
-load)
- if [[ ! -d ${sim_dir} ]] ; then
- red "Error: no saved simulation was found named '${opt_name}'."
- exit 1
- fi
-
- blue "Sourcing configurations from '${sim_opts}':"
- cat ${sim_opts}
- source ${sim_opts}
- ;;
-esac
-
-blue "Generating a temporary Salis directory:"
-salis_tmp=/tmp/salis-tmp
-salis_exe=${salis_tmp}/salis-bin
-mkdir -pv ${salis_tmp}
-
-act_bench=1
-act_load=2
-act_new=3
-
-act_var="act_${cmd}"
-
-gcc_flags="-Wall -Wextra -Werror -std=c11 -pedantic"
-
-fquote() {
- echo "\\\"${1}\\\""
-}
-
-fpow() {
- printf '%#xul' $((1 << ${1}))
-}
-
-bcmd="gcc src/salis.c -o ${salis_exe} ${gcc_flags} -Isrc -lncursesw -pthread"
-bcmd="${bcmd} `[[ ${opt_optimized} == true ]] && echo "-O3 -DNDEBUG" || echo "-ggdb"`"
-bcmd="${bcmd} -DACTION=${!act_var}"
-bcmd="${bcmd} -DARCHITECTURE=`fquote ${opt_arch}`"
-bcmd="${bcmd} -DARCH_SOURCE=`fquote arch/${opt_arch}.c`"
-bcmd="${bcmd} -DCORE_COUNT=${opt_cores}"
-bcmd="${bcmd} -DMUTA_RANGE=`fpow ${opt_muta_pow}`"
-bcmd="${bcmd} -DMVEC_SIZE=`fpow ${opt_mvec_pow}`"
-bcmd="${bcmd} -DNCURSES_WIDECHAR=1"
-bcmd="${bcmd} -DSEED=${opt_seed}ul"
-bcmd="${bcmd} -DSYNC_INTERVAL=`fpow ${opt_sync_pow}`"
-bcmd="${bcmd} -DTGAP_SIZE=${opt_thread_gap}ul"
-
-case ${cmd} in
-bench)
- bcmd="${bcmd} -DBENCH_STEPS=${opt_steps}ul"
- bcmd="${bcmd} -DUI=`fquote bench.c`"
- ;;
-esac
-
-case ${cmd} in
-bench|new)
- anc_list=
-
- for cix in `seq 1 ${opt_cores}` ; do
- anc_spec=`echo ${opt_anc_spec}, | cut -s -d, -f${cix}`
- anc_spec=${anc_spec:-${opt_anc_def}}
-
- if [[ -n ${anc_spec} ]] ; then
- anc_src=ancs/${opt_arch}/${anc_spec}.asm
- anc_path=${salis_tmp}/${anc_spec}.asm
- sed -E '/(^$|^;)/d; s/ +/ /g' ${anc_src} > ${anc_path}
- else
- anc_path=_
- fi
-
- anc_list=${anc_list}${anc_path},
- done
-
- bcmd="${bcmd} -DANC_LIST=`fquote "${anc_list::-1}"`"
- bcmd="${bcmd} -DANC_HALF=`[[ ${opt_half} == true ]] && echo 1 || echo 0`"
- bcmd="${bcmd} -DANC_CLONES=${opt_clones}"
- ;;
-esac
-
-case ${cmd} in
-load|new)
- bcmd="${bcmd} -DAUTO_SAVE_INTERVAL=`fpow ${opt_auto_save_pow}`"
- bcmd="${bcmd} -DAUTO_SAVE_NAME_LEN=$((${#sim_path} + 20))"
- bcmd="${bcmd} -DMUTA_FLIP_BIT=`[[ ${opt_muta_flip} == true ]] && echo 1 || echo 0`"
- bcmd="${bcmd} -DSIM_NAME=`fquote ${opt_name}`"
- bcmd="${bcmd} -DSIM_PATH=`fquote ${sim_path}`"
- bcmd="${bcmd} -DUI=`fquote ui/${opt_ui}.c`"
- ;;
-esac
-
-blue "Using build command:"
-echo "${bcmd}"
-eval "${bcmd}"
-
-case ${cmd} in
-new)
- if [[ -d ${sim_dir} ]] && [[ ${opt_force} == true ]] ; then
- red "Force flag used. Wiping old simulation at '${sim_dir}':"
- rm -rv ${sim_dir}
- fi
-
- if [[ -d ${sim_dir} ]] ; then
- red "Error: simulation directory found at '${sim_dir}'."
- red "Please, remove it or call 'load' instead."
- exit 1
- fi
-
- blue "Creating new simulation directory at '${sim_dir}':"
- mkdir -pv ${sim_dir}
- ;;
-esac
-
-rcmd="`[[ -z ${opt_pre_cmd} ]] || echo "${opt_pre_cmd} "`${salis_exe}"
-
-blue "Using run command:"
-echo "${rcmd}"
-
-blue "Running Salis..."
-eval "${rcmd}"
-
-case ${cmd} in
-new)
- blue "Saving new simulation configuration file at:"
- echo "${sim_opts}"
-
- for ((i = 0; i < ${#options[@]}; i++)) ; do
- oopt=`field "${options[${i}]}" 7`
-
- [[ ! ${oopt} =~ new ]] || [[ ${oopt} =~ load ]] && continue
-
- lopt=`field "${options[${i}]}" 2`
- nopt=opt_${lopt//-/_}
-
- echo "${nopt}=${!nopt}" >> ${sim_opts}
- done
- ;;
-esac
-
-blue "Removing temporary Salis directory and resources:"
-rm -rv ${salis_tmp}
diff --git a/salis.py b/salis.py
new file mode 100755
index 0000000..dabc2a3
--- /dev/null
+++ b/salis.py
@@ -0,0 +1,471 @@
+#!/usr/bin/env -S PYTHONDONTWRITEBYTECODE=1 python
+
+# index
+# [section] argument parser
+# [section] run unit tests
+# [section] build class
+# [section] logging
+# [section] options dict formatter
+# [section] source options
+# [section] source architecture variables
+# [section] ancestor assembler
+# [section] compiler flags
+# [section] build and launch
+
+import ctypes
+import json
+import os
+import random
+import runpy
+import shutil
+import socket
+import subprocess
+import sys
+import unittest
+
+from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, ArgumentTypeError, RawTextHelpFormatter
+from tempfile import TemporaryDirectory
+from types import SimpleNamespace
+
+# ------------------------------------------------------------------------------
+# [section] argument parser
+# ------------------------------------------------------------------------------
+prog = sys.argv[0]
+epilog = f"Use '-h' to show command arguments; e.g. '{prog} new -h'"
+
+parser = ArgumentParser(description="Salis: Simple A-Life Simulator", epilog=epilog, formatter_class=RawTextHelpFormatter, prog=prog)
+sub_parsers = parser.add_subparsers(dest="command", required=True)
+formatter_class = lambda prog: ArgumentDefaultsHelpFormatter(max_help_position=48, prog=prog)
+
+new = sub_parsers.add_parser("new", formatter_class=formatter_class, help="create new simulation")
+load = sub_parsers.add_parser("load", formatter_class=formatter_class, help="load saved simulation")
+server = sub_parsers.add_parser("server", formatter_class=formatter_class, help="run data server")
+client = sub_parsers.add_parser("client", formatter_class=formatter_class, help="run data client")
+test = sub_parsers.add_parser("test", formatter_class=formatter_class, help="run unit tests")
+
+def seed(i):
+ ival = int(i, 0)
+ if ival < -1: raise ArgumentTypeError("invalid seed value")
+ return ival
+
+def pos(i):
+ ival = int(i, 0)
+ if ival < 0: raise ArgumentTypeError("value must be positive integer")
+ return ival
+
+def nat(i):
+ ival = int(i, 0)
+ if ival < 1: raise ArgumentTypeError("value must be greater than zero")
+ return ival
+
+def port(i):
+ ival = int(i, 0)
+ if not 0 <= ival <= 65535: raise ArgumentTypeError("value must be valid port number")
+ return ival
+
+fmt_id = lambda val: val
+fmt_hex = lambda val: hex(int(val, 0)) if type(val) == str else hex(val)
+
+options = {
+ (("a", "anc"), (new,), fmt_id): {"metavar": "ANC", "help": "ancestor file name without extension; to be compiled on all cores; ANC points to '{anc-path}/{arch}/{ANC}.asm'", "required": True, "type": str},
+ (("A", "anc-path"), (new,), fmt_id): {"metavar": "PATH", "help": "path to search for ancestor files", "default": "anc", "required": False, "type": str},
+ (("b", "build-only"), (new, load, server, client), fmt_id): {"action": "store_true", "help": "only build (do not run) executable", "required": False},
+ (("C", "clones"), (new,), fmt_id): {"metavar": "N", "help": "number of ancestor clones on each core", "default": 1, "required": False, "type": nat},
+ (("c", "cores"), (new,), fmt_id): {"metavar": "N", "help": "number of simulator cores", "default": 2, "required": False, "type": nat},
+ (("d", "data-push-pow"), (new,), fmt_id): {"metavar": "POW", "help": "data aggregation interval exponent; interval = 2^{POW} >= {sync-pow}", "default": 28, "required": False, "type": nat},
+ (("f", "font"), (client,), fmt_id): {"metavar": "FILE", "help": "font face to use", "default": "/usr/share/fonts/droid/DroidSansMono.ttf", "required": False},
+ (("f", "force"), (new,), fmt_id): {"action": "store_true", "help": "overwrite existing simulation of given name", "required": False},
+ (("g", "c-compiler"), (new, load, server, client), fmt_id): {"metavar": "CC", "help": "C compiler to use", "default": "gcc", "required": False, "type": str},
+ (("G", "c-compiler-flags"), (new, load, server, client), fmt_id): {"metavar": "FLAGS", "help": "base set of flags to pass to C compiler", "default": "-Wall -Wextra -Werror -Wno-overlength-strings -pedantic", "required": False, "type": str},
+ (("H", "home"), (new, load, server), fmt_id): {"metavar": "PATH", "help": "salis home directory", "default": os.path.join(os.environ["HOME"], ".salis"), "required": False, "type": str},
+ (("i", "ip"), (client,), fmt_id): {"metavar": "IP", "help": "ip address of server", "default": "127.0.0.1", "required": False, "type": str},
+ (("M", "muta-pow"), (new,), fmt_id): {"metavar": "POW", "help": "mutator range exponent; each step a cosmic ray hits addr, where addr = rand_uint64() %% 2^{POW}; lower values of POW mean higher mutation rates", "default": 32, "required": False, "type": pos},
+ (("m", "mvec-pow"), (new,), fmt_id): {"metavar": "POW", "help": "memory core size exponent; size = 2^{POW}", "default": 20, "required": False, "type": pos},
+ (("n", "name"), (new, load, server), fmt_id): {"metavar": "NAME", "help": "name of new or loaded simulation", "default": "def.sim", "required": False, "type": str},
+ (("o", "optimized"), (new, load, server, client), fmt_id): {"action": "store_true", "help": "build with optimizations", "required": False},
+ (("p", "pattern"), (test,), fmt_id): {"metavar": "PATTERN", "help": "pattern for unit test discovery", "default": "*", "required": False, "type": str},
+ (("P", "port"), (server, client), fmt_id): {"metavar": "PORT", "help": "port number for data server", "default": 8080, "required": False, "type": port},
+ (("p", "pre-cmd"), (new, load, server, client), fmt_id): {"metavar": "CMD", "help": "shell command with which to wrap call to executable; e.g. gdb, time, valgrind, etc.", "required": False, "type": str},
+ (("s", "seed"), (new,), fmt_hex): {"metavar": "SEED", "help": "seed value for new simulation; a value of 0 disables cosmic rays; a value of -1 creates a random seed", "default": 0, "required": False, "type": seed},
+ (("T", "keep-temp-dir"), (new, load, server, client), fmt_id): {"action": "store_true", "help": "keep temporary directory on exit", "required": False},
+ (("t", "thread-gap"), (new, load), fmt_hex): {"metavar": "N", "help": "memory gap between core elements in bytes; may help reduce cache misses", "default": 0x100, "required": False, "type": nat},
+ (("u", "ui"), (new, load), fmt_id): {"metavar": "UI", "help": "user interface", "default": "curses", "required": False, "type": str},
+ (("U", "ui-path"), (new, load), fmt_id): {"metavar": "PATH", "help": "path to search for UIs", "default": "ui", "required": False, "type": str},
+ (("v", "no-vsync"), (client,), fmt_id): {"action": "store_true", "help": "disable vsync", "required": False},
+ (("v", "vm-arch"), (new,), fmt_id): {"metavar": "ARCH", "help": "VM architecture", "default": "null", "required": False, "type": str},
+ (("x", "cpp-compiler"), (client,), fmt_id): {"metavar": "CXX", "help": "C++ compiler to use", "default": "g++", "required": False, "type": str},
+ (("X", "cpp-compiler-flags"), (client,), fmt_id): {"metavar": "FLAGS", "help": "base set of flags to pass to C++ compiler", "default": "-Wall -Wextra -Werror -pedantic -std=c++20 -lstdc++", "required": False, "type": str},
+ (("y", "sync-pow"), (new,), fmt_id): {"metavar": "POW", "help": "core sync interval exponent; sync events occur every N steps, where N = 2^{POW}", "default": 20, "required": False, "type": pos},
+ (("z", "auto-save-pow"), (new,), fmt_id): {"metavar": "POW", "help": "auto-save interval exponent; auto-saves occur every N steps, where N = 2^{POW}", "default": 36, "required": False, "type": pos},
+}
+
+for (name, sub_parsers, _), kwargs in options.items():
+ for sub_parser in sub_parsers:
+ sub_parser.add_argument(f"-{name[0]}", f"--{name[1]}", **kwargs)
+
+args = parser.parse_args()
+
+# ------------------------------------------------------------------------------
+# [section] run unit tests
+# ------------------------------------------------------------------------------
+if args.command == "test":
+ test_runner = unittest.TextTestRunner(verbosity=2)
+ test_loader = unittest.TestLoader()
+ test_loader.testNamePatterns = [args.pattern]
+ test_runner.run(test_loader.discover("test"))
+ sys.exit(0)
+
+# ------------------------------------------------------------------------------
+# [section] build class
+# ------------------------------------------------------------------------------
+class Build:
+ def __init__(self, path, log, library=False, cpp=False):
+ self.log = log
+ self.library = library
+
+ self.tempdir = TemporaryDirectory(prefix="salis_", delete=not args.keep_temp_dir)
+ self.log.info(f"Generated temporary directory for builds at: {self.tempdir.name}")
+
+ self.name = os.path.splitext(os.path.basename(path))[0]
+ self.binfile = os.path.join(self.tempdir.name, f"{self.name}{".so" if library else ""}")
+ self.argsfile = os.path.join(self.tempdir.name, f"{self.name}.arg")
+
+ self.flags = {*(args.cpp_compiler_flags if cpp else args.c_compiler_flags).split(), *({"-shared", "-fPIC"} if library else set()), *({"-O3"} if args.optimized else {"-ggdb"})}
+ self.defines = {"-DNDEBUG"} if args.optimized else set()
+ self.links = set()
+
+ self.build_cmd = [args.cpp_compiler if cpp else args.c_compiler, f"@{self.argsfile}", path, "-o", self.binfile]
+ self.log.info(f"Build class initialized for {"library" if library else "executable"}: {path}")
+ self.log.info(f"Compiler flags stored at: {self.argsfile}")
+
+ def build(self):
+ fmt_nl = lambda line: f"{line}\n"
+ fmt_define = lambda line: f"{line.replace(" ", "\\ ").replace("\"", "\\\"").replace("'", "\\'")}\n"
+
+ with open(self.argsfile, "w") as f:
+ f.writelines(map(fmt_nl, sorted(self.flags)))
+ f.writelines(map(fmt_define, sorted(self.defines)))
+ f.writelines(map(fmt_nl, sorted(self.links)))
+
+ self.log.info(f"Running build command: '{self.build_cmd}'")
+ subprocess.run(self.build_cmd, check=True)
+
+ def exec(self):
+ assert not self.library
+
+ run_cmd = args.pre_cmd.split() if args.pre_cmd else []
+ run_cmd.append(self.binfile)
+
+ self.log.info(f"Running binary with command: '{" ".join(run_cmd)}'")
+ proc = subprocess.Popen(run_cmd, stdout=sys.stdout, stderr=sys.stderr)
+
+ # When using signals (e.g. SIGTERM), they must be sent to the entire process group
+ # to make sure both the simulator and the interpreter get shut down.
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.terminate()
+ proc.wait()
+
+ code = proc.returncode
+
+ if code != 0: raise RuntimeError(f"Binary returned code: {code}")
+
+# ------------------------------------------------------------------------------
+# [section] logging
+# ------------------------------------------------------------------------------
+log_logs = []
+log_ns = SimpleNamespace()
+log_ns.info = lambda msg: log_logs.append(("info", msg))
+log_ns.warn = lambda msg: log_logs.append(("warn", msg))
+log_b = Build("core/logger.c", log_ns, library=True)
+log_b.build()
+
+# Pull in logging functions from C
+# This way there's only a single unified logging system to care about
+log = SimpleNamespace()
+log_dll = ctypes.CDLL(log_b.binfile)
+log.info = lambda msg: log_dll.log_info_default(msg.encode())
+log.warn = lambda msg: log_dll.log_warn_default(msg.encode())
+
+for log_level, log_msg in log_logs:
+ getattr(log, log_level)(log_msg)
+
+# ------------------------------------------------------------------------------
+# [section] options dict formatter
+# ------------------------------------------------------------------------------
+fmt_h2u = lambda field: field.replace("-", "_")
+fmt_u2h = lambda field: field.replace("_", "-")
+
+def fmt_field(field, val):
+ for ((_, name), _, fmt), _ in options.items():
+ if name == fmt_u2h(field):
+ return fmt(val)
+
+ return fmt_id(val)
+
+def fmt_opts(opts):
+ opts_out = {}
+
+ for field, val in opts.items():
+ opts_out[field] = fmt_field(field, val)
+
+ return json.dumps(opts_out, indent=2)
+
+# ------------------------------------------------------------------------------
+# [section] source options
+# ------------------------------------------------------------------------------
+log.info(f"Called '{prog} {args.command}' with the following options: {fmt_opts(vars(args))}")
+ns = SimpleNamespace()
+
+def gen_sim_paths():
+ ns.sim_dir = os.path.join(args.home, args.name)
+ ns.sim_edir = os.path.join(ns.sim_dir, "evas")
+ ns.sim_opts = os.path.join(ns.sim_dir, "opts.json")
+ ns.sim_path = os.path.join(ns.sim_dir, args.name)
+
+def source_from_opts_file():
+ if not os.path.isdir(ns.sim_dir):
+ raise RuntimeError(f"No simulation found named {args.name}")
+
+ with open(ns.sim_opts, "r") as f:
+ opts = json.loads(f.read())
+
+ for key, val in opts.items():
+ setattr(args, key, val)
+
+ log.info(f"Sourced configuration from '{ns.sim_opts}': {fmt_opts(opts)}")
+
+def request_from_server(request):
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
+ client.connect((args.ip, args.port))
+ client.sendall(json.dumps(request).encode())
+ client.shutdown(socket.SHUT_WR)
+ return json.load(client.makefile(mode="r"))
+
+# New simulation
+if args.command == "new":
+ gen_sim_paths()
+
+ if 0 < args.data_push_pow < args.sync_pow:
+ raise RuntimeError("Data push power must be equal or greater than core sync power")
+
+ if os.path.isdir(ns.sim_dir) and args.force:
+ log.warn(f"Force flag used! Wiping old simulation at: {ns.sim_dir}")
+ shutil.rmtree(ns.sim_dir)
+
+ if os.path.isdir(ns.sim_dir):
+ raise RuntimeError(f"Simulation directory found at: {ns.sim_dir}; use --force flag to remove it")
+
+ if args.seed == -1:
+ args.seed = random.getrandbits(64)
+ log.info(f"Using random seed: {hex(args.seed)}")
+
+ log.info(f"Creating new simulation directory at: {ns.sim_dir}")
+ log.info(f"Creating new event-array storage directory at: {ns.sim_edir}")
+ log.info(f"Creating configuration file at: {ns.sim_opts}")
+ os.mkdir(ns.sim_dir)
+ os.mkdir(ns.sim_edir)
+
+ opts = {fmt_h2u(name[1]): getattr(args, fmt_h2u(name[1])) for (name, sub_parsers, _), kwargs in options.items() if new in sub_parsers and load not in sub_parsers}
+
+ with open(ns.sim_opts, "w") as f:
+ f.write(f"{fmt_opts(opts)}\n")
+
+# Loaded simulation
+if args.command == "load":
+ gen_sim_paths()
+ source_from_opts_file()
+
+# Data server
+if args.command == "server":
+ gen_sim_paths()
+ source_from_opts_file()
+
+# Data client
+if args.command == "client":
+ # Check provided font file exists
+ if not os.path.isfile(args.font):
+ raise RuntimeError(f"Font file {args.font} does not exist; please provide a valid font")
+
+ # Pull basic information from the server
+ # Required to build the client with correct flags
+ args.name = request_from_server({"request": "name"})["name"]
+ log.info(f"Sourced simulation name from '{args.ip}:{args.port}': {args.name}")
+
+ opts = request_from_server({"request": "opts"})
+
+ for key, val in opts.items():
+ setattr(args, key, val)
+
+ log.info(f"Sourced configuration from '{args.ip}:{args.port}': {fmt_opts(opts)}")
+
+ # Server and client should be on the same hash
+ server_hash = request_from_server({"request": "hash"})["hash"]
+ client_hash = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("utf-8").strip()
+
+ if server_hash != client_hash:
+ raise RuntimeError(f"Server hash '{server_hash}' and client hash '{client_hash}' do not match")
+
+ log.info(f"Confirmed server and client are running against the same git hash: {server_hash}")
+
+# ------------------------------------------------------------------------------
+# [section] source architecture variables
+# ------------------------------------------------------------------------------
+arch_path = os.path.join("arch", args.vm_arch, "vars.py")
+log.info(f"Loading architecture variables from: {arch_path}")
+arch_vars = runpy.run_path(arch_path, init_globals=globals())
+
+# ------------------------------------------------------------------------------
+# [section] ancestor assembler
+# ------------------------------------------------------------------------------
+anc_path = os.path.join(args.anc_path, args.vm_arch, f"{args.anc}.asm")
+
+if not os.path.isfile(anc_path):
+ raise RuntimeError(f"Could not find ancestor file: {anc_path}")
+
+with open(anc_path, "r") as f:
+ lines = f.read().splitlines()
+
+lines = filter(lambda line: not line.startswith(";"), lines)
+lines = filter(lambda line: not line.isspace(), lines)
+lines = filter(lambda line: line, lines)
+lines = map(lambda line: line.split(), lines)
+
+anc_bytes = []
+
+for line in lines:
+ found = False
+
+ for byte, tup in enumerate(arch_vars["inst_set"]):
+ if line == tup[0]:
+ anc_bytes.append(byte)
+ found = True
+ break
+
+ if not found:
+ raise RuntimeError(f"Unrecognized instruction in ancestor file: {line}")
+
+anc_repr = f"{{{",".join(map(str, anc_bytes))}}}"
+log.info(f"Compiled ancestor file '{anc_path}' into byte array: {anc_repr}")
+
+# ------------------------------------------------------------------------------
+# [section] compiler flags
+# ------------------------------------------------------------------------------
+def pop_ui_vars():
+ ns.ui_vars_path = os.path.join(args.ui_path, args.ui, "vars.py")
+ ns.ui_vars = runpy.run_path(ns.ui_vars_path, init_globals=globals())
+ log.info(f"Sourced UI variables from: {ns.ui_vars_path}")
+
+ ns.b.flags.add(f"-I{args.ui_path}/{args.ui}")
+ ns.b.flags.update({*ns.ui_vars["flags"]})
+ ns.b.defines.update({*ns.ui_vars["defines"]})
+ ns.b.links.update({*ns.ui_vars["links"]})
+
+def pop_data_push_vars():
+ ns.sim_db = os.path.join(ns.sim_dir, f"{args.name}.sqlite3")
+ ns.b.defines.add(f"-DDATA_PUSH_PATH=\"{ns.sim_db}\"")
+ ns.b.links.add("-lsqlite3")
+ ns.b.links.add("-lz")
+
+def pop_sim_path_vars():
+ ns.b.defines.add(f"-DSIM_EDIR=\"{ns.sim_edir}\"")
+ ns.b.defines.add(f"-DSIM_OPTS=\"{ns.sim_opts}\"")
+ ns.b.defines.add(f"-DSIM_PATH=\"{ns.sim_path}\"")
+
+def pop_net_vars():
+ ns.b.defines.add(f"-DPORT={args.port}")
+ ns.b.defines.add(f"-DPORT_STR=\"{args.port}\"")
+ ns.b.links.add("-ljson-c")
+
+def pop_general():
+ ns.b.flags.add(f"-Iarch/{args.vm_arch}")
+ ns.b.flags.add("-Icore")
+
+ if arch_vars["muta_flip"]:
+ ns.b.defines.add("-DMUTA_FLIP")
+
+ if arch_vars["mvec_loop"]:
+ ns.b.defines.add("-DMVEC_LOOP")
+
+ ns.b.defines.add(f"-DANC=\"{args.anc}\"")
+ ns.b.defines.add(f"-DANC_BYTES={anc_repr}")
+ ns.b.defines.add(f"-DANC_SIZE={len(anc_bytes)}")
+ ns.b.defines.add(f"-DARCH=\"{args.vm_arch}\"")
+ ns.b.defines.add(f"-DAUTOSAVE_INTERVAL={2 ** args.auto_save_pow}ul")
+ ns.b.defines.add(f"-DCLONES={args.clones}")
+ ns.b.defines.add(f"-DCOMMAND_{args.command.upper()}")
+ ns.b.defines.add(f"-DCORE_DATA_FIELDS={" ".join(f"CORE_DATA_FIELD({", ".join(field)})" for field in arch_vars["core_data_fields"])}")
+ ns.b.defines.add(f"-DCORE_FIELD_COUNT={len(arch_vars["core_fields"])}")
+ ns.b.defines.add(f"-DCORE_FIELDS={" ".join(f"CORE_FIELD({", ".join(field)})" for field in arch_vars["core_fields"])}")
+ ns.b.defines.add(f"-DCORES={args.cores}")
+ ns.b.defines.add(f"-DDATA_PUSH_INTERVAL={2 ** args.data_push_pow}ul")
+ ns.b.defines.add(f"-DFOR_CORES={" ".join(f"FOR_CORE({i})" for i in range(args.cores))}")
+ ns.b.defines.add(f"-DINST_COUNT={len(arch_vars["inst_set"])}")
+ ns.b.defines.add(f"-DINST_SET(core, pref)={" ".join(f"INST(core, pref, {index}, {"_".join(inst[0])}, \"{" ".join(inst[0])}\", L'{inst[1]}')" for index, inst in enumerate(arch_vars["inst_set"]))}")
+ ns.b.defines.add(f"-DMUTA_RANGE={2 ** args.muta_pow}ul")
+ ns.b.defines.add(f"-DMVEC_SIZE={2 ** args.mvec_pow}ul")
+ ns.b.defines.add(f"-DNAME=\"{args.name}\"")
+ ns.b.defines.add(f"-DPROC_FIELD_COUNT={len(arch_vars["proc_fields"])}")
+ ns.b.defines.add(f"-DPROC_FIELDS={" ".join(f"PROC_FIELD({", ".join(field)})" for field in arch_vars["proc_fields"])}")
+ ns.b.defines.add(f"-DSEED={args.seed}ul")
+ ns.b.defines.add(f"-DSYNC_INTERVAL={2 ** args.sync_pow}ul")
+
+# Populate for new
+if args.command == "new":
+ ns.b = Build("core/salis.c", log)
+ pop_ui_vars()
+ pop_data_push_vars()
+ pop_sim_path_vars()
+ pop_general()
+
+ ns.b.defines.add(f"-DAUTOSAVE_NAME_LEN={len(ns.sim_path) + 20}")
+ ns.b.defines.add(f"-DEVA_SAVE_NAME_LEN={len(ns.sim_edir) + 25}")
+ ns.b.defines.add(f"-DTHREAD_GAP={args.thread_gap}")
+
+# Populate for load
+if args.command == "load":
+ ns.b = Build("core/salis.c", log)
+ pop_ui_vars()
+ pop_data_push_vars()
+ pop_sim_path_vars()
+ pop_general()
+
+ ns.b.defines.add(f"-DAUTOSAVE_NAME_LEN={len(ns.sim_path) + 20}")
+ ns.b.defines.add(f"-DEVA_SAVE_NAME_LEN={len(ns.sim_edir) + 25}")
+ ns.b.defines.add(f"-DTHREAD_GAP={args.thread_gap}")
+
+# Populate for server
+if args.command == "server":
+ ns.b = Build("core/server.c", log)
+ pop_data_push_vars()
+ pop_sim_path_vars()
+ pop_net_vars()
+ pop_general()
+
+ ns.b.defines.add(f"-DEVA_SAVE_NAME_LEN={len(ns.sim_edir) + 25}")
+
+# Populate for client
+if args.command == "client":
+ ns.b = Build("core/client.cpp", log, cpp=True)
+ pop_net_vars()
+ pop_general()
+
+ if not args.no_vsync:
+ ns.b.defines.add("-DVSYNC")
+
+ ns.b.defines.add(f"-DFONT_SOURCE=\"{args.font}\"")
+ ns.b.defines.add(f"-DIP=\"{args.ip}\"")
+ ns.b.links.add("-lGL")
+ ns.b.links.add("-lglfw")
+ ns.b.links.add("-limgui")
+ ns.b.links.add("-limplot")
+ ns.b.links.add("-lm")
+
+# ------------------------------------------------------------------------------
+# [section] build and launch
+# ------------------------------------------------------------------------------
+ns.b.build()
+
+if not args.build_only:
+ ns.b.exec()
diff --git a/sim.png b/sim.png
new file mode 100644
index 0000000..23eb84c
--- /dev/null
+++ b/sim.png
Binary files differ
diff --git a/src/arch/dummy.c b/src/arch/dummy.c
deleted file mode 100644
index b440be3..0000000
--- a/src/arch/dummy.c
+++ /dev/null
@@ -1,147 +0,0 @@
-// 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/arch/salis-v1.c b/src/arch/salis-v1.c
deleted file mode 100644
index 1085883..0000000
--- a/src/arch/salis-v1.c
+++ /dev/null
@@ -1,875 +0,0 @@
-// Project: Salis
-// Author: Paul Oliver
-// Email: contact@pauloliver.dev
-
-/*
- * This is based on the original VM architecture in salis-v1:
- * https://git.pauloliver.dev/salis-v1/about/
- */
-
-bool mvec_is_alloc(const Core *core, u64 pix);
-void mvec_alloc(Core *core, u64 addr);
-void mvec_free(Core *core, u64 addr);
-u8 mvec_get_inst(const Core *core, u64 addr);
-void mvec_set_inst(Core *core, u64 addr, u8 inst);
-bool mvec_is_proc_owner(const Core *core, u64 addr, u64 pix);
-void proc_new(Core *core, const Proc *proc);
-bool proc_is_live(const Core *core, u64 pix);
-const Proc *proc_get(const Core *core, u64 pix);
-Proc *proc_fetch(Core *core, u64 pix);
-
-#define INST_LIST \
- INST(noop, L' ') \
- INST(nop0, L'0') \
- INST(nop1, L'1') \
- INST(nop2, L'2') \
- INST(nop3, L'3') \
- \
- INST(jmpb, L'(') \
- INST(jmpf, L')') \
- INST(adrb, L'[') \
- INST(adrf, L']') \
- INST(ifnz, L'?') \
- \
- INST(allb, L'{') \
- INST(allf, L'}') \
- INST(bswp, L'%') \
- INST(bclr, L'|') \
- INST(splt, L'$') \
- \
- INST(addn, L'+') \
- INST(subn, L'-') \
- INST(muln, L'*') \
- INST(divn, L'/') \
- INST(incn, L'^') \
- INST(decn, L'v') \
- INST(notn, L'!') \
- INST(shfl, L'<') \
- INST(shfr, L'>') \
- INST(zero, L'z') \
- INST(unit, L'u') \
- \
- INST(pshn, L'#') \
- INST(popn, L'~') \
- \
- INST(load, L'.') \
- INST(wrte, L':') \
- INST(dupl, L'"') \
- INST(swap, L'x') \
- \
- INST(keya, L'a') \
- INST(keyb, L'b') \
- INST(keyc, L'c') \
- INST(keyd, L'd') \
- INST(keye, L'e') \
- INST(keyf, L'f') \
- INST(keyg, L'g') \
- INST(keyh, L'h') \
- INST(keyi, L'i') \
- INST(keyj, L'j') \
- INST(keyk, L'k') \
- INST(keyl, L'l') \
- INST(keym, L'm') \
- INST(keyn, L'n') \
- INST(keyo, L'o') \
- INST(keyp, L'p') \
- \
- INST(loka, L'A') \
- INST(lokb, L'B') \
- INST(lokc, L'C') \
- INST(lokd, L'D') \
- INST(loke, L'E') \
- INST(lokf, L'F') \
- INST(lokg, L'G') \
- INST(lokh, L'H') \
- INST(loki, L'I') \
- INST(lokj, L'J') \
- INST(lokk, L'K') \
- INST(lokl, L'L') \
- INST(lokm, L'M') \
- INST(lokn, L'N') \
- INST(loko, L'O') \
- INST(lokp, L'P')
-
-#define MNEMONIC_BUFF_SIZE (0x5)
-
-enum sinst {
-#define INST(name, symb) name,
- INST_LIST
-#undef INST
- INST_COUNT
-};
-
-#define PROC_FIELDS \
- PROC_FIELD(u64, ip) \
- PROC_FIELD(u64, sp) \
- PROC_FIELD(u64, mb0a) \
- PROC_FIELD(u64, mb0s) \
- PROC_FIELD(u64, mb1a) \
- PROC_FIELD(u64, mb1s) \
- PROC_FIELD(u64, r0x) \
- PROC_FIELD(u64, r1x) \
- PROC_FIELD(u64, r2x) \
- PROC_FIELD(u64, r3x) \
- PROC_FIELD(u64, s0) \
- PROC_FIELD(u64, s1) \
- PROC_FIELD(u64, s2) \
- PROC_FIELD(u64, s3) \
- PROC_FIELD(u64, s4) \
- PROC_FIELD(u64, s5) \
- PROC_FIELD(u64, s6) \
- PROC_FIELD(u64, s7)
-
-struct Proc {
-#define PROC_FIELD(type, name) type name;
- PROC_FIELDS
-#undef PROC_FIELD
-};
-
-u64 arch_proc_mb0_addr(const Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
- return proc_get(core, pix)->mb0a;
-}
-
-u64 arch_proc_mb0_size(const Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
- return proc_get(core, pix)->mb0s;
-}
-
-u64 arch_proc_mb1_addr(const Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
- return proc_get(core, pix)->mb1a;
-}
-
-u64 arch_proc_mb1_size(const Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
- return proc_get(core, pix)->mb1s;
-}
-
-u64 arch_proc_ip_addr(const Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
- return proc_get(core, pix)->ip;
-}
-
-u64 arch_proc_sp_addr(const Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
- return proc_get(core, pix)->sp;
-}
-
-u64 arch_proc_slice(const Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- (void)core;
- (void)pix;
-
- return 1;
-}
-
-void _free_memory_block(Core *core, u64 addr, u64 size) {
- assert(core);
- assert(size);
-
- for (u64 i = 0; i < size; ++i) {
- mvec_free(core, addr + i);
- }
-}
-
-void arch_on_proc_kill(Core *core) {
- assert(core);
- assert(core->pnum > 1);
-
- Proc *pfst = proc_fetch(core, core->pfst);
-
- _free_memory_block(core, pfst->mb0a, pfst->mb0s);
-
- if (pfst->mb1s) {
- _free_memory_block(core, pfst->mb1a, pfst->mb1s);
- }
-
- memcpy(pfst, &g_dead_proc, sizeof(Proc));
-}
-
-#if ACTION == ACT_BENCH || ACTION == ACT_NEW
-void arch_anc_init(Core *core, u64 size) {
- assert(core);
-
-#if ANC_HALF == 1
- u64 addr = U64_HALF;
-#else
- u64 addr = 0;
-#endif
-
- for (int i = 0; i < ANC_CLONES; ++i) {
- u64 addr_clone = addr + ((MVEC_SIZE / ANC_CLONES) * i);
-
- Proc *panc = proc_fetch(core, i);
-
- panc->mb0a = addr_clone;
- panc->mb0s = size;
- panc->ip = addr_clone;
- panc->sp = addr_clone;
- }
-}
-#endif
-
-u8 _get_inst(const Core *core, u64 addr) {
- assert(core);
-
- return mvec_get_inst(core, addr) % INST_COUNT;
-}
-
-void _increment_ip(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
-
- proc->ip++;
- proc->sp = proc->ip;
-}
-
-bool _is_between(u8 inst, u8 lo, u8 hi) {
- assert(inst < INST_COUNT);
- assert(lo < INST_COUNT);
- assert(hi < INST_COUNT);
- assert(lo < hi);
-
- return (inst >= lo) && (inst <= hi);
-}
-
-bool _is_key(u8 inst) {
- assert(inst < INST_COUNT);
-
- return _is_between(inst, keya, keyp);
-}
-
-bool _is_lock(u8 inst) {
- assert(inst < INST_COUNT);
-
- return _is_between(inst, loka, lokp);
-}
-
-bool _is_rmod(u8 inst) {
- assert(inst < INST_COUNT);
-
- return _is_between(inst, nop0, nop3);
-}
-
-bool _key_lock_match(u8 key, u8 lock) {
- assert(key < INST_COUNT);
- assert(lock < INST_COUNT);
- assert(_is_key(key));
-
- return (key - keya) == (lock - loka);
-}
-
-bool _seek(Core *core, u64 pix, bool fwrd) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u8 next = _get_inst(core, proc->ip + 1);
-
- if (!_is_key(next)) {
- _increment_ip(core, pix);
- return false;
- }
-
- u8 spin = _get_inst(core, proc->sp);
-
- if (_key_lock_match(next, spin)) {
- return true;
- }
-
- if (fwrd) {
- proc->sp++;
- } else {
- proc->sp--;
- }
-
- return false;
-}
-
-void _jump(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
-
-#ifndef NDEBUG
- u8 next = _get_inst(core, proc->ip + 1);
- u8 spin = _get_inst(core, proc->sp);
- assert(_is_key(next));
- assert(_is_lock(spin));
- assert(_key_lock_match(next, spin));
-#endif
-
- proc->ip = proc->sp;
-}
-
-void _get_reg_addr_list(Core *core, u64 pix, u64 **rlist, int rcount, bool offset) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- assert(rlist);
- assert(rcount);
- assert(rcount < 4);
-
- Proc *proc = proc_fetch(core, pix);
- u64 madr = proc->ip + (offset ? 2 : 1);
-
- for (int i = 0; i < rcount; ++i) {
- rlist[i] = &proc->r0x;
- }
-
- for (int i = 0; i < rcount; ++i) {
- u64 mnxt = madr + i;
- u8 mins = _get_inst(core, mnxt);
-
- if (!_is_rmod(mins)) {
- break;
- }
-
- switch (mins) {
- case nop0:
- rlist[i] = &proc->r0x;
- break;
- case nop1:
- rlist[i] = &proc->r1x;
- break;
- case nop2:
- rlist[i] = &proc->r2x;
- break;
- case nop3:
- rlist[i] = &proc->r3x;
- break;
- }
- }
-}
-
-void _addr(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u64 *reg;
-
-#ifndef NDEBUG
- u8 next = _get_inst(core, proc->ip + 1);
- u8 spin = _get_inst(core, proc->sp);
- assert(_is_key(next));
- assert(_is_lock(spin));
- assert(_key_lock_match(next, spin));
-#endif
-
- _get_reg_addr_list(core, pix, &reg, 1, true);
- *reg = proc->sp;
-
- _increment_ip(core, pix);
-}
-
-void _ifnz(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u64 *reg;
-
- _get_reg_addr_list(core, pix, &reg, 1, false);
-
- u64 jmod = _is_rmod(_get_inst(core, proc->ip + 1)) ? 1 : 0;
- u64 rmod = *reg ? 1 : 2;
-
- proc->ip += jmod + rmod;
- proc->sp = proc->ip;
-}
-
-void _free_child_memory_of(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
-
- assert(proc->mb1s);
-
- _free_memory_block(core, proc->mb1a, proc->mb1s);
-
- proc->mb1a = 0;
- proc->mb1s = 0;
-}
-
-void _alloc(Core *core, u64 pix, bool fwrd) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u64 *regs[2];
-
- _get_reg_addr_list(core, pix, regs, 2, false);
-
- u64 bsize = *regs[0];
-
- // do nothing if block-size is zero
- if (!bsize) {
- _increment_ip(core, pix);
- return;
- }
-
- // do nothing if sp is not adjacent to allocated mem. block
- if (proc->mb1s) {
- u64 exp_addr = proc->mb1a;
-
- if (fwrd) {
- exp_addr += proc->mb1s;
- } else {
- exp_addr--;
- }
-
- if (proc->sp != exp_addr) {
- _increment_ip(core, pix);
- return;
- }
- }
-
- // allocation was successful, store block address on register
- if (proc->mb1s == bsize) {
- _increment_ip(core, pix);
- *regs[1] = proc->mb1a;
- return;
- }
-
- // sp collided with another allocated block, clear and try again
- if (mvec_is_alloc(core, proc->sp)) {
- if (proc->mb1s) {
- _free_child_memory_of(core, pix);
- }
-
- if (fwrd) {
- proc->sp++;
- } else {
- proc->sp--;
- }
-
- return;
- }
-
- // otherwise enlarge block
- mvec_alloc(core, proc->sp);
-
- // adjust child block address and size
- if (!proc->mb1s || !fwrd) {
- proc->mb1a = proc->sp;
- }
-
- proc->mb1s++;
-
- // move sp to new location
- if (fwrd) {
- proc->sp++;
- } else {
- proc->sp--;
- }
-}
-
-void _bswap(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
-
- if (proc->mb1s) {
- u64 tmpa = proc->mb0a;
- u64 tmps = proc->mb0s;
-
- proc->mb0a = proc->mb1a;
- proc->mb0s = proc->mb1s;
- proc->mb1a = tmpa;
- proc->mb1s = tmps;
- }
-
- _increment_ip(core, pix);
-}
-
-void _bclear(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
-
- if (proc->mb1s) {
- _free_child_memory_of(core, pix);
- }
-
- _increment_ip(core, pix);
-}
-
-void _split(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
-
- if (proc->mb1s) {
- Proc child = {0};
-
- child.ip = proc->mb1a;
- child.sp = proc->mb1a;
- child.mb0a = proc->mb1a;
- child.mb0s = proc->mb1s;
-
- proc->mb1a = 0;
- proc->mb1s = 0;
-
- proc_new(core, &child);
- } else {
- assert(!proc->mb1a);
- }
-
- _increment_ip(core, pix);
-}
-
-void _3rop(Core *core, u64 pix, u8 inst) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- u64 *regs[3];
-
- _get_reg_addr_list(core, pix, regs, 3, false);
-
- switch (inst) {
- case addn:
- *regs[0] = *regs[1] + *regs[2];
- break;
- case subn:
- *regs[0] = *regs[1] - *regs[2];
- break;
- case muln:
- *regs[0] = *regs[1] * *regs[2];
- break;
- case divn:
- // do nothing on div. by zero
- if (*regs[2]) {
- *regs[0] = *regs[1] / *regs[2];
- }
-
- break;
- default:
- assert(false);
- }
-
- _increment_ip(core, pix);
-}
-
-void _1rop(Core *core, u64 pix, u8 inst) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- u64 *reg;
-
- _get_reg_addr_list(core, pix, &reg, 1, false);
-
- switch (inst) {
- case incn:
- (*reg)++;
- break;
- case decn:
- (*reg)--;
- break;
- case notn:
- *reg = !(*reg);
- break;
- case shfl:
- *reg <<= 1;
- break;
- case shfr:
- *reg >>= 1;
- break;
- case zero:
- *reg = 0;
- break;
- case unit:
- *reg = 1;
- break;
- default:
- assert(false);
- }
-
- _increment_ip(core, pix);
-}
-
-void _push(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u64 *reg;
-
- _get_reg_addr_list(core, pix, &reg, 1, false);
-
- proc->s7 = proc->s6;
- proc->s6 = proc->s5;
- proc->s5 = proc->s4;
- proc->s4 = proc->s3;
- proc->s3 = proc->s2;
- proc->s2 = proc->s1;
- proc->s1 = proc->s0;
- proc->s0 = *reg;
-
- _increment_ip(core, pix);
-}
-
-void _pop(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u64 *reg;
-
- _get_reg_addr_list(core, pix, &reg, 1, false);
-
- *reg = proc->s0;
- proc->s0 = proc->s1;
- proc->s1 = proc->s2;
- proc->s2 = proc->s3;
- proc->s3 = proc->s4;
- proc->s4 = proc->s5;
- proc->s5 = proc->s6;
- proc->s6 = proc->s7;
- proc->s7 = 0;
-
- _increment_ip(core, pix);
-}
-
-int _sp_dir(u64 src, u64 dst) {
- if (src == dst) {
- return 0;
- } else if (src - dst <= dst - src) {
- return -1;
- } else {
- return 1;
- }
-}
-
-void _load(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u64 *regs[2];
-
- _get_reg_addr_list(core, pix, regs, 2, false);
-
- int sp_dir = _sp_dir(proc->sp, *regs[0]);
-
- if (sp_dir == 1) {
- proc->sp++;
- } else if (sp_dir == -1) {
- proc->sp--;
- } else {
- *regs[1] = mvec_get_inst(core, *regs[0]);
- _increment_ip(core, pix);
- }
-}
-
-bool _is_writeable_by(const Core *core, u64 addr, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- return !mvec_is_alloc(core, addr) || mvec_is_proc_owner(core, addr, pix);
-}
-
-void _write(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u64 *regs[2];
-
- _get_reg_addr_list(core, pix, regs, 2, false);
-
- int sp_dir = _sp_dir(proc->sp, *regs[0]);
-
- if (sp_dir == 1) {
- proc->sp++;
- } else if (sp_dir == -1) {
- proc->sp--;
- } else {
- if (_is_writeable_by(core, *regs[0], pix)) {
- mvec_set_inst(core, *regs[0], *regs[1] % INST_CAPS);
- }
-
- _increment_ip(core, pix);
- }
-}
-
-void _2rop(Core *core, u64 pix, u8 inst) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- u64 *regs[2];
-
- _get_reg_addr_list(core, pix, regs, 2, false);
-
- switch (inst) {
- case dupl:
- *regs[1] = *regs[0];
- break;
- case swap:
- {
- u64 tmp = *regs[0];
- *regs[0] = *regs[1];
- *regs[1] = tmp;
- }
-
- break;
- default:
- assert(false);
- }
-
- _increment_ip(core, pix);
-}
-
-void arch_proc_step(Core *core, u64 pix) {
- assert(core);
- assert(proc_is_live(core, pix));
-
- Proc *proc = proc_fetch(core, pix);
- u8 inst = _get_inst(core, proc->ip);
-
- switch (inst) {
- case jmpb:
- if (_seek(core, pix, false)) {
- _jump(core, pix);
- }
-
- break;
- case jmpf:
- if (_seek(core, pix, true)) {
- _jump(core, pix);
- }
-
- break;
- case adrb:
- if (_seek(core, pix, false)) {
- _addr(core, pix);
- }
-
- break;
- case adrf:
- if (_seek(core, pix, true)) {
- _addr(core, pix);
- }
-
- break;
- case ifnz:
- _ifnz(core, pix);
- break;
- case allb:
- _alloc(core, pix, false);
- break;
- case allf:
- _alloc(core, pix, true);
- break;
- case bswp:
- _bswap(core, pix);
- break;
- case bclr:
- _bclear(core, pix);
- break;
- case splt:
- _split(core, pix);
- break;
- case addn:
- case subn:
- case muln:
- case divn:
- _3rop(core, pix, inst);
- break;
- case incn:
- case decn:
- case notn:
- case shfl:
- case shfr:
- case zero:
- case unit:
- _1rop(core, pix, inst);
- break;
- case pshn:
- _push(core, pix);
- break;
- case popn:
- _pop(core, pix);
- break;
- case load:
- _load(core, pix);
- break;
- case wrte:
- _write(core, pix);
- break;
- case dupl:
- case swap:
- _2rop(core, pix, inst);
- break;
- default:
- _increment_ip(core, pix);
- break;
- }
-
- return;
-}
-
-#ifndef NDEBUG
-void arch_validate_proc(const Core *core, u64 pix) {
- assert(core);
-
- const Proc *proc = proc_get(core, pix);
-
- assert(proc->mb0s);
-
- if (proc->mb1a) {
- assert(proc->mb1s);
- }
-
- for (u64 i = 0; i < proc->mb0s; ++i) {
- u64 addr = proc->mb0a + i;
- assert(mvec_is_alloc(core, addr));
- assert(mvec_is_proc_owner(core, addr, pix));
- }
-
- for (u64 i = 0; i < proc->mb1s; ++i) {
- u64 addr = proc->mb1a + i;
- assert(mvec_is_alloc(core, addr));
- assert(mvec_is_proc_owner(core, addr, pix));
- }
-}
-#endif
-
-wchar_t arch_symbol(u8 inst) {
- switch (inst % INST_COUNT) {
-#define INST(name, symb) case name: return symb;
- INST_LIST
-#undef INST
- }
-
- assert(false);
- return L'\0';
-}
-
-void arch_mnemonic(u8 inst, char *buff) {
- assert(buff);
-
- switch (inst % INST_COUNT) {
-#define INST(name, symb) case name: snprintf(buff, MNEMONIC_BUFF_SIZE, #name); return;
- INST_LIST
-#undef INST
- }
-
- assert(false);
-}
diff --git a/src/bench.c b/src/bench.c
deleted file mode 100644
index eb7e0e5..0000000
--- a/src/bench.c
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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
deleted file mode 100644
index 8114f30..0000000
--- a/src/graphics.c
+++ /dev/null
@@ -1,223 +0,0 @@
-// 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
deleted file mode 100644
index 814dd0d..0000000
--- a/src/salis.c
+++ /dev/null
@@ -1,688 +0,0 @@
-// 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)
-
-#define U64_HALF (0x8000000000000000)
-
-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);
-
-#if ANC_HALF == 1
- u64 addr = U64_HALF;
-#else
- u64 addr = 0;
-#endif
-
- 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) {
- for (u64 j = 0; j < ANC_CLONES; ++j) {
- u64 addr_clone = addr + ((MVEC_SIZE / ANC_CLONES) * j);
-
- mvec_alloc(core, addr_clone);
- mvec_set_inst(core, addr_clone, 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 = ANC_CLONES;
- core->pcap = ANC_CLONES;
- core->plst = ANC_CLONES - 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);
-
-#if ANC_HALF == 1
- anc_size -= U64_HALF;
-#endif
-
- arch_anc_init(core, anc_size);
-}
-#endif
-
-#if ACTION == ACT_LOAD
-void core_load(FILE *f, Core *core) {
- assert(f);
- assert(core);
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-result"
- 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);
-#pragma GCC diagnostic pop
-
- 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);
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-result"
- 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);
-#pragma GCC diagnostic pop
-}
-#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]);
- }
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-result"
- fread(&g_steps, sizeof(u64), 1, f);
- fread(&g_syncs, sizeof(u64), 1, f);
-#pragma GCC diagnostic pop
-
- 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
deleted file mode 100644
index 8f91d9f..0000000
--- a/src/ui/curses.c
+++ /dev/null
@@ -1,944 +0,0 @@
-// 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
deleted file mode 100644
index f74713d..0000000
--- a/src/ui/daemon.c
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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;
-}
diff --git a/test/anc/v1/1rops.asm b/test/anc/v1/1rops.asm
new file mode 100644
index 0000000..e717aef
--- /dev/null
+++ b/test/anc/v1/1rops.asm
@@ -0,0 +1,105 @@
+incn
+incn
+nop0
+incn
+nop1
+nop2
+incn
+nop2
+nop3
+nop0
+incn
+nop3
+
+noop
+
+decn
+decn
+nop0
+decn
+nop1
+nop2
+decn
+nop2
+nop3
+nop0
+decn
+nop3
+
+noop
+
+incn
+incn
+incn
+notn
+notn
+nop0
+notn
+nop1
+nop2
+notn
+nop2
+nop3
+nop0
+notn
+nop3
+
+noop
+
+shfl
+shfl
+nop0
+shfl
+nop1
+nop2
+shfl
+nop2
+nop3
+nop0
+shfl
+nop3
+
+noop
+
+shfr
+shfr
+nop0
+shfr
+nop1
+nop2
+shfr
+nop2
+nop3
+nop0
+shfr
+nop3
+
+noop
+
+zero
+zero
+nop0
+zero
+nop1
+nop2
+zero
+nop2
+nop3
+nop0
+zero
+nop3
+
+noop
+
+unit
+unit
+nop0
+unit
+nop1
+nop2
+unit
+nop2
+nop3
+nop0
+unit
+nop3
diff --git a/test/anc/v1/2rops.asm b/test/anc/v1/2rops.asm
new file mode 100644
index 0000000..4060285
--- /dev/null
+++ b/test/anc/v1/2rops.asm
@@ -0,0 +1,22 @@
+incn
+incn
+incn
+
+noop
+
+dupl
+nop0
+nop1
+dupl
+nop2
+dupl
+nop1
+nop3
+
+noop
+
+swap
+nop1
+swap
+nop3
+nop2
diff --git a/test/anc/v1/3rops.asm b/test/anc/v1/3rops.asm
new file mode 100644
index 0000000..b8b2895
--- /dev/null
+++ b/test/anc/v1/3rops.asm
@@ -0,0 +1,81 @@
+incn
+addn
+addn
+nop0
+addn
+nop1
+nop0
+addn
+nop2
+nop1
+nop0
+addn
+nop3
+nop2
+nop1
+
+noop
+
+subn
+nop3
+nop2
+nop1
+subn
+nop3
+nop2
+subn
+nop3
+subn
+nop1
+nop2
+subn
+
+noop
+
+incn
+incn
+muln
+muln
+nop0
+muln
+nop1
+nop0
+muln
+nop2
+nop1
+nop0
+muln
+nop3
+nop2
+nop1
+
+noop
+
+divn
+nop3
+nop2
+nop1
+divn
+nop3
+nop2
+divn
+nop3
+divn
+nop1
+nop2
+divn
+
+noop
+
+; div by zero == noop
+zero
+divn
+divn
+nop1
+divn
+nop2
+nop1
+divn
+nop3
+nop2
+nop0
diff --git a/test/anc/v1/addrs.asm b/test/anc/v1/addrs.asm
new file mode 100644
index 0000000..5031007
--- /dev/null
+++ b/test/anc/v1/addrs.asm
@@ -0,0 +1,71 @@
+lokb
+lokd
+lokf
+lokh
+lokj
+lokl
+lokn
+lokp
+
+noop
+noop
+
+adrf
+keya
+adrb
+keyb
+nop1
+adrf
+keyc
+nop2
+adrb
+keyd
+nop3
+adrf
+keye
+nop0
+adrb
+keyf
+nop1
+adrf
+keyg
+nop2
+adrb
+keyh
+nop3
+adrf
+keyi
+nop0
+adrb
+keyj
+nop1
+adrf
+keyk
+nop2
+adrb
+keyl
+nop3
+adrf
+keym
+nop0
+adrb
+keyn
+nop1
+adrf
+keyo
+nop2
+adrb
+keyp
+nop3
+
+noop
+noop
+
+loka
+lokc
+loke
+lokg
+loki
+lokk
+lokm
+loko
diff --git a/test/anc/v1/allos.asm b/test/anc/v1/allos.asm
new file mode 100644
index 0000000..4c04a0f
--- /dev/null
+++ b/test/anc/v1/allos.asm
@@ -0,0 +1,27 @@
+incn
+shfl
+shfl
+
+allf
+nop0
+nop1
+allf
+allb
+splt
+splt
+splt
+
+allf
+nop0
+nop1
+allb
+allf
+splt
+splt
+
+allb
+nop0
+nop1
+allb
+allf
+splt
diff --git a/test/anc/v1/ifnzs.asm b/test/anc/v1/ifnzs.asm
new file mode 100644
index 0000000..a40f4dc
--- /dev/null
+++ b/test/anc/v1/ifnzs.asm
@@ -0,0 +1,46 @@
+ifnz
+incn
+incn
+ifnz
+nop0
+incn
+nop0
+
+noop
+
+ifnz
+nop1
+incn
+nop1
+incn
+nop1
+ifnz
+nop1
+incn
+nop1
+
+noop
+
+ifnz
+nop2
+incn
+nop2
+incn
+nop2
+ifnz
+nop2
+incn
+nop2
+
+noop
+
+ifnz
+nop3
+incn
+nop3
+incn
+nop3
+ifnz
+nop3
+incn
+nop3
diff --git a/test/anc/v1/ioops.asm b/test/anc/v1/ioops.asm
new file mode 100644
index 0000000..539715e
--- /dev/null
+++ b/test/anc/v1/ioops.asm
@@ -0,0 +1,100 @@
+keya
+lokb
+keyc
+lokd
+keye
+lokf
+keyg
+lokh
+
+noop
+
+incn
+nop1
+incn
+nop2
+incn
+nop2
+incn
+nop3
+incn
+nop3
+incn
+nop3
+pshn
+pshn
+nop1
+pshn
+nop2
+pshn
+nop3
+
+noop
+
+load
+load
+nop1
+nop1
+load
+nop2
+nop2
+load
+nop3
+nop3
+
+popn
+nop3
+popn
+nop2
+popn
+nop1
+popn
+pshn
+pshn
+nop1
+pshn
+nop2
+pshn
+nop3
+
+noop
+
+load
+nop2
+nop3
+load
+nop1
+nop2
+load
+nop0
+nop1
+load
+
+noop
+
+muln
+nop0
+nop1
+nop2
+muln
+nop1
+nop2
+nop3
+muln
+nop2
+nop3
+zero
+nop3
+
+wrte
+nop3
+incn
+nop3
+wrte
+nop3
+nop1
+incn
+nop3
+wrte
+nop3
+nop2
diff --git a/test/anc/v1/jumps.asm b/test/anc/v1/jumps.asm
new file mode 100644
index 0000000..0f86eda
--- /dev/null
+++ b/test/anc/v1/jumps.asm
@@ -0,0 +1,52 @@
+jmpf
+keya
+lokb
+jmpf
+keyc
+lokd
+jmpf
+keye
+lokf
+jmpf
+keyg
+lokh
+jmpf
+keyi
+lokj
+jmpf
+keyk
+lokl
+jmpf
+keym
+lokn
+jmpf
+keyo
+lokp
+
+noop
+noop
+
+loka
+jmpb
+keyb
+lokc
+jmpb
+keyd
+loke
+jmpb
+keyf
+lokg
+jmpb
+keyh
+loki
+jmpb
+keyj
+lokk
+jmpb
+keyl
+lokm
+jmpb
+keyn
+loko
+jmpb
+keyp
diff --git a/test/anc/v1/mbops.asm b/test/anc/v1/mbops.asm
new file mode 100644
index 0000000..d586173
--- /dev/null
+++ b/test/anc/v1/mbops.asm
@@ -0,0 +1,13 @@
+incn
+shfl
+shfl
+allf
+
+bswp
+bswp
+bswp
+
+bclr
+
+bswp
+bclr
diff --git a/test/anc/v1/noops.asm b/test/anc/v1/noops.asm
new file mode 100644
index 0000000..77862c4
--- /dev/null
+++ b/test/anc/v1/noops.asm
@@ -0,0 +1,38 @@
+noop
+nop0
+nop1
+nop2
+nop3
+
+keya
+keyb
+keyc
+keyd
+keye
+keyf
+keyg
+keyh
+keyi
+keyj
+keyk
+keyl
+keym
+keyn
+keyo
+keyp
+loka
+lokb
+lokc
+lokd
+loke
+lokf
+lokg
+lokh
+loki
+lokj
+lokk
+lokl
+lokm
+lokn
+loko
+lokp
diff --git a/test/anc/v1/stack.asm b/test/anc/v1/stack.asm
new file mode 100644
index 0000000..54c1e7d
--- /dev/null
+++ b/test/anc/v1/stack.asm
@@ -0,0 +1,47 @@
+incn
+incn
+nop1
+shfl
+nop1
+incn
+nop2
+shfl
+nop2
+shfl
+nop2
+
+noop
+
+pshn
+pshn
+nop1
+pshn
+nop2
+pshn
+nop3
+pshn
+nop0
+pshn
+nop1
+pshn
+nop2
+pshn
+nop3
+
+noop
+
+popn
+popn
+nop1
+popn
+nop2
+popn
+nop3
+popn
+nop0
+popn
+nop1
+popn
+nop2
+popn
+nop3
diff --git a/test/test_build.py b/test/test_build.py
new file mode 100644
index 0000000..a57c09d
--- /dev/null
+++ b/test/test_build.py
@@ -0,0 +1,80 @@
+import signal
+
+from test_general import SalisTest
+
+class TestBuild(SalisTest):
+ def cmds_new(self, anc, arch):
+ return (
+ f"./salis.py new -a{anc} -b -f -g{compiler} -H{self.tempdir.name} {optimized} -s{seed} -u{ui} -v{arch}"
+ for compiler in ["clang", "gcc"]
+ for optimized in ["", "-o"]
+ for seed in [-1, 0, 1234]
+ for ui in ["curses", "daemon"]
+ )
+
+ def cmds_new_null(self):
+ return self.cmds_new("0123", "null")
+
+ def cmds_new_v1(self):
+ return self.cmds_new("55a", "v1")
+
+ def cmds_load(self):
+ return (
+ f"./salis.py load -b -g{compiler} -H{self.tempdir.name} {optimized} -u{ui}"
+ for compiler in ["clang", "gcc"]
+ for optimized in ["", "-o"]
+ for ui in ["curses", "daemon"]
+ )
+
+ def cmds_server(self):
+ return (
+ f"./salis.py server -b -g{compiler} -H{self.tempdir.name} {optimized}"
+ for compiler in ["clang", "gcc"]
+ for optimized in ["", "-o"]
+ )
+
+ def cmds_client(self):
+ return (
+ f"./salis.py client -b -g{c_compiler} {optimized} -x{cxx_compiler}"
+ for c_compiler in ["clang", "gcc"]
+ for cxx_compiler in ["clang", "gcc"]
+ for optimized in ["", "-o"]
+ )
+
+ def test_null_new(self):
+ for cmd in self.cmds_new_null():
+ SalisTest.run_subprocess(cmd)
+
+ def test_v1_new(self):
+ for cmd in self.cmds_new_v1():
+ SalisTest.run_subprocess(cmd)
+
+ def test_null_load(self):
+ SalisTest.run_subprocess(next(self.cmds_new_null()))
+
+ for cmd in self.cmds_load():
+ SalisTest.run_subprocess(cmd)
+
+ def test_v1_load(self):
+ SalisTest.run_subprocess(next(self.cmds_new_v1()))
+
+ for cmd in self.cmds_load():
+ SalisTest.run_subprocess(cmd)
+
+ def test_server(self):
+ SalisTest.run_subprocess(next(self.cmds_new_null()))
+
+ for cmd in self.cmds_server():
+ SalisTest.run_subprocess(cmd)
+
+ @SalisTest.run_ui_test(anc="0123", anc_path="anc", name="def.sim", ui="null", ui_path="test/ui", vm_arch="null")
+ def test_client(self):
+ with SalisTest.run_pipe(f"./salis.py server -H{self.tempdir.name}") as server_proc:
+ for line in server_proc.stdout:
+ if "Listening..." in line:
+ break
+
+ for cmd in self.cmds_client():
+ SalisTest.run_subprocess(cmd)
+
+ server_proc.send_signal(signal.SIGINT)
diff --git a/test/test_general.py b/test/test_general.py
new file mode 100644
index 0000000..eb3185d
--- /dev/null
+++ b/test/test_general.py
@@ -0,0 +1,31 @@
+import subprocess
+import unittest
+
+from tempfile import TemporaryDirectory
+
+class SalisTest(unittest.TestCase):
+ def setUp(self):
+ self.tempdir = TemporaryDirectory(prefix="salis_test_")
+
+ def tearDown(self):
+ self.tempdir.cleanup()
+
+ @staticmethod
+ def run_subprocess(cmd):
+ subprocess.run(cmd.split(), check=True, stdout=subprocess.DEVNULL)
+
+ @staticmethod
+ def run_pipe(cmd):
+ return subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, text=True)
+
+ @staticmethod
+ def run_ui_test(anc, anc_path, ui, ui_path, vm_arch, clones=1, cores=1, name="def.sim"):
+ def test_decorator(test_case):
+ def test_wrapper(self):
+ cmd = f"./salis.py new -a{anc} -A{anc_path} -C{clones} -c{cores} -H{self.tempdir.name} -n{name} -u{ui} -U{ui_path} -v{vm_arch}"
+ subprocess.run(cmd.split(), check=True, stdout=subprocess.DEVNULL)
+ test_case(self)
+
+ return test_wrapper
+
+ return test_decorator
diff --git a/test/test_v1.py b/test/test_v1.py
new file mode 100644
index 0000000..783dc38
--- /dev/null
+++ b/test/test_v1.py
@@ -0,0 +1,46 @@
+from test_general import SalisTest
+
+class TestV1(SalisTest):
+ @SalisTest.run_ui_test(anc="noops", anc_path="test/anc", ui="noops", ui_path="test/ui/v1", vm_arch="v1")
+ def test_noops(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="jumps", anc_path="test/anc", ui="jumps", ui_path="test/ui/v1", vm_arch="v1")
+ def test_jumps(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="addrs", anc_path="test/anc", ui="addrs", ui_path="test/ui/v1", vm_arch="v1")
+ def test_addrs(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="ifnzs", anc_path="test/anc", ui="ifnzs", ui_path="test/ui/v1", vm_arch="v1")
+ def test_ifnzs(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="allos", anc_path="test/anc", ui="allos", ui_path="test/ui/v1", vm_arch="v1", clones=2)
+ def test_allos(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="mbops", anc_path="test/anc", ui="mbops", ui_path="test/ui/v1", vm_arch="v1")
+ def test_mbops(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="3rops", anc_path="test/anc", ui="3rops", ui_path="test/ui/v1", vm_arch="v1")
+ def test_3rops(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="1rops", anc_path="test/anc", ui="1rops", ui_path="test/ui/v1", vm_arch="v1")
+ def test_1rops(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="stack", anc_path="test/anc", ui="stack", ui_path="test/ui/v1", vm_arch="v1")
+ def test_stack(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="ioops", anc_path="test/anc", ui="ioops", ui_path="test/ui/v1", vm_arch="v1")
+ def test_ioops(self):
+ pass
+
+ @SalisTest.run_ui_test(anc="2rops", anc_path="test/anc", ui="2rops", ui_path="test/ui/v1", vm_arch="v1")
+ def test_2rops(self):
+ pass
diff --git a/test/ui/null/ui.c b/test/ui/null/ui.c
new file mode 100644
index 0000000..5872342
--- /dev/null
+++ b/test/ui/null/ui.c
@@ -0,0 +1,10 @@
+int main(void) {
+#if defined(COMMAND_NEW)
+ salis_init();
+#elif defined(COMMAND_LOAD)
+ salis_load();
+#endif
+ salis_save(SIM_PATH);
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/null/vars.py b/test/ui/null/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/null/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/1rops/ui.c b/test/ui/v1/1rops/ui.c
new file mode 100644
index 0000000..9c96470
--- /dev/null
+++ b/test/ui/v1/1rops/ui.c
@@ -0,0 +1,64 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+int main(void) {
+ salis_init();
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ uint8_t inst = mvec_get_inst(g_cores, g_cores->pvec->ip);
+
+ if (inst != incn && inst != decn && inst != notn && inst != shfl && inst != shfr && inst != zero && inst != unit) {
+ salis_step(1);
+ continue;
+ }
+
+ uint64_t *reg = &g_cores->pvec->r0x;
+
+ switch (mvec_get_inst(g_cores, g_cores->pvec->ip + 1)) {
+ case nop0:
+ reg = &g_cores->pvec->r0x;
+ break;
+ case nop1:
+ reg = &g_cores->pvec->r1x;
+ break;
+ case nop2:
+ reg = &g_cores->pvec->r2x;
+ break;
+ case nop3:
+ reg = &g_cores->pvec->r3x;
+ break;
+ }
+
+ uint64_t reg_val = *reg;
+
+ salis_step(1);
+
+ switch (inst) {
+ case incn:
+ assert(*reg == reg_val + 1);
+ break;
+ case decn:
+ assert(*reg == reg_val - 1);
+ break;
+ case notn:
+ assert(*reg == !reg_val);
+ break;
+ case shfl:
+ assert(*reg == reg_val << 1);
+ break;
+ case shfr:
+ assert(*reg == reg_val >> 1);
+ break;
+ case zero:
+ assert(*reg == 0);
+ break;
+ case unit:
+ assert(*reg == 1);
+ break;
+ }
+ }
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/1rops/vars.py b/test/ui/v1/1rops/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/1rops/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/2rops/ui.c b/test/ui/v1/2rops/ui.c
new file mode 100644
index 0000000..5fcf65f
--- /dev/null
+++ b/test/ui/v1/2rops/ui.c
@@ -0,0 +1,46 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+int main(void) {
+ salis_init();
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ uint8_t inst = mvec_get_inst(g_cores, g_cores->pvec->ip);
+
+ if (inst != dupl && inst != swap) {
+ salis_step(1);
+ continue;
+ }
+
+ uint64_t *regs[2] = {
+ &g_cores->pvec->r0x,
+ &g_cores->pvec->r0x,
+ };
+
+ for (uint64_t i = 0; i < 2; i++) {
+ uint8_t rmod = mvec_get_inst(g_cores, g_cores->pvec->ip + 1 + i);
+ if (rmod == nop0) regs[i] = &g_cores->pvec->r0x;
+ else if (rmod == nop1) regs[i] = &g_cores->pvec->r1x;
+ else if (rmod == nop2) regs[i] = &g_cores->pvec->r2x;
+ else if (rmod == nop3) regs[i] = &g_cores->pvec->r3x;
+ else break;
+ }
+
+ uint64_t reg_vals[2] = {*regs[0], *regs[1]};
+ salis_step(1);
+
+ switch (inst) {
+ case dupl:
+ assert(*regs[1] == reg_vals[0]);
+ break;
+ case swap:
+ assert(*regs[0] == reg_vals[1]);
+ assert(*regs[1] == reg_vals[0]);
+ break;
+ }
+ }
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/2rops/vars.py b/test/ui/v1/2rops/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/2rops/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/3rops/ui.c b/test/ui/v1/3rops/ui.c
new file mode 100644
index 0000000..e4e2d83
--- /dev/null
+++ b/test/ui/v1/3rops/ui.c
@@ -0,0 +1,60 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+int main(void) {
+ salis_init();
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ uint8_t inst = mvec_get_inst(g_cores, g_cores->pvec->ip);
+
+ if (inst != addn && inst != subn && inst != muln && inst != divn) {
+ salis_step(1);
+ continue;
+ }
+
+ uint64_t *regs[3] = {
+ &g_cores->pvec->r0x,
+ &g_cores->pvec->r0x,
+ &g_cores->pvec->r0x,
+ };
+
+ for (uint64_t i = 0; i < 3; i++) {
+ uint8_t rmod = mvec_get_inst(g_cores, g_cores->pvec->ip + 1 + i);
+ if (rmod == nop0) regs[i] = &g_cores->pvec->r0x;
+ else if (rmod == nop1) regs[i] = &g_cores->pvec->r1x;
+ else if (rmod == nop2) regs[i] = &g_cores->pvec->r2x;
+ else if (rmod == nop3) regs[i] = &g_cores->pvec->r3x;
+ else break;
+ }
+
+ uint64_t reg_vals[3] = {*regs[0], *regs[1], *regs[2]};
+ salis_step(1);
+ uint64_t result = *regs[0];
+
+ switch (inst) {
+ case addn:
+ assert(result == reg_vals[1] + reg_vals[2]);
+ break;
+ case subn:
+ assert(result == reg_vals[1] - reg_vals[2]);
+ break;
+ case muln:
+ assert(result == reg_vals[1] * reg_vals[2]);
+ break;
+ case divn:
+ if (reg_vals[2] != 0) {
+ assert(result == reg_vals[1] / reg_vals[2]);
+ } else {
+ assert(*regs[0] == reg_vals[0]);
+ assert(*regs[1] == reg_vals[1]);
+ assert(*regs[2] == reg_vals[2]);
+ }
+
+ break;
+ }
+ }
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/3rops/vars.py b/test/ui/v1/3rops/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/3rops/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/addrs/ui.c b/test/ui/v1/addrs/ui.c
new file mode 100644
index 0000000..8949c3f
--- /dev/null
+++ b/test/ui/v1/addrs/ui.c
@@ -0,0 +1,47 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+void check_found(uint64_t addr, uint64_t lok, uint64_t regi) {
+ assert(mvec_get_inst(g_cores, addr - 1) == (regi % 2 == 0 ? adrf : adrb));
+ assert(mvec_get_inst(g_cores, (&g_cores->pvec->r0x)[regi]) == lok);
+}
+
+int main(void) {
+ salis_init();
+ uint64_t regi = 0;
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ uint8_t inst = mvec_get_inst(g_cores, g_cores->pvec->ip);
+
+ switch (inst) {
+#define CASE(id) \
+ case key##id: \
+ check_found(g_cores->pvec->ip, lok##id, regi); \
+ regi = (regi + 1) % 4; \
+ break;
+ CASE(a)
+ CASE(b)
+ CASE(c)
+ CASE(d)
+ CASE(e)
+ CASE(f)
+ CASE(g)
+ CASE(h)
+ CASE(i)
+ CASE(j)
+ CASE(k)
+ CASE(l)
+ CASE(m)
+ CASE(n)
+ CASE(o)
+ CASE(p)
+#undef CASE
+ }
+
+ salis_step(1);
+ }
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/addrs/vars.py b/test/ui/v1/addrs/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/addrs/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/allos/ui.c b/test/ui/v1/allos/ui.c
new file mode 100644
index 0000000..1a02c30
--- /dev/null
+++ b/test/ui/v1/allos/ui.c
@@ -0,0 +1,90 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+#if CLONES != 2
+#error
+#endif
+
+#define CHILD_SIZE 4
+
+void check_new_proc(void) {
+ const struct Proc *proc = proc_get(g_cores, g_cores->plst);
+ assert(proc->mb0s == CHILD_SIZE);
+ assert(proc->mb1a == 0);
+ assert(proc->mb1s == 0);
+ assert(proc->r0x == 0);
+ assert(proc->r1x == 0);
+ assert(proc->r2x == 0);
+ assert(proc->r3x == 0);
+ assert(proc->s0 == 0);
+ assert(proc->s1 == 0);
+ assert(proc->s2 == 0);
+ assert(proc->s3 == 0);
+ assert(proc->s4 == 0);
+ assert(proc->s5 == 0);
+ assert(proc->s6 == 0);
+ assert(proc->s7 == 0);
+
+ uint64_t birth_addr = (uint64_t)-1;
+
+ switch (g_cores->plst) {
+ case 2:
+ birth_addr = ANC_SIZE;
+ break;
+ case 3:
+ birth_addr = ANC_SIZE + (MVEC_SIZE / 2);
+ break;
+ case 4:
+ birth_addr = ANC_SIZE + CHILD_SIZE;
+ break;
+ case 5:
+ birth_addr = ANC_SIZE + CHILD_SIZE + (MVEC_SIZE / 2);
+ break;
+ case 6:
+ birth_addr = (MVEC_SIZE / 2) - CHILD_SIZE;
+ break;
+ default:
+ assert(false);
+ }
+
+ assert(proc->ip == birth_addr);
+ assert(proc->sp == birth_addr);
+ assert(proc->mb0a == birth_addr);
+}
+
+int main(void) {
+ salis_init();
+
+ while (mvec_is_proc_owner(g_cores, proc_fetch(g_cores, 1)->ip, 1)) {
+ const struct Proc *proc_next = proc_get(g_cores, (g_cores->pcur + 1) % g_cores->pnum);
+ bool will_split = mvec_get_inst(g_cores, proc_next->ip) == splt && proc_next->mb1s;
+ uint64_t pnum = g_cores->pnum;
+
+ salis_step(1);
+
+ if (will_split) {
+ assert(g_cores->pnum == pnum + 1);
+ assert(g_cores->plst == pnum);
+
+ switch (g_cores->pnum) {
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ check_new_proc();
+ break;
+ default:
+ assert(false);
+ }
+ }
+ }
+
+ assert(mvec_is_proc_owner(g_cores, proc_fetch(g_cores, 0)->ip, 0));
+ assert(g_cores->pnum == 7);
+ assert(g_cores->pvec[0].sp > MVEC_SIZE);
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/allos/vars.py b/test/ui/v1/allos/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/allos/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/ifnzs/ui.c b/test/ui/v1/ifnzs/ui.c
new file mode 100644
index 0000000..57e9533
--- /dev/null
+++ b/test/ui/v1/ifnzs/ui.c
@@ -0,0 +1,46 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+int main(void) {
+ salis_init();
+
+ uint64_t next_addr = 0;
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ assert(g_cores->pvec->ip == next_addr);
+
+ if (mvec_get_inst(g_cores, g_cores->pvec->ip) == ifnz) {
+ switch (mvec_get_inst(g_cores, g_cores->pvec->ip + 1)) {
+ case nop0:
+ next_addr += g_cores->pvec->r0x ? 1 : 2;
+ break;
+ case nop1:
+ next_addr += g_cores->pvec->r1x ? 1 : 2;
+ break;
+ case nop2:
+ next_addr += g_cores->pvec->r2x ? 1 : 2;
+ break;
+ case nop3:
+ next_addr += g_cores->pvec->r3x ? 1 : 2;
+ break;
+ default:
+ next_addr += g_cores->pvec->r0x ? 0 : 1;
+ }
+ }
+
+ salis_step(1);
+ next_addr++;
+
+ assert(g_cores->pvec->ip == next_addr);
+ }
+
+ assert(g_cores->pvec->ip == ANC_SIZE);
+ assert(g_cores->pvec->r0x == 2);
+ assert(g_cores->pvec->r1x == 2);
+ assert(g_cores->pvec->r2x == 2);
+ assert(g_cores->pvec->r3x == 2);
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/ifnzs/vars.py b/test/ui/v1/ifnzs/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/ifnzs/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/ioops/ui.c b/test/ui/v1/ioops/ui.c
new file mode 100644
index 0000000..489cb38
--- /dev/null
+++ b/test/ui/v1/ioops/ui.c
@@ -0,0 +1,55 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+int main(void) {
+ salis_init();
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ uint8_t inst = mvec_get_inst(g_cores, g_cores->pvec->ip);
+
+ if (inst != load && inst != wrte) {
+ salis_step(1);
+ continue;
+ }
+
+ uint64_t *regs[2] = {
+ &g_cores->pvec->r0x,
+ &g_cores->pvec->r0x,
+ };
+
+ for (uint64_t i = 0; i < 2; i++) {
+ uint8_t rmod = mvec_get_inst(g_cores, g_cores->pvec->ip + 1 + i);
+ if (rmod == nop0) regs[i] = &g_cores->pvec->r0x;
+ else if (rmod == nop1) regs[i] = &g_cores->pvec->r1x;
+ else if (rmod == nop2) regs[i] = &g_cores->pvec->r2x;
+ else if (rmod == nop3) regs[i] = &g_cores->pvec->r3x;
+ else break;
+ }
+
+ if (g_cores->pvec->sp != *regs[0]) {
+ salis_step(1);
+ continue;
+ }
+
+ uint64_t reg_vals[2] = {*regs[0], *regs[1]};
+
+ assert(g_cores->pvec->sp == *regs[0]);
+ salis_step(1);
+ assert(g_cores->pvec->sp == g_cores->pvec->ip);
+
+ switch (inst) {
+ case load:
+ assert(*regs[1] == (uint64_t)mvec_get_inst(g_cores, reg_vals[0]));
+ break;
+ case wrte:
+ assert(*regs[0] == reg_vals[0]);
+ assert(*regs[1] == reg_vals[1]);
+ assert(mvec_get_inst(g_cores, *regs[0]) == *regs[1] % INST_CAP);
+ break;
+ }
+ }
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/ioops/vars.py b/test/ui/v1/ioops/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/ioops/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/jumps/ui.c b/test/ui/v1/jumps/ui.c
new file mode 100644
index 0000000..61514f5
--- /dev/null
+++ b/test/ui/v1/jumps/ui.c
@@ -0,0 +1,21 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+int main(void) {
+ salis_init();
+
+ uint8_t lok = loka;
+
+ while (lok != lokp) {
+ if (mvec_get_inst(g_cores, g_cores->pvec->ip) == lok) {
+ lok = lok + 1;
+ }
+
+ salis_step(1);
+ assert(g_cores->pvec->ip < ANC_SIZE);
+ }
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/jumps/vars.py b/test/ui/v1/jumps/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/jumps/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/mbops/ui.c b/test/ui/v1/mbops/ui.c
new file mode 100644
index 0000000..8a92d6f
--- /dev/null
+++ b/test/ui/v1/mbops/ui.c
@@ -0,0 +1,36 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+int main(void) {
+ salis_init();
+
+ while (g_cores->pvec->ip < ANC_SIZE) {
+ bool on_swap = mvec_get_inst(g_cores, g_cores->pvec->ip) == bswp;
+ bool on_clear = mvec_get_inst(g_cores, g_cores->pvec->ip) == bclr;
+
+ uint64_t mb0a = g_cores->pvec->mb0a;
+ uint64_t mb0s = g_cores->pvec->mb0s;
+ uint64_t mb1a = g_cores->pvec->mb1a;
+ uint64_t mb1s = g_cores->pvec->mb1s;
+
+ salis_step(1);
+
+ if (on_swap) {
+ if (mb1s) {
+ assert(g_cores->pvec->mb0a == mb1a);
+ assert(g_cores->pvec->mb0s == mb1s);
+ assert(g_cores->pvec->mb1a == mb0a);
+ assert(g_cores->pvec->mb1s == mb0s);
+ }
+ }
+
+ if (on_clear) {
+ assert(g_cores->pvec->mb1a == 0);
+ assert(g_cores->pvec->mb1s == 0);
+ }
+ }
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/mbops/vars.py b/test/ui/v1/mbops/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/mbops/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/noops/ui.c b/test/ui/v1/noops/ui.c
new file mode 100644
index 0000000..230e98d
--- /dev/null
+++ b/test/ui/v1/noops/ui.c
@@ -0,0 +1,114 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+void check_invariants(void) {
+ assert(g_cores->mall == ANC_SIZE);
+ assert(g_cores->muta[0] == 0);
+ assert(g_cores->muta[1] == 0);
+ assert(g_cores->muta[2] == 0);
+ assert(g_cores->muta[3] == 0);
+
+ assert(g_cores->pnum == 1);
+ assert(g_cores->pcap == 1);
+ assert(g_cores->pfst == 0);
+ assert(g_cores->plst == 0);
+ assert(g_cores->pcur == 0);
+ assert(g_cores->psli == 0);
+
+ for (uint64_t i = 0; i < SYNC_INTERVAL; i++) {
+ assert(g_cores->ivav[i] == 0);
+ assert(g_cores->iviv[i] == 0);
+ }
+
+ assert(g_cores->emb1 == 0);
+ assert(g_cores->eliv == 0);
+ assert(g_cores->edea == 0);
+
+ for (uint64_t i = 0; i < MVEC_SIZE; i++) {
+ assert(g_cores->aeva[i] == 0);
+ assert(g_cores->beva[i] == 0);
+ }
+
+ for (uint64_t i = 0; i < INST_COUNT; i++) {
+ assert(g_cores->ipop[i] == 0);
+ assert(g_cores->iwrt[i] == 0);
+ }
+
+ assert(g_cores->wmb0 == 0);
+ assert(g_cores->wmb1 == 0);
+ assert(g_cores->wdea == 0);
+
+ for (uint64_t i = 0; i < MVEC_SIZE; i++) {
+ assert(g_cores->weva[i] == 0);
+ assert(g_cores->xeva[i] == 0);
+ }
+
+ assert(g_cores->pvec->mb0a == 0);
+ assert(g_cores->pvec->mb0s == ANC_SIZE);
+ assert(g_cores->pvec->mb1a == 0);
+ assert(g_cores->pvec->mb1s == 0);
+ assert(g_cores->pvec->r0x == 0);
+ assert(g_cores->pvec->r1x == 0);
+ assert(g_cores->pvec->r2x == 0);
+ assert(g_cores->pvec->r3x == 0);
+ assert(g_cores->pvec->s0 == 0);
+ assert(g_cores->pvec->s1 == 0);
+ assert(g_cores->pvec->s2 == 0);
+ assert(g_cores->pvec->s3 == 0);
+ assert(g_cores->pvec->s4 == 0);
+ assert(g_cores->pvec->s5 == 0);
+ assert(g_cores->pvec->s6 == 0);
+ assert(g_cores->pvec->s7 == 0);
+}
+
+void check_variants(uint64_t step) {
+ assert(g_cores->cycl == step);
+ assert(g_cores->ivpt == step);
+
+ assert(g_cores->emb0 == step);
+
+ for (uint64_t i = 0; i < MVEC_SIZE; i++) {
+ assert(g_cores->eeva[i] == (i < step ? 1 : 0));
+ }
+
+ for (uint64_t i = 0; i < INST_COUNT; i++) {
+ uint64_t exed = 0;
+
+ for (uint64_t j = 0; j < step; j++) {
+ if (mvec_get_inst(g_cores, j) == i) {
+ exed = 1;
+ }
+ }
+
+ assert(g_cores->iexe[i] == exed);
+ }
+
+ assert(g_cores->pvec->ip == step);
+ assert(g_cores->pvec->sp == step);
+}
+
+void check_final(void) {
+ assert(g_cores->pvec->ip == ANC_SIZE);
+ assert(g_cores->pvec->sp == ANC_SIZE);
+}
+
+int main(void) {
+ uint64_t step = 0;
+
+ salis_init();
+ check_invariants();
+ check_variants(step);
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ salis_step(1);
+ step++;
+
+ check_invariants();
+ check_variants(step);
+ }
+
+ check_final();
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/noops/vars.py b/test/ui/v1/noops/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/noops/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/stack/ui.c b/test/ui/v1/stack/ui.c
new file mode 100644
index 0000000..1ad047e
--- /dev/null
+++ b/test/ui/v1/stack/ui.c
@@ -0,0 +1,75 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+int main(void) {
+ salis_init();
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ uint8_t inst = mvec_get_inst(g_cores, g_cores->pvec->ip);
+
+ if (inst != pshn && inst != popn) {
+ salis_step(1);
+ continue;
+ }
+
+ uint64_t *reg = &g_cores->pvec->r0x;
+
+ switch (mvec_get_inst(g_cores, g_cores->pvec->ip + 1)) {
+ case nop0:
+ reg = &g_cores->pvec->r0x;
+ break;
+ case nop1:
+ reg = &g_cores->pvec->r1x;
+ break;
+ case nop2:
+ reg = &g_cores->pvec->r2x;
+ break;
+ case nop3:
+ reg = &g_cores->pvec->r3x;
+ break;
+ }
+
+ uint64_t reg_val = *reg;
+ uint64_t stack_val[8] = {
+ g_cores->pvec->s0,
+ g_cores->pvec->s1,
+ g_cores->pvec->s2,
+ g_cores->pvec->s3,
+ g_cores->pvec->s4,
+ g_cores->pvec->s5,
+ g_cores->pvec->s6,
+ g_cores->pvec->s7,
+ };
+
+ salis_step(1);
+
+ switch (inst) {
+ case pshn:
+ assert(*reg == reg_val);
+ assert(g_cores->pvec->s0 == *reg);
+ assert(g_cores->pvec->s1 == stack_val[0]);
+ assert(g_cores->pvec->s2 == stack_val[1]);
+ assert(g_cores->pvec->s3 == stack_val[2]);
+ assert(g_cores->pvec->s4 == stack_val[3]);
+ assert(g_cores->pvec->s5 == stack_val[4]);
+ assert(g_cores->pvec->s6 == stack_val[5]);
+ assert(g_cores->pvec->s7 == stack_val[6]);
+ break;
+ case popn:
+ assert(*reg == stack_val[0]);
+ assert(g_cores->pvec->s0 == stack_val[1]);
+ assert(g_cores->pvec->s1 == stack_val[2]);
+ assert(g_cores->pvec->s2 == stack_val[3]);
+ assert(g_cores->pvec->s3 == stack_val[4]);
+ assert(g_cores->pvec->s4 == stack_val[5]);
+ assert(g_cores->pvec->s5 == stack_val[6]);
+ assert(g_cores->pvec->s6 == stack_val[7]);
+ assert(g_cores->pvec->s7 == 0);
+ break;
+ }
+ }
+
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/stack/vars.py b/test/ui/v1/stack/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/stack/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/todo.adoc b/todo.adoc
deleted file mode 100644
index 0011c6a..0000000
--- a/todo.adoc
+++ /dev/null
@@ -1,5 +0,0 @@
-= TODO
-Paul Oliver <contact@pauloliver.dev>
-
-== Architectures
-. salis-v3
diff --git a/ui/curses/ui.c b/ui/curses/ui.c
new file mode 100644
index 0000000..ace7bf9
--- /dev/null
+++ b/ui/curses/ui.c
@@ -0,0 +1,1398 @@
+// index
+// [section] includes
+// [section] macros & enums
+// [section] globals
+// [section] gfx functions
+// [section] tui functions
+// [section] process page functions
+// [section] world page functions
+// [section] ipc page functions
+// [section] log page functions
+// [section] main print functions
+// [section] event functions
+// [section] main functions
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <curses.h>
+#include <locale.h>
+
+// ----------------------------------------------------------------------------
+// [section] macros & enums
+// ----------------------------------------------------------------------------
+#define LOG_LINE_COUNT 1024
+#define PANE_AND_MARGIN_WIDTH (PANE_WIDTH + 2)
+#define PANE_WIDTH 27
+#define PROC_FIELD_WIDTH 21
+#define PROC_PAGE_LINES 12
+
+#define CTRL(x) (x & 0x1f)
+
+#if defined(NDEBUG)
+#define MIN_FPS 30
+#define MAX_FPS 60
+#else
+#define MIN_FPS 5
+#define MAX_FPS 10
+#endif
+
+enum {
+ PAGE_CORE,
+ PAGE_PROCESS,
+ PAGE_WORLD,
+ PAGE_IPC,
+ PAGE_LOG,
+ PAGE_COUNT,
+};
+
+enum {
+ PAIR_NORMAL = 0,
+ PAIR_HEADER = 1,
+ PAIR_LIVE_PROC = 2,
+ PAIR_SELECTED_PROC = 3,
+ PAIR_FREE_CELL = 4,
+ PAIR_ALLOC_CELL = 5,
+ PAIR_MEM_BLOCK_START = 6,
+ PAIR_SELECTED_MB1 = 7,
+ PAIR_SELECTED_MB2 = 8,
+ PAIR_SELECTED_IP = 9,
+ PAIR_SELECTED_SP = 10,
+ PAIR_INFO = 11,
+ PAIR_WARN = 12,
+};
+
+// ----------------------------------------------------------------------------
+// [section] globals
+// ----------------------------------------------------------------------------
+uint64_t g_gfx_vsiz; // zoom level
+uint64_t *g_gfx_inst; // instruction channel
+uint64_t *g_gfx_mall; // allocated state channel
+uint64_t *g_gfx_mbst; // memory block start channel
+uint64_t *g_gfx_mb0s; // selected organism's memory block #1 channel
+uint64_t *g_gfx_mb1s; // selected organism's memory block #2 channel
+uint64_t *g_gfx_ipas; // selected organism's IP channel
+uint64_t *g_gfx_spas; // selected organism's SP channel
+
+bool g_exit;
+bool g_running;
+unsigned g_core;
+unsigned g_page;
+char *g_line_buff;
+bool g_proc_genes;
+uint64_t g_proc_scroll;
+uint64_t g_proc_field_scroll;
+uint64_t g_proc_gene_scroll;
+uint64_t g_proc_selected;
+uint64_t g_wrld_pos;
+uint64_t g_wrld_zoom;
+bool g_wcursor_mode;
+int g_wcursor_x;
+int g_wcursor_y;
+uint64_t g_wcursor_pointed;
+uint64_t g_log_cnt;
+unsigned g_log_ptr;
+unsigned g_log_scroll;
+enum LogLevel g_log_levels[LOG_LINE_COUNT];
+char g_logs[LOG_LINE_COUNT][LOG_LINE_SIZE];
+uint64_t g_vlin;
+uint64_t g_vsiz;
+uint64_t g_vlin_rng;
+uint64_t g_vsiz_rng;
+uint64_t g_ivpt_scroll;
+uint64_t g_step_block;
+float g_steps_per_sec;
+
+const wchar_t *g_zoomed_symbols = (
+ L"⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟"
+ L"⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿"
+ L"⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟"
+ L"⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿"
+);
+
+// ----------------------------------------------------------------------------
+// [section] gfx functions
+// ----------------------------------------------------------------------------
+void gfx_init(uint64_t vsiz) {
+ assert(vsiz);
+
+ g_gfx_vsiz = vsiz;
+
+ g_gfx_inst = calloc(g_gfx_vsiz, sizeof(uint64_t));
+ g_gfx_mall = calloc(g_gfx_vsiz, sizeof(uint64_t));
+ g_gfx_mbst = calloc(g_gfx_vsiz, sizeof(uint64_t));
+ g_gfx_mb0s = calloc(g_gfx_vsiz, sizeof(uint64_t));
+ g_gfx_mb1s = calloc(g_gfx_vsiz, sizeof(uint64_t));
+ g_gfx_ipas = calloc(g_gfx_vsiz, sizeof(uint64_t));
+ g_gfx_spas = calloc(g_gfx_vsiz, sizeof(uint64_t));
+
+ 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(void) {
+ 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(uint64_t vsiz) {
+ assert(vsiz);
+
+ gfx_free();
+ gfx_init(vsiz);
+}
+
+void gfx_render_inst(const struct Core *core, uint64_t pos, uint64_t zoom) {
+ assert(core);
+
+ for (uint64_t i = 0; i < g_gfx_vsiz; ++i) {
+ g_gfx_inst[i] = 0;
+ g_gfx_mall[i] = 0;
+
+ for (uint64_t j = 0; j < zoom; ++j) {
+ uint64_t addr = pos + (i * zoom) + j;
+
+ g_gfx_inst[i] += mvec_get_byte(core, addr);
+ g_gfx_mall[i] += mvec_is_alloc(core, addr) ? 1 : 0;
+ }
+ }
+}
+
+void gfx_clear_array(uint64_t *arry) {
+ assert(arry);
+ memset(arry, 0, g_gfx_vsiz * sizeof(uint64_t));
+}
+
+#if defined(MVEC_LOOP)
+void gfx_accumulate_pixel(uint64_t pos, uint64_t zoom, uint64_t pixa, uint64_t *arry) {
+ assert(arry);
+
+ uint64_t beg_mod = pos % MVEC_SIZE;
+ uint64_t end_mod = beg_mod + (g_gfx_vsiz * zoom);
+ uint64_t pix_mod = pixa % MVEC_SIZE;
+
+#if !defined(NDEBUG)
+ uint64_t inc_cnt = 0;
+#endif
+
+ while (pix_mod < end_mod) {
+ if (pix_mod >= beg_mod && pix_mod < end_mod) {
+ uint64_t pixi = (pix_mod - beg_mod) / zoom;
+ assert(pixi < g_gfx_vsiz);
+ arry[pixi]++;
+
+#if !defined(NDEBUG)
+ inc_cnt++;
+#endif
+ }
+
+ pix_mod += MVEC_SIZE;
+ }
+
+#if !defined(NDEBUG)
+ if (zoom != 1) {
+ assert(inc_cnt <= 2);
+ }
+#endif
+}
+#endif
+
+#if !defined(MVEC_LOOP)
+void gfx_accumulate_pixel(uint64_t pos, uint64_t zoom, uint64_t pixa, uint64_t *arry) {
+ assert(arry);
+
+ uint64_t end = pos + (g_gfx_vsiz * zoom);
+
+ if (pixa < pos || pixa >= end) {
+ return;
+ }
+
+ uint64_t pixi = (pixa - pos) / zoom;
+ assert(pixi < g_gfx_vsiz);
+ arry[pixi]++;
+}
+#endif
+
+void gfx_render_mbst(const struct Core *core, uint64_t pos, uint64_t zoom) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_mbst);
+
+ for (uint64_t pix = core->pfst; pix <= core->plst; ++pix) {
+ uint64_t mb0a = arch_proc_mb0_addr(core, pix);
+ uint64_t mb1a = arch_proc_mb1_addr(core, pix);
+
+ gfx_accumulate_pixel(pos, zoom, mb0a, g_gfx_mbst);
+
+ if (arch_proc_mb1_size(core, pix)) {
+ gfx_accumulate_pixel(pos, zoom, mb1a, g_gfx_mbst);
+ }
+ }
+}
+
+void gfx_render_mb0s(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_mb0s);
+
+ if (psel < core->pfst || psel > core->plst) {
+ return;
+ }
+
+ uint64_t mb0a = arch_proc_mb0_addr(core, psel);
+ uint64_t mb0s = arch_proc_mb0_size(core, psel);
+
+ for (uint64_t i = 0; i < mb0s; ++i) {
+ gfx_accumulate_pixel(pos, zoom, mb0a + i, g_gfx_mb0s);
+ }
+}
+
+void gfx_render_mb1s(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_mb1s);
+
+ if (psel < core->pfst || psel > core->plst) {
+ return;
+ }
+
+ uint64_t mb1a = arch_proc_mb1_addr(core, psel);
+ uint64_t mb1s = arch_proc_mb1_size(core, psel);
+
+ for (uint64_t i = 0; i < mb1s; ++i) {
+ gfx_accumulate_pixel(pos, zoom, mb1a + i, g_gfx_mb1s);
+ }
+}
+
+void gfx_render_ipas(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_ipas);
+
+ if (psel < core->pfst || psel > core->plst) {
+ return;
+ }
+
+ uint64_t ipa = arch_proc_ip_addr(core, psel);
+
+ gfx_accumulate_pixel(pos, zoom, ipa, g_gfx_ipas);
+}
+
+void gfx_render_spas(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) {
+ assert(core);
+
+ gfx_clear_array(g_gfx_spas);
+
+ if (psel < core->pfst || psel > core->plst) {
+ return;
+ }
+
+ uint64_t spa = arch_proc_sp_addr(core, psel);
+
+ gfx_accumulate_pixel(pos, zoom, spa, g_gfx_spas);
+}
+
+void gfx_render(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t 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);
+}
+
+// ----------------------------------------------------------------------------
+// [section] tui functions
+// ----------------------------------------------------------------------------
+void tui_line_buff_free(void) {
+ if (g_line_buff) {
+ free(g_line_buff);
+ }
+
+ g_line_buff = NULL;
+}
+
+void tui_line_buff_resize(void) {
+ tui_line_buff_free();
+
+ g_line_buff = calloc(COLS + 1, sizeof(char));
+}
+
+void tui_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, "%s", g_line_buff);
+
+ va_end(args);
+ attroff(COLOR_PAIR(color) | attr);
+}
+
+void tui_clear_line(int l) {
+ tui_line(true, l, PAIR_NORMAL, A_NORMAL, "");
+}
+
+void tui_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, "%s", g_line_buff);
+
+ va_end(args);
+ attroff(COLOR_PAIR(color) | attr);
+}
+
+void tui_str_field(int l, const char *label, const char *value) {
+ assert(label);
+ assert(strlen(label) <= 4);
+ assert(value);
+ tui_line(false, l, PAIR_NORMAL, A_NORMAL, "%-4s : %18s", label, value);
+}
+
+void tui_ulx_field(int l, const char *label, uint64_t value) {
+ assert(label);
+ assert(strlen(label) <= 4);
+ tui_line(false, l, PAIR_NORMAL, A_NORMAL, "%-4s : %#18lx", label, value);
+}
+
+void tui_uld_field(int l, const char *label, uint64_t value) {
+ assert(label);
+ assert(strlen(label) <= 4);
+ tui_line(false, l, PAIR_NORMAL, A_NORMAL, "%-4s : %#18ld", label, value);
+}
+
+void tui_print_core(int l) {
+ tui_line(false, ++l, PAIR_HEADER, A_BOLD, "CORE [%d]", g_core);
+ tui_ulx_field(++l, "cycl", g_cores[g_core].cycl);
+ tui_ulx_field(++l, "mall", g_cores[g_core].mall);
+ tui_ulx_field(++l, "mut0", g_cores[g_core].muta[0]);
+ tui_ulx_field(++l, "mut1", g_cores[g_core].muta[1]);
+ tui_ulx_field(++l, "mut2", g_cores[g_core].muta[2]);
+ tui_ulx_field(++l, "mut3", g_cores[g_core].muta[3]);
+ tui_ulx_field(++l, "pnum", g_cores[g_core].pnum);
+ tui_ulx_field(++l, "pcap", g_cores[g_core].pcap);
+ tui_ulx_field(++l, "pfst", g_cores[g_core].pfst);
+ tui_ulx_field(++l, "plst", g_cores[g_core].plst);
+ tui_ulx_field(++l, "pcur", g_cores[g_core].pcur);
+ tui_ulx_field(++l, "psli", g_cores[g_core].psli);
+ tui_ulx_field(++l, "ivpt", g_cores[g_core].ivpt);
+
+ ++l;
+
+#if CORE_FIELD_COUNT != 0
+ tui_line(false, ++l, PAIR_HEADER, A_BOLD, "ARCH SPECIFIC");
+
+#define CORE_FIELD(type, name, suffix) tui_ulx_field(++l, #name, (uint64_t)g_cores[g_core].name);
+ CORE_FIELDS
+#undef CORE_FIELD
+#endif
+}
+
+// ----------------------------------------------------------------------------
+// [section] process page functions
+// ----------------------------------------------------------------------------
+int tui_proc_pair(uint64_t pix) {
+ if (pix == g_proc_selected) {
+ return PAIR_SELECTED_PROC;
+ } else if (mvec_proc_is_live(&g_cores[g_core], pix)) {
+ return PAIR_LIVE_PROC;
+ } else {
+ return PAIR_NORMAL;
+ }
+}
+
+const char *tui_proc_state(uint64_t pix) {
+ return mvec_proc_is_live(&g_cores[g_core], pix) ? "live" : "dead";
+}
+
+void tui_print_process_genome_header(int l) {
+ tui_line(false, l++, PAIR_NORMAL, A_NORMAL, "%s : %18s : %s", "stat", "pix", "genome");
+}
+
+void tui_print_process_gene(int l, int gcol, uint64_t gidx, uint64_t mba, uint64_t pix, int pair) {
+ assert(gcol >= PANE_AND_MARGIN_WIDTH);
+ assert(gcol < COLS);
+ assert(mvec_proc_is_live(&g_cores[g_core], pix));
+ assert(pair == PAIR_SELECTED_MB1 || pair == PAIR_SELECTED_MB2);
+
+ const struct Core *core = &g_cores[g_core];
+
+ uint64_t addr = mba + gidx;
+ uint8_t 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 tui_print_process_genes(int l, uint64_t pix) {
+ tui_line(true, l, tui_proc_pair(pix), A_NORMAL, "%s : %#18lx :", tui_proc_state(pix), pix);
+
+ if (!mvec_proc_is_live(&g_cores[g_core], pix)) {
+ return;
+ }
+
+ const struct Core *core = &g_cores[g_core];
+
+ int scol = PANE_AND_MARGIN_WIDTH;
+ int gcol = scol - g_proc_gene_scroll;
+ uint64_t mb0a = arch_proc_mb0_addr(core, pix);
+ uint64_t mb0s = arch_proc_mb0_size(core, pix);
+ uint64_t mb1a = arch_proc_mb1_addr(core, pix);
+ uint64_t mb1s = arch_proc_mb1_size(core, pix);
+
+ for (uint64_t gidx = 0; gidx < mb0s && gcol < COLS; ++gidx, ++gcol) {
+ if (gcol >= scol) {
+ tui_print_process_gene(l, gcol, gidx, mb0a, pix, PAIR_SELECTED_MB1);
+ }
+ }
+
+ for (uint64_t gidx = 0; gidx < mb1s && gcol < COLS; ++gidx, ++gcol) {
+ if (gcol >= scol) {
+ tui_print_process_gene(l, gcol, gidx, mb1a, pix, PAIR_SELECTED_MB2);
+ }
+ }
+
+ clrtoeol();
+}
+
+void tui_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;
+
+ tui_field(l, fcol, PAIR_NORMAL, A_NORMAL, " : %18s", name);
+}
+
+void tui_print_process_field_header(int l) {
+ tui_line(true, l, PAIR_NORMAL, A_NORMAL, "%s : %18s", "stat", "pix");
+
+ int fidx = 0;
+
+#define PROC_FIELD(type, name) tui_print_process_field_header_element(l, fidx++, #name);
+ PROC_FIELDS
+#undef PROC_FIELD
+}
+
+void tui_print_process_field_element(int l, int fidx, int fclr, uint64_t 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;
+
+ tui_field(l, fcol, fclr, A_NORMAL, " : %#18lx", field);
+}
+
+void tui_print_process_fields(int l, uint64_t pix) {
+ tui_line(true, l, tui_proc_pair(pix), A_NORMAL, "%s : %#18lx", tui_proc_state(pix), pix);
+
+ const struct Proc *proc = proc_get(&g_cores[g_core], pix);
+
+ int fidx = 0;
+ int fclr = tui_proc_pair(pix);
+
+#define PROC_FIELD(type, name) tui_print_process_field_element(l, fidx++, fclr, proc->name);
+ PROC_FIELDS
+#undef PROC_FIELD
+}
+
+void tui_print_process(int l) {
+ l++;
+
+ tui_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
+ );
+
+ uint64_t pix = g_proc_scroll;
+
+ if (g_proc_genes) {
+ tui_print_process_genome_header(l++);
+
+ while (l < LINES) {
+ tui_print_process_genes(l++, pix++);
+ }
+ } else {
+ tui_print_process_field_header(l++);
+
+ while (l < LINES) {
+ tui_print_process_fields(l++, pix++);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] world page functions
+// ----------------------------------------------------------------------------
+void tui_world_resize(void) {
+ 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);
+ }
+}
+
+#if defined(MVEC_LOOP)
+void tui_print_cell(uint64_t i, uint64_t r, uint64_t x, uint64_t y) {
+#else
+void tui_print_cell(uint64_t i, uint64_t r, uint64_t x, uint64_t y, uint64_t a) {
+#endif
+ wchar_t inst_nstr[2] = { L'\0', L'\0' };
+ cchar_t cchar = { 0 };
+ uint64_t inst_avrg = g_gfx_inst[i] / g_wrld_zoom;
+
+ if (g_wrld_zoom == 1) {
+ inst_nstr[0] = arch_symbol((uint8_t)inst_avrg);
+ } else {
+ inst_nstr[0] = g_zoomed_symbols[(uint8_t)inst_avrg];
+ }
+
+ int pair_cell;
+
+#if defined(MVEC_LOOP)
+ if (g_wcursor_mode && r == (uint64_t)g_wcursor_x && y == (uint64_t)g_wcursor_y) {
+#else
+ if (a >= MVEC_SIZE || (g_wcursor_mode && r == (uint64_t)g_wcursor_x && y == (uint64_t)g_wcursor_y)) {
+#endif
+ pair_cell = PAIR_NORMAL;
+ } else if (g_wcursor_mode && r == (uint64_t)g_wcursor_x && y == (uint64_t)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 tui_print_wcursor_bar(void) {
+ tui_clear_line(LINES - 1);
+
+ const struct Core *core = &g_cores[g_core];
+
+ char cownr[PROC_FIELD_WIDTH];
+
+ uint64_t cpos = g_vlin * g_wcursor_y + g_wcursor_x;
+ uint64_t caddr = cpos * g_wrld_zoom + g_wrld_pos;
+ uint8_t cbyte = mvec_get_byte(core, caddr);
+
+ if (caddr < MVEC_SIZE && 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 = (uint64_t)(-1);
+ snprintf(cownr, PROC_FIELD_WIDTH, "-");
+ }
+
+ 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],
+ arch_mnemonic(cbyte),
+ cownr
+ );
+}
+
+void tui_print_world(int l) {
+ l++;
+
+ tui_line(false, l++, PAIR_HEADER, A_BOLD, "WORLD");
+ tui_ulx_field(l++, "wrlp", g_wrld_pos);
+ tui_ulx_field(l++, "wrlz", g_wrld_zoom);
+ tui_ulx_field(l++, "psel", g_proc_selected);
+ tui_ulx_field(l++, "pabs", g_proc_selected % g_cores[g_core].pcap);
+ tui_ulx_field(l++, "vrng", g_vsiz_rng);
+ tui_str_field(l++, "curs", g_wcursor_mode ? "on" : "off");
+
+ l++;
+
+ tui_line(false, l++, PAIR_HEADER, A_BOLD, "SELECTED");
+
+ const struct Proc *psel = proc_get(&g_cores[g_core], g_proc_selected);
+
+#define PROC_FIELD(type, name) tui_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 (uint64_t i = 0; i < g_vsiz; ++i) {
+ uint64_t r = i % g_vlin;
+ uint64_t x = r + PANE_WIDTH;
+ uint64_t y = i / g_vlin;
+
+#if defined(MVEC_LOOP)
+ tui_print_cell(i, r, x, y);
+#else
+ uint64_t a = g_wrld_pos + (i * g_wrld_zoom);
+ tui_print_cell(i, r, x, y, a);
+#endif
+ }
+
+ if (g_wcursor_mode) {
+ tui_print_wcursor_bar();
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] ipc page functions
+// ----------------------------------------------------------------------------
+void tui_print_ipc_field(int l, uint64_t i, int color) {
+ uint8_t iinst = g_cores[g_core].iviv[i];
+ uint64_t iaddr = g_cores[g_core].ivav[i];
+
+ tui_field(l, PANE_WIDTH, color, A_NORMAL, "%#18x : %#18x : %#18x", i, iinst, iaddr);
+}
+
+void tui_print_ipc_data(void) {
+ tui_field(0, PANE_WIDTH, PAIR_NORMAL, A_NORMAL, "%18s : %18s : %18s", "ipci", "inst", "addr");
+
+ int l = 1 - g_ivpt_scroll;
+
+ for (uint64_t i = 0; i < SYNC_INTERVAL; ++i) {
+ if (i == g_cores[g_core].ivpt) {
+ if (l >= 1) {
+ tui_print_ipc_field(l++, i, PAIR_SELECTED_PROC);
+ }
+
+ continue;
+ }
+
+ uint8_t iinst = g_cores[g_core].iviv[i];
+
+ if ((iinst & IPC_FLAG) != 0) {
+ if (l >= 1) {
+ tui_print_ipc_field(l++, i, PAIR_LIVE_PROC);
+ }
+
+ continue;
+ }
+ }
+
+ for (; l < LINES; ++l) {
+ if (l >= 1) {
+ move(l, PANE_WIDTH);
+ clrtoeol();
+ }
+ }
+}
+
+void tui_print_ipc(int l) {
+ l++;
+
+ const struct Core *core = &g_cores[g_core];
+
+ tui_line(true, l++, PAIR_HEADER, A_BOLD, "IPC [%#lx]", g_ivpt_scroll);
+ tui_ulx_field(l++, "ivpt", core->ivpt);
+ tui_ulx_field(l++, "ivpi", core->iviv[core->ivpt]);
+ tui_ulx_field(l++, "ivpa", core->ivav[core->ivpt]);
+
+ tui_print_ipc_data();
+}
+
+// ----------------------------------------------------------------------------
+// [section] log page functions
+// ----------------------------------------------------------------------------
+void tui_info_impl(const char *format, ...) {
+ assert(format);
+
+ va_list args;
+ va_start(args, format);
+ log_msg_to_buff(g_logs[g_log_ptr], LOG_LINE_SIZE, INFO, false, format, args);
+ va_end(args);
+
+ g_log_levels[g_log_ptr] = INFO;
+ g_log_cnt++;
+ g_log_ptr = (g_log_ptr + 1) % LOG_LINE_COUNT;
+}
+
+void tui_warn_impl(const char *format, ...) {
+ assert(format);
+
+ va_list args;
+ va_start(args, format);
+ log_msg_to_buff(g_logs[g_log_ptr], LOG_LINE_SIZE, WARN, false, format, args);
+ va_end(args);
+
+ g_log_levels[g_log_ptr] = WARN;
+ g_log_cnt++;
+ g_log_ptr = (g_log_ptr + 1) % LOG_LINE_COUNT;
+}
+
+void tui_clear_log_line(int line) {
+ assert(line >= 0 && line < LINES);
+ move(line, PANE_WIDTH);
+ clrtoeol();
+}
+
+void tui_print_log_line(unsigned lptr, int line) {
+ assert(lptr < LOG_LINE_COUNT);
+ assert(line >= 0 && line < LINES);
+ tui_clear_log_line(line);
+
+ if (strlen(g_logs[lptr])) {
+ tui_field(line, PANE_AND_MARGIN_WIDTH, g_log_levels[lptr] == INFO ? PAIR_INFO : PAIR_WARN, A_NORMAL, "%.38s", g_logs[lptr]);
+ tui_field(line, PANE_AND_MARGIN_WIDTH + 38, PAIR_NORMAL, A_NORMAL, g_logs[lptr] + 38);
+ }
+}
+
+void tui_print_log(int l) {
+ l++;
+
+ tui_line(true, l++, PAIR_HEADER, A_BOLD, "LOG");
+ tui_ulx_field(l++, "lscr", g_log_scroll);
+ tui_ulx_field(l++, "lcnt", g_log_cnt);
+ tui_ulx_field(l++, "lptr", g_log_ptr);
+
+ unsigned lptr = g_log_ptr;
+ int line = LINES + g_log_scroll;
+
+ while (line) {
+ lptr = (lptr - 1 + LOG_LINE_COUNT) % LOG_LINE_COUNT;
+ line--;
+
+ if (line < LINES) {
+ tui_print_log_line(lptr, line);
+ }
+
+ if (lptr == g_log_ptr) {
+ break;
+ }
+ }
+
+ while (line) {
+ line--;
+ tui_clear_log_line(line);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] main print functions
+// ----------------------------------------------------------------------------
+void tui_print(void) {
+ int l = 1;
+
+ tui_line(false, l++, PAIR_HEADER, A_BOLD, "SALIS [%d:%d]", g_core, CORES);
+ tui_str_field(l++, "name", NAME);
+ tui_ulx_field(l++, "seed", SEED);
+ tui_str_field(l++, "anc", ANC);
+ tui_str_field(l++, "arch", ARCH);
+ tui_ulx_field(l++, "asav", AUTOSAVE_INTERVAL);
+#if defined(MUTA_FLIP)
+ tui_str_field(l++, "mflp", "yes");
+#else
+ tui_str_field(l++, "mflp", "no");
+#endif
+ tui_ulx_field(l++, "mrng", MUTA_RANGE);
+ tui_ulx_field(l++, "size", MVEC_SIZE);
+#if defined(MVEC_LOOP)
+ tui_str_field(l++, "loop", "true");
+#else
+ tui_str_field(l++, "loop", "false");
+#endif
+ tui_ulx_field(l++, "syni", SYNC_INTERVAL);
+#if defined(DATA_PUSH_INTERVAL)
+ tui_ulx_field(l++, "dpsi", DATA_PUSH_INTERVAL);
+#endif
+ tui_ulx_field(l++, "step", g_steps);
+ tui_ulx_field(l++, "sync", g_syncs);
+ tui_ulx_field(l++, "step", g_step_block);
+ tui_ulx_field(l++, "stps", (uint64_t)g_steps_per_sec);
+
+ switch (g_page) {
+ case PAGE_CORE:
+ tui_print_core(l);
+ break;
+ case PAGE_PROCESS:
+ tui_print_process(l);
+ break;
+ case PAGE_WORLD:
+ tui_print_world(l);
+ break;
+ case PAGE_IPC:
+ tui_print_ipc(l);
+ break;
+ case PAGE_LOG:
+ tui_print_log(l);
+ break;
+ default:
+ break;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] event functions
+// ----------------------------------------------------------------------------
+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':
+#if defined(MVEC_LOOP)
+ g_wrld_pos -= g_vsiz_rng;
+#else
+ if (g_wrld_pos < g_vsiz_rng) {
+ g_wrld_pos = 0;
+ } else {
+ g_wrld_pos -= g_vsiz_rng;
+ }
+#endif
+ break;
+ case 'w':
+ g_wrld_pos += g_vlin_rng;
+ break;
+ case 's':
+#if defined(MVEC_LOOP)
+ g_wrld_pos -= g_vlin_rng;
+#else
+ if (g_wrld_pos < g_vlin_rng) {
+ g_wrld_pos = 0;
+ } else {
+ g_wrld_pos -= g_vlin_rng;
+ }
+#endif
+ 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 < (uint64_t)LINES ? g_ivpt_scroll : (uint64_t)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;
+ case PAGE_LOG:
+ switch (ev) {
+ case 'W':
+ g_log_scroll += LINES;
+ g_log_scroll = g_log_scroll >= LOG_LINE_COUNT ? LOG_LINE_COUNT - 1 : g_log_scroll;
+ break;
+ case 'S':
+ g_log_scroll -= g_log_scroll < (uint64_t)LINES ? g_log_scroll : (uint64_t)LINES;
+ break;
+ case 'w':
+ g_log_scroll += 1;
+ g_log_scroll = g_log_scroll >= LOG_LINE_COUNT ? LOG_LINE_COUNT - 1 : g_log_scroll;
+ break;
+ case 's':
+ g_log_scroll -= g_log_scroll ? 1 : 0;
+ break;
+ case 'q':
+ g_log_scroll = 0;
+ break;
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+void ev_hscroll(int ev) {
+ switch (g_page) {
+ case PAGE_PROCESS: {
+ uint64_t *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':
+#if defined(MVEC_LOOP)
+ g_wrld_pos -= g_wrld_zoom;
+#else
+ if (g_wrld_pos < g_wrld_zoom) {
+ g_wrld_pos = 0;
+ } else {
+ g_wrld_pos -= g_wrld_zoom;
+ }
+#endif
+ 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;
+ tui_world_resize();
+ break;
+ case 'z':
+ g_wrld_zoom /= (g_wrld_zoom != 1) ? 2 : 1;
+ tui_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 += ((uint64_t)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(void) {
+ 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(void) {
+ 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 != (uint64_t)(-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 + CORES - 1) % CORES;
+ break;
+ case KEY_SRIGHT:
+ clear();
+ g_core = (g_core + 1) % CORES;
+ break;
+ case KEY_LEFT:
+ clear();
+ g_page = (g_page + PAGE_COUNT - 1) % PAGE_COUNT;
+ break;
+ case KEY_RIGHT:
+ clear();
+ g_page = (g_page + 1) % PAGE_COUNT;
+ break;
+ case KEY_RESIZE:
+ clear();
+ tui_line_buff_resize();
+ tui_world_resize();
+
+ if (g_vlin) {
+ while (g_vsiz_rng >= MVEC_SIZE * 2 && g_wrld_zoom != 1) {
+ g_wrld_zoom /= 2;
+ tui_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;
+ g_steps_per_sec = 0.f;
+ 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) {
+ uint64_t cycles = 1 << (((ev - '0') ? (ev - '0') : 10) - 1);
+ salis_step(cycles);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] main functions
+// ----------------------------------------------------------------------------
+void init(void) {
+ 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);
+ init_pair(PAIR_INFO, COLOR_GREEN, COLOR_BLACK);
+ init_pair(PAIR_WARN, COLOR_RED, COLOR_BLACK);
+
+ log_info = tui_info_impl;
+ log_warn = tui_warn_impl;
+
+#if defined(COMMAND_NEW)
+ salis_init();
+#elif defined(COMMAND_LOAD)
+ salis_load();
+#endif
+
+ g_wrld_zoom = 1;
+ g_step_block = 1;
+
+ tui_line_buff_resize();
+ tui_world_resize();
+}
+
+void exec(void) {
+ 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 / MIN_FPS)) {
+ g_step_block <<= 1;
+ }
+
+ if ((end - beg) >= (CLOCKS_PER_SEC / MAX_FPS) && g_step_block != 1) {
+ g_step_block >>= 1;
+ }
+
+ float secs = (float)(end - beg) / (float)CLOCKS_PER_SEC;
+ g_steps_per_sec = (float)g_step_block / secs;
+ }
+
+ tui_print();
+ ev_handle();
+ }
+}
+
+void quit(void) {
+ gfx_free();
+ tui_line_buff_free();
+ salis_save(SIM_PATH);
+ salis_free();
+ endwin();
+}
+
+int main(void) {
+ init();
+ exec();
+ quit();
+
+ return 0;
+}
diff --git a/ui/curses/vars.py b/ui/curses/vars.py
new file mode 100644
index 0000000..d83befa
--- /dev/null
+++ b/ui/curses/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = {"-DNCURSES_WIDECHAR=1"}
+links = {"-lncurses"}
diff --git a/ui/daemon/ui.c b/ui/daemon/ui.c
new file mode 100644
index 0000000..2417788
--- /dev/null
+++ b/ui/daemon/ui.c
@@ -0,0 +1,57 @@
+#include <signal.h>
+
+bool g_running;
+uint64_t g_step_block;
+
+void sig_handler(int signo) {
+ (void)signo;
+
+ if (g_running) {
+ log_warn("Signal received, will stop simulator soon...");
+ g_running = false;
+ }
+}
+
+void step_block(void) {
+ 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;
+ }
+
+ float secs = (float)(end - beg) / (float)CLOCKS_PER_SEC;
+ float steps_per_sec = (float)g_step_block / secs;
+
+ log_info("Simulator running on step %#lx @%#lxf steps/s", g_steps, (int)steps_per_sec);
+}
+
+int main(void) {
+ g_running = true;
+ g_step_block = 1;
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+#if defined(COMMAND_NEW)
+ salis_init();
+#elif defined(COMMAND_LOAD)
+ salis_load();
+#endif
+
+ while (g_running) {
+ step_block();
+ }
+
+ log_info("Saving simulation...");
+ salis_save(SIM_PATH);
+ salis_free();
+
+ log_info("Exiting salis...");
+ return 0;
+}
diff --git a/ui/daemon/vars.py b/ui/daemon/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/ui/daemon/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()