diff options
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 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 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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(¶ms->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(¶ms->strm, Z_FINISH); + + return 0; +} + +void comp_deflate_end(struct DeflateParams *params) { + assert(params); + deflateEnd(¶ms->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(¶ms->strm); + + params->strm.avail_out = params->size; + params->strm.next_out = params->out; + +#if defined(NDEBUG) + inflate(¶ms->strm, Z_FINISH); +#else + assert(inflate(¶ms->strm, Z_FINISH)); +#endif + + return 0; +} + +void comp_inflate_end(struct InflateParams *params) { + assert(params); + inflateEnd(¶ms->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(¶ms); + + 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(¶ms); + + 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(¶ms); + comp_inflate_end(¶ms); + + 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(¶ms); + comp_inflate_end(¶ms); + + 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 Binary files differnew file mode 100644 index 0000000..17d30f4 --- /dev/null +++ b/data.png @@ -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() Binary files differdiff --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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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() |
