summaryrefslogtreecommitdiff
path: root/arch/v1
diff options
context:
space:
mode:
Diffstat (limited to 'arch/v1')
-rw-r--r--arch/v1/arch.c989
-rw-r--r--arch/v1/arch_inst.c40
-rw-r--r--arch/v1/arch_spec.h140
-rw-r--r--arch/v1/client_plots.h83
4 files changed, 1252 insertions, 0 deletions
diff --git a/arch/v1/arch.c b/arch/v1/arch.c
new file mode 100644
index 0000000..ea6ff36
--- /dev/null
+++ b/arch/v1/arch.c
@@ -0,0 +1,989 @@
+// file : arch/v1/arch.c
+// project : Salis-VM
+// author : Paul Oliver <contact@pauloliver.dev>
+//
+// Based on the original VM architecture from Salis-V1.
+// The only addition are the extra core-fields used for data aggregation.
+
+// index:
+// [section] includes
+// [section] getters
+// [section] statics
+// [section] callbacks
+// [section] validation
+// [section] data
+// [section] main
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <assert.h>
+#include <sqlite3.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <threads.h>
+#include <zlib.h>
+
+#include "arch.h"
+#include "arch_spec.h"
+#include "arch_inst.h"
+#include "compress.h"
+#include "logger.h"
+#include "salis.h"
+#include "sql.h"
+
+// ----------------------------------------------------------------------------
+// [section] getters
+// ----------------------------------------------------------------------------
+uint64_t arch_proc_get_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_get_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_get_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_get_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_get_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_get_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_get_slice(const struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ (void)core;
+ (void)pix;
+ return 1;
+}
+
+// ----------------------------------------------------------------------------
+// [section] statics
+// ----------------------------------------------------------------------------
+static 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);
+ assert(proc->ip < MVEC_SIZE);
+ proc->ip++;
+ proc->sp = proc->ip;
+}
+
+static bool is_between(uint8_t inst, uint8_t lo, uint8_t hi) {
+ assert(inst < ARCH_INST_COUNT);
+ assert(lo < ARCH_INST_COUNT);
+ assert(hi < ARCH_INST_COUNT);
+ assert(lo < hi);
+ return (inst >= lo) && (inst <= hi);
+}
+
+static bool is_key(uint8_t inst) {
+ assert(inst < ARCH_INST_COUNT);
+ return is_between(inst, keya, keyp);
+}
+
+#if !defined(NDEBUG)
+static bool is_lock(uint8_t inst) {
+ assert(inst < ARCH_INST_COUNT);
+ return is_between(inst, loka, lokp);
+}
+#endif
+
+static bool is_rmod(uint8_t inst) {
+ assert(inst < ARCH_INST_COUNT);
+ return is_between(inst, nop0, nop3);
+}
+
+static bool key_lock_match(uint8_t key, uint8_t lock) {
+ assert(key < ARCH_INST_COUNT);
+ assert(lock < ARCH_INST_COUNT);
+ assert(is_key(key));
+ return (key - keya) == (lock - loka);
+}
+
+static 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);
+ assert(proc->ip < MVEC_SIZE);
+
+ if (proc->ip + 1 >= MVEC_SIZE) {
+ increment_ip(core, pix);
+ return false;
+ }
+
+ uint8_t next = mvec_get_inst(core, proc->ip + 1);
+
+ if (!is_key(next)) {
+ increment_ip(core, pix);
+ return false;
+ }
+
+ if (proc->sp >= MVEC_SIZE) {
+ // HALT: process SP address is invalid
+ core->hlts++;
+ return false;
+ }
+
+ uint8_t spin = mvec_get_inst(core, proc->sp);
+
+ if (key_lock_match(next, spin)) {
+ return true;
+ }
+
+ if (fwrd) {
+ proc->sp++;
+ } else {
+ proc->sp--;
+ }
+
+ return false;
+}
+
+static void jump(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+
+#if !defined(NDEBUG)
+ uint8_t next = mvec_get_inst(core, proc->ip + 1);
+ uint8_t spin = mvec_get_inst(core, proc->sp);
+ assert(is_key(next));
+ assert(is_lock(spin));
+ assert(key_lock_match(next, spin));
+#endif
+
+ proc->ip = proc->sp;
+}
+
+static 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);
+ assert(proc->ip < MVEC_SIZE);
+ 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;
+
+ if (mnxt >= MVEC_SIZE) {
+ break;
+ }
+
+ uint8_t mins = mvec_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;
+ }
+ }
+}
+
+static void address(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+ uint64_t *reg;
+
+#if !defined(NDEBUG)
+ uint8_t next = mvec_get_inst(core, proc->ip + 1);
+ uint8_t spin = mvec_get_inst(core, proc->sp);
+ assert(is_key(next));
+ assert(is_lock(spin));
+ assert(key_lock_match(next, spin));
+#endif
+
+ get_reg_addr_list(core, pix, &reg, 1, true);
+ *reg = proc->sp;
+ increment_ip(core, pix);
+}
+
+static 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);
+ }
+}
+
+static 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->ip < MVEC_SIZE);
+ assert(proc->mb1s);
+ free_memory_block(core, proc->mb1a, proc->mb1s);
+ proc->mb1a = 0;
+ proc->mb1s = 0;
+}
+
+static 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);
+ assert(proc->ip < MVEC_SIZE);
+ 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;
+ }
+ }
+
+ // HALT: process SP address is invalid
+ // Discard allocated memory if any...
+ if (proc->sp >= MVEC_SIZE) {
+ if (proc->mb1s) {
+ free_child_memory_of(core, pix);
+ }
+
+ core->hlts++;
+ 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--;
+ }
+}
+
+static void if_not_zero(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+ uint64_t *reg;
+ get_reg_addr_list(core, pix, &reg, 1, false);
+ uint64_t jmod = ((proc->ip + 1 < MVEC_SIZE) && is_rmod(mvec_get_inst(core, proc->ip + 1))) ? 1 : 0;
+ uint64_t rmod = *reg ? 1 : 2;
+ proc->ip += jmod + rmod;
+ proc->sp = proc->ip;
+}
+
+static void mem_block_swap(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+
+ 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++) {
+ core->xeva.data[proc->mb0a + i]++;
+ }
+
+ for (uint64_t i = 0; i < proc->mb1s; i++) {
+ core->xeva.data[proc->mb1a + i]++;
+ }
+ }
+
+ increment_ip(core, pix);
+}
+
+static void mem_block_clear(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+
+ if (proc->mb1s) {
+ free_child_memory_of(core, pix);
+ }
+
+ increment_ip(core, pix);
+}
+
+static void mem_block_split(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+
+ 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);
+}
+
+static void three_regs_op(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:
+ if (*regs[2]) {
+ *regs[0] = *regs[1] / *regs[2];
+ } else {
+ // HALT: division by zero
+ core->hlts++;
+ return;
+ }
+
+ break;
+ default:
+ assert(false);
+ }
+
+ increment_ip(core, pix);
+}
+
+static void one_reg_op(struct Core *core, uint64_t pix, uint8_t inst) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ uint64_t *reg;
+ get_reg_addr_list(core, pix, &reg, 1, false);
+
+ switch (inst) {
+ case incn:
+ (*reg)++;
+ break;
+ case decn:
+ (*reg)--;
+ break;
+ case notn:
+ *reg = !(*reg);
+ break;
+ case shfl:
+ *reg <<= 1;
+ break;
+ case shfr:
+ *reg >>= 1;
+ break;
+ case zero:
+ *reg = 0;
+ break;
+ case unit:
+ *reg = 1;
+ break;
+ default:
+ assert(false);
+ }
+
+ increment_ip(core, pix);
+}
+
+static int get_sp_dir(uint64_t src, uint64_t dst) {
+ if (src == dst) {
+ return 0;
+ } else if (src - dst <= dst - src) {
+ return -1;
+ } else {
+ return 1;
+ }
+}
+
+static void load_reg(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+ uint64_t *regs[2];
+ get_reg_addr_list(core, pix, regs, 2, false);
+ int sp_dir = get_sp_dir(proc->sp, *regs[0]);
+
+ if (*regs[0] >= MVEC_SIZE) {
+ // Target address is invalid
+ increment_ip(core, pix);
+ } else 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);
+ }
+}
+
+static bool is_writeable_by(const struct Core *core, uint64_t addr, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ return addr < MVEC_SIZE && (!mvec_is_alloc(core, addr) || mvec_is_proc_owner(core, addr, pix));
+}
+
+static void write_reg(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+ uint64_t *regs[2];
+ get_reg_addr_list(core, pix, regs, 2, false);
+ int sp_dir = get_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] & ARCH_INST_MASK;
+
+ // Store write event
+ core->iwrt[inst]++;
+ core->weva.data[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
+ mvec_set_inst(core, addr, inst);
+ }
+
+ increment_ip(core, pix);
+ }
+}
+
+static void two_reg_op(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 rdup:
+ *regs[1] = *regs[0];
+ break;
+ case rswp:
+ {
+ uint64_t tmp = *regs[0];
+ *regs[0] = *regs[1];
+ *regs[1] = tmp;
+ }
+
+ break;
+ default:
+ assert(false);
+ }
+
+ increment_ip(core, pix);
+}
+
+static void push_to_stack(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+ uint64_t *reg;
+ get_reg_addr_list(core, pix, &reg, 1, false);
+ proc->s7 = proc->s6;
+ proc->s6 = proc->s5;
+ proc->s5 = proc->s4;
+ proc->s4 = proc->s3;
+ proc->s3 = proc->s2;
+ proc->s2 = proc->s1;
+ proc->s1 = proc->s0;
+ proc->s0 = *reg;
+ increment_ip(core, pix);
+}
+
+static void pop_from_stack(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+ assert(proc->ip < MVEC_SIZE);
+ uint64_t *reg;
+ get_reg_addr_list(core, pix, &reg, 1, false);
+ *reg = proc->s0;
+ proc->s0 = proc->s1;
+ proc->s1 = proc->s2;
+ proc->s2 = proc->s3;
+ proc->s3 = proc->s4;
+ proc->s4 = proc->s5;
+ proc->s5 = proc->s6;
+ proc->s6 = proc->s7;
+ proc->s7 = 0;
+ increment_ip(core, pix);
+}
+
+// ----------------------------------------------------------------------------
+// [section] callbacks
+// ----------------------------------------------------------------------------
+void arch_on_proc_step(struct Core *core, uint64_t pix) {
+ assert(core);
+ assert(mvec_proc_is_live(core, pix));
+ struct Proc *proc = proc_fetch(core, pix);
+
+ if (proc->ip >= MVEC_SIZE) {
+ // HALT: process IP address is invalid
+ core->hlts++;
+ return;
+ }
+
+ // Fetch instruction
+ uint8_t inst = mvec_get_inst(core, proc->ip);
+ 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)) {
+ address(core, pix);
+ }
+
+ break;
+ case adrf:
+ if (seek(core, pix, true)) {
+ address(core, pix);
+ }
+
+ break;
+ case malb:
+ alloc(core, pix, false);
+ break;
+ case malf:
+ alloc(core, pix, true);
+ break;
+ case ifnz:
+ if_not_zero(core, pix);
+ break;
+ case bswp:
+ mem_block_swap(core, pix);
+ break;
+ case bclr:
+ mem_block_clear(core, pix);
+ break;
+ case bspl:
+ mem_block_split(core, pix);
+ break;
+ case addn:
+ case subn:
+ case muln:
+ case divn:
+ three_regs_op(core, pix, inst);
+ break;
+ case incn:
+ case decn:
+ case notn:
+ case shfl:
+ case shfr:
+ case zero:
+ case unit:
+ one_reg_op(core, pix, inst);
+ break;
+ case rloa:
+ load_reg(core, pix);
+ break;
+ case rwrt:
+ write_reg(core, pix);
+ break;
+ case rdup:
+ case rswp:
+ two_reg_op(core, pix, inst);
+ break;
+ case rpsh:
+ push_to_stack(core, pix);
+ break;
+ case rpop:
+ pop_from_stack(core, pix);
+ break;
+ default:
+ increment_ip(core, pix);
+ break;
+ }
+}
+
+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_null_proc, sizeof(struct Proc));
+}
+
+// ----------------------------------------------------------------------------
+// [section] validation
+// ----------------------------------------------------------------------------
+#if !defined(NDEBUG)
+void arch_validate_core(struct Core *core) {
+ assert(core);
+
+ for (uint64_t pix = core->pfst; pix <= core->plst; pix++) {
+ 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
+
+// ----------------------------------------------------------------------------
+// [section] data
+// ----------------------------------------------------------------------------
+void arch_push_data_header(void) {
+ assert(g_sim_db);
+ log_info("Creating arch table in SQLite database");
+
+ // Empty blob columns help the data-server stay agnostic of DB schema.
+ // These represent EVA data that will be appended by data server.
+ sql_exec(
+ NULL, NULL,
+ "create table arch ("
+#define ARCH_SPEC_INST(label, symbol, core) \
+ #label "_pop_" #core " int not null, " \
+ #label "_exe_" #core " int not null, " \
+ #label "_wrt_" #core " int not null, "
+#define FOR_CORE(i) \
+ ARCH_SPEC_INST_SET(i) \
+ "wmb0_" #i " int not null, " \
+ "wmb1_" #i " int not null, " \
+ "wdea_" #i " int not null, " \
+ "hlts_" #i " int not null, " \
+ "wev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i " int not null, wev_" #i " blob, " \
+ "xev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i " int not null, xev_" #i " blob, "
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_SPEC_INST
+ "step int not null"
+ ");"
+ );
+}
+
+static int arch_count_inst_pop(struct Core *core) {
+ assert(core);
+
+ for (uint64_t i = 0; i < MVEC_SIZE; i++) {
+ core->ipop[mvec_get_inst(core, i)]++;
+ }
+
+#if !defined(NDEBUG)
+ uint64_t pop_total = 0;
+
+ for (uint64_t i = 0; i < ARCH_INST_COUNT; i++) {
+ pop_total += core->ipop[i];
+ }
+
+ assert(pop_total == MVEC_SIZE);
+#endif
+
+ return 0;
+}
+
+void arch_prepare_data_line(void) {
+ assert(g_sim_db);
+
+ // Measure instruction population and compress event-arrays in parallel
+ for (int i = 0; i < CORES; i++) {
+ struct Core *core = &g_cores[i];
+ thrd_create(&core->ipop_thrd, (thrd_start_t)arch_count_inst_pop, core);
+ thrd_create(&core->weva.thrd, (thrd_start_t)comp_deflate, &core->weva.params);
+ thrd_create(&core->xeva.thrd, (thrd_start_t)comp_deflate, &core->xeva.params);
+ }
+
+ for (int i = 0; i < CORES; i++) {
+ struct Core *core = &g_cores[i];
+ thrd_join(core->ipop_thrd, NULL);
+ thrd_join(core->weva.thrd, NULL);
+ thrd_join(core->xeva.thrd, NULL);
+ core->weva.blob_size = core->weva.params.strm.total_out;
+ core->xeva.blob_size = core->xeva.params.strm.total_out;
+ }
+}
+
+void arch_push_data_line(FILE *eva_file) {
+ assert(g_sim_db);
+ assert(eva_file);
+ assert(g_step % DATA_PUSH_INTERVAL == 0);
+ log_info("Pushing row to arch table in SQLite database");
+ sql_exec(
+ NULL, NULL,
+ "insert into arch ("
+#define ARCH_SPEC_INST(label, symbol, core) \
+ #label "_pop_" #core ", " \
+ #label "_exe_" #core ", " \
+ #label "_wrt_" #core ", "
+#define FOR_CORE(i) \
+ ARCH_SPEC_INST_SET(i) \
+ "wmb0_" #i ", " \
+ "wmb1_" #i ", " \
+ "wdea_" #i ", " \
+ "hlts_" #i ", " \
+ "wev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i ", " \
+ "xev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i ", "
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_SPEC_INST
+ "step"
+ ") values ("
+#define ARCH_SPEC_INST(label, symbol, core) \
+ "%ld, " \
+ "%ld, " \
+ "%ld, "
+#define FOR_CORE(i) \
+ ARCH_SPEC_INST_SET(i) \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, " \
+ "%ld, "
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_SPEC_INST
+ "%ld"
+ ");",
+#define ARCH_SPEC_INST(label, symbol, core) \
+ g_cores[core].ipop[label], \
+ g_cores[core].iexe[label], \
+ g_cores[core].iwrt[label],
+#define FOR_CORE(i) \
+ ARCH_SPEC_INST_SET(i) \
+ g_cores[i].wmb0, \
+ g_cores[i].wmb1, \
+ g_cores[i].wdea, \
+ g_cores[i].hlts, \
+ g_cores[i].weva.blob_size, \
+ g_cores[i].xeva.blob_size,
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_SPEC_INST
+ g_step
+ );
+
+ for (int i = 0; i < CORES; i++) {
+ struct Core *core = &g_cores[i];
+ fwrite(core->weva.comp, sizeof(Bytef), core->weva.blob_size, eva_file);
+ fwrite(core->xeva.comp, sizeof(Bytef), core->xeva.blob_size, eva_file);
+ comp_deflate_end(&core->weva.params);
+ comp_deflate_end(&core->xeva.params);
+ }
+
+ // Reset data aggregation fields
+ for (int i = 0; i < CORES; i++) {
+ struct Core *core = &g_cores[i];
+ memset(core->ipop, 0, sizeof(uint64_t) * ARCH_INST_COUNT);
+ memset(core->iexe, 0, sizeof(uint64_t) * ARCH_INST_COUNT);
+ memset(core->iwrt, 0, sizeof(uint64_t) * ARCH_INST_COUNT);
+ core->wmb0 = 0;
+ core->wmb1 = 0;
+ core->wdea = 0;
+ core->hlts = 0;
+ memset(core->weva.data, 0, sizeof(uint64_t) * MVEC_SIZE);
+ memset(core->xeva.data, 0, sizeof(uint64_t) * MVEC_SIZE);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// [section] main
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_NEW)
+void arch_core_init(struct Core *core) {
+ assert(core);
+
+ for (uint64_t i = 0; i < CLONES; i++) {
+ uint64_t addr_clone = (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;
+ }
+
+ core_event_array_init(&core->weva);
+ core_event_array_init(&core->xeva);
+}
+#endif
+
+#if defined(COMMAND_LOAD)
+void arch_core_load(struct Core *core, FILE *f) {
+ assert(core);
+ assert(f);
+
+ fread(core->ipop, sizeof(uint64_t), ARCH_INST_COUNT, f);
+ fread(core->iexe, sizeof(uint64_t), ARCH_INST_COUNT, f);
+ fread(core->iwrt, sizeof(uint64_t), ARCH_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);
+ fread(core->weva.data, sizeof(uint64_t), MVEC_SIZE, f);
+ fread(core->xeva.data, sizeof(uint64_t), MVEC_SIZE, f);
+
+ core_event_array_init(&core->weva);
+ core_event_array_init(&core->xeva);
+}
+#endif
+
+void arch_core_save(const struct Core *core, FILE *f) {
+ assert(core);
+ assert(f);
+
+ fwrite(core->ipop, sizeof(uint64_t), ARCH_INST_COUNT, f);
+ fwrite(core->iexe, sizeof(uint64_t), ARCH_INST_COUNT, f);
+ fwrite(core->iwrt, sizeof(uint64_t), ARCH_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);
+ fwrite(core->weva.data, sizeof(uint64_t), MVEC_SIZE, f);
+ fwrite(core->xeva.data, sizeof(uint64_t), MVEC_SIZE, f);
+}
+
+void arch_core_free(struct Core *core) {
+ assert(core);
+ (void)core;
+}
diff --git a/arch/v1/arch_inst.c b/arch/v1/arch_inst.c
new file mode 100644
index 0000000..4d75ede
--- /dev/null
+++ b/arch/v1/arch_inst.c
@@ -0,0 +1,40 @@
+// file : arch/v1/arch_inst.c
+// project : Salis-VM
+// author : Paul Oliver <contact@pauloliver.dev>
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define ARCH_EXPORT_INST_COUNT
+#include "arch_spec.h"
+#include "arch_inst.h"
+
+wchar_t arch_inst_symbol(uint8_t inst) {
+ assert(inst < ARCH_INST_COUNT);
+ assert(inst_enum_count == ARCH_INST_COUNT);
+
+ switch (inst) {
+#define ARCH_SPEC_INST(label, symbol, core) case label: return symbol;
+ ARCH_SPEC_INST_SET(core)
+#undef ARCH_SPEC_INST
+ }
+
+ assert(false);
+ return L'\0';
+}
+
+const char *arch_inst_mnemonic(uint8_t inst) {
+ assert(inst < ARCH_INST_COUNT);
+ assert(inst_enum_count == ARCH_INST_COUNT);
+
+ switch (inst) {
+#define ARCH_SPEC_INST(label, symbol, core) case label: return #label;
+ ARCH_SPEC_INST_SET(core)
+#undef ARCH_SPEC_INST
+ }
+
+ assert(false);
+ return NULL;
+}
diff --git a/arch/v1/arch_spec.h b/arch/v1/arch_spec.h
new file mode 100644
index 0000000..a904206
--- /dev/null
+++ b/arch/v1/arch_spec.h
@@ -0,0 +1,140 @@
+// file : arch/v1/arch_spec.h
+// project : Salis-VM
+// author : Paul Oliver <contact@pauloliver.dev>
+
+#pragma once
+
+// index:
+// [section] arch-specific core fields
+// [section] arch-specific process fields
+// [section] instruction set
+// [section] instruction enum
+
+// V1 has 64 instructions - 1 unused bit
+#define ARCH_SPEC_INST_BITS 6
+
+// ----------------------------------------------------------------------------
+// [section] arch-specific core fields
+// ----------------------------------------------------------------------------
+#define ARCH_SPEC_CORE_FIELDS \
+ ARCH_SPEC_CORE_FIELD(thrd_t, ipop_thrd) \
+ ARCH_SPEC_CORE_FIELD(uint64_t, ipop[ARCH_INST_COUNT]) \
+ ARCH_SPEC_CORE_FIELD(uint64_t, iexe[ARCH_INST_COUNT]) \
+ ARCH_SPEC_CORE_FIELD(uint64_t, iwrt[ARCH_INST_COUNT]) \
+ ARCH_SPEC_CORE_FIELD(uint64_t, wmb0) \
+ ARCH_SPEC_CORE_FIELD(uint64_t, wmb1) \
+ ARCH_SPEC_CORE_FIELD(uint64_t, wdea) \
+ ARCH_SPEC_CORE_FIELD(uint64_t, hlts) \
+ ARCH_SPEC_CORE_FIELD(struct EventArray, weva) \
+ ARCH_SPEC_CORE_FIELD(struct EventArray, xeva)
+
+// ----------------------------------------------------------------------------
+// [section] arch-specific process fields
+// ----------------------------------------------------------------------------
+#define ARCH_SPEC_PROC_FIELDS \
+ ARCH_SPEC_PROC_FIELD(uint64_t, ip) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, sp) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, mb0a) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, mb0s) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, mb1a) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, mb1s) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, r0x) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, r1x) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, r2x) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, r3x) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, s0) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, s1) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, s2) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, s3) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, s4) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, s5) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, s6) \
+ ARCH_SPEC_PROC_FIELD(uint64_t, s7)
+
+// ----------------------------------------------------------------------------
+// [section] instruction set
+// ----------------------------------------------------------------------------
+// Extra core argument allows calling macro recursively
+// within calls to FOR_CORES. See arch_push_data_header in arch/v1/arch.c
+// for an example of what I mean.
+#define ARCH_SPEC_INST_SET(core) \
+ ARCH_SPEC_INST(noop, L'⢀', core) \
+ ARCH_SPEC_INST(nop0, L'0', core) \
+ ARCH_SPEC_INST(nop1, L'1', core) \
+ ARCH_SPEC_INST(nop2, L'2', core) \
+ ARCH_SPEC_INST(nop3, L'3', core) \
+ \
+ ARCH_SPEC_INST(jmpb, L'❨', core) \
+ ARCH_SPEC_INST(jmpf, L'❩', core) \
+ ARCH_SPEC_INST(adrb, L'⟦', core) \
+ ARCH_SPEC_INST(adrf, L'⟧', core) \
+ ARCH_SPEC_INST(malb, L'❴', core) \
+ ARCH_SPEC_INST(malf, L'❵', core) \
+ \
+ ARCH_SPEC_INST(ifnz, L'?', core) \
+ ARCH_SPEC_INST(bswp, L'%', core) \
+ ARCH_SPEC_INST(bclr, L'∅', core) \
+ ARCH_SPEC_INST(bspl, L'$', core) \
+ \
+ ARCH_SPEC_INST(addn, L'+', core) \
+ ARCH_SPEC_INST(subn, L'−', core) \
+ ARCH_SPEC_INST(muln, L'×', core) \
+ ARCH_SPEC_INST(divn, L'/', core) \
+ ARCH_SPEC_INST(incn, L'△', core) \
+ ARCH_SPEC_INST(decn, L'▽', core) \
+ ARCH_SPEC_INST(notn, L'¬', core) \
+ ARCH_SPEC_INST(shfl, L'«', core) \
+ ARCH_SPEC_INST(shfr, L'»', core) \
+ ARCH_SPEC_INST(zero, L'z', core) \
+ ARCH_SPEC_INST(unit, L'u', core) \
+ \
+ ARCH_SPEC_INST(rloa, L'↓', core) \
+ ARCH_SPEC_INST(rwrt, L'↑', core) \
+ ARCH_SPEC_INST(rdup, L':', core) \
+ ARCH_SPEC_INST(rswp, L'↔', core) \
+ ARCH_SPEC_INST(rpsh, L'↥', core) \
+ ARCH_SPEC_INST(rpop, L'↧', core) \
+ \
+ ARCH_SPEC_INST(keya, L'a', core) \
+ ARCH_SPEC_INST(keyb, L'b', core) \
+ ARCH_SPEC_INST(keyc, L'c', core) \
+ ARCH_SPEC_INST(keyd, L'd', core) \
+ ARCH_SPEC_INST(keye, L'e', core) \
+ ARCH_SPEC_INST(keyf, L'f', core) \
+ ARCH_SPEC_INST(keyg, L'g', core) \
+ ARCH_SPEC_INST(keyh, L'h', core) \
+ ARCH_SPEC_INST(keyi, L'i', core) \
+ ARCH_SPEC_INST(keyj, L'j', core) \
+ ARCH_SPEC_INST(keyk, L'k', core) \
+ ARCH_SPEC_INST(keyl, L'l', core) \
+ ARCH_SPEC_INST(keym, L'm', core) \
+ ARCH_SPEC_INST(keyn, L'n', core) \
+ ARCH_SPEC_INST(keyo, L'o', core) \
+ ARCH_SPEC_INST(keyp, L'p', core) \
+ \
+ ARCH_SPEC_INST(loka, L'A', core) \
+ ARCH_SPEC_INST(lokb, L'B', core) \
+ ARCH_SPEC_INST(lokc, L'C', core) \
+ ARCH_SPEC_INST(lokd, L'D', core) \
+ ARCH_SPEC_INST(loke, L'E', core) \
+ ARCH_SPEC_INST(lokf, L'F', core) \
+ ARCH_SPEC_INST(lokg, L'G', core) \
+ ARCH_SPEC_INST(lokh, L'H', core) \
+ ARCH_SPEC_INST(loki, L'I', core) \
+ ARCH_SPEC_INST(lokj, L'J', core) \
+ ARCH_SPEC_INST(lokk, L'K', core) \
+ ARCH_SPEC_INST(lokl, L'L', core) \
+ ARCH_SPEC_INST(lokm, L'M', core) \
+ ARCH_SPEC_INST(lokn, L'N', core) \
+ ARCH_SPEC_INST(loko, L'O', core) \
+ ARCH_SPEC_INST(lokp, L'P', core)
+
+// ----------------------------------------------------------------------------
+// [section] instruction enum
+// ----------------------------------------------------------------------------
+enum {
+#define ARCH_SPEC_INST(label, symbol, core) label,
+ ARCH_SPEC_INST_SET(core)
+#undef ARCH_SPEC_INST
+ inst_enum_count,
+};
diff --git a/arch/v1/client_plots.h b/arch/v1/client_plots.h
new file mode 100644
index 0000000..4b2c3af
--- /dev/null
+++ b/arch/v1/client_plots.h
@@ -0,0 +1,83 @@
+// file : arch/v1/client_plots.h
+// project : Salis-VM
+// author : Paul Oliver <contact@pauloliver.dev>
+
+#pragma once
+
+// index:
+
+static std::array g_arch_traces = std::to_array<TraceNamed<ImS64>>({
+#define ARCH_SPEC_INST(label, symbol, core) \
+ {#label "_pop_" #core, #label}, \
+ {#label "_exe_" #core, #label}, \
+ {#label "_wrt_" #core, #label},
+#define FOR_CORE(i) \
+ {"wmb0_" #i, "wmb0_" #i}, \
+ {"wmb1_" #i, "wmb1_" #i}, \
+ {"wdea_" #i, "wdea_" #i}, \
+ {"hlts_" #i, "hlts_" #i}, \
+ ARCH_SPEC_INST_SET(i)
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_SPEC_INST
+});
+
+static 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
+});
+
+static 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
+ }},
+
+ {"hlts", "general", {
+#define FOR_CORE(i) "hlts_" #i,
+ FOR_CORES
+#undef FOR_CORE
+ }}
+});
+
+static std::array g_arch_plots_stacked = std::to_array<PlotStacked>({
+#define ARCH_SPEC_INST(label, symbol, core) \
+ #label "_pop_" #core,
+#define FOR_CORE(i) \
+ {"ipop%_" #i, "population", { ARCH_SPEC_INST_SET(i) }},
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_SPEC_INST
+
+#define ARCH_SPEC_INST(label, symbol, core) \
+ #label "_exe_" #core,
+#define FOR_CORE(i) \
+ {"iexe%_" #i, "population", { ARCH_SPEC_INST_SET(i) }},
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_SPEC_INST
+
+#define ARCH_SPEC_INST(label, symbol, core) \
+ #label "_wrt_" #core,
+#define FOR_CORE(i) \
+ {"iwrt%_" #i, "population", { ARCH_SPEC_INST_SET(i) }},
+ FOR_CORES
+#undef FOR_CORE
+#undef ARCH_SPEC_INST
+});
+
+static 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
+});