aboutsummaryrefslogtreecommitdiff
path: root/core.c
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2026-02-24 01:33:45 +0100
committerPaul Oliver <contact@pauloliver.dev>2026-02-24 01:33:45 +0100
commit9f7e70904e6c0fa650323ac5e50ebf6003da333c (patch)
tree3015be498d36e8d5c960cf55667c6c825f7de493 /core.c
parent0fb1497a62332e0db45f94b4f195cb37183678cb (diff)
Removes usage of Jinja templates
Use CPP to pre-process C files instead
Diffstat (limited to 'core.c')
-rw-r--r--core.c982
1 files changed, 982 insertions, 0 deletions
diff --git a/core.c b/core.c
new file mode 100644
index 0000000..a4b57d8
--- /dev/null
+++ b/core.c
@@ -0,0 +1,982 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <threads.h>
+
+#define DATA_PUSH_BUSY_TIMEOUT 600000
+#define INST_CAP 0x80
+#define INST_MASK 0x7f
+#define IPC_FLAG 0x80
+#define MALL_FLAG 0x80
+#define UINT64_HALF 0x8000000000000000ul
+
+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_idx;
+
+ uint64_t ivpt;
+ uint64_t *ivav;
+ uint8_t *iviv;
+
+#define CORE_FIELD(type, name, suff) type name suff;
+ CORE_FIELDS
+#undef CORE_FIELD
+
+ struct Proc *pvec;
+ uint8_t mvec[MVEC_SIZE];
+ uint8_t tgap[THREAD_GAP];
+};
+
+// Globals
+struct Core g_cores[CORES];
+uint64_t g_steps;
+uint64_t g_syncs;
+const struct Proc g_dead_proc;
+
+#if defined(COMMAND_LOAD) || defined(COMMAND_NEW)
+char g_asav_pbuf[AUTOSAVE_NAME_LEN];
+#endif
+
+#if defined(DATA_PUSH_PATH)
+sqlite3 *g_sim_data;
+#endif
+
+// Each UI must install these logger functions before salis_init() gets invoked
+void (*g_info)(const char *fmt, ...);
+void (*g_warn)(const char *fmt, ...);
+
+// Each architecture must define these functions
+#if defined(COMMAND_BENCH) || defined(COMMAND_NEW)
+void arch_core_init(struct Core *core);
+#endif
+
+void arch_core_free(struct Core *core);
+
+#if defined(COMMAND_LOAD) || defined(COMMAND_NEW)
+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(DATA_PUSH_PATH)
+void arch_push_data_header();
+void arch_push_data_line();
+#endif
+
+// ----------------------------------------------------------------------------
+// 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;
+#else
+ assert(addr < MVEC_SIZE);
+ core->mvec[addr] |= MALL_FLAG;
+#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;
+#else
+ assert(addr < MVEC_SIZE);
+ core->mvec[addr] ^= MALL_FLAG;
+#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);
+ 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;
+}
+
+// ----------------------------------------------------------------------------
+// Mutator functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_BENCH) || 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
+ }
+}
+
+// ----------------------------------------------------------------------------
+// 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));
+}
+
+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];
+}
+
+// ----------------------------------------------------------------------------
+// Core functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_LOAD) || defined(COMMAND_NEW)
+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->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);
+
+ arch_core_save(f, core);
+}
+#endif
+
+#if defined(COMMAND_BENCH) || 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) {
+ 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 = CLONES;
+ core->pcap = CLONES;
+ core->plst = 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);
+
+ 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);
+
+ 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);
+ 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);
+ }
+
+ muta_cosmic_ray(core);
+ core_step(core);
+}
+
+// ----------------------------------------------------------------------------
+// Main salis functions
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_LOAD) || defined(COMMAND_NEW)
+void salis_save(const char *path) {
+#if defined(COMPRESS)
+ size_t size = 0;
+ char *in = NULL;
+ FILE *f = open_memstream(&in, &size);
+#else
+ FILE *f = fopen(path, "wb");
+#endif
+
+ 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);
+
+#if defined(COMPRESS)
+ assert(size);
+
+ char *out = malloc(size);
+ assert(out);
+
+ z_stream strm = { 0 };
+ strm.zalloc = NULL;
+ strm.zfree = NULL;
+ strm.opaque = NULL;
+
+ deflateInit(&strm, Z_DEFAULT_COMPRESSION);
+
+ strm.avail_in = size;
+ strm.avail_out = size;
+ strm.next_in = (Bytef *)in;
+ strm.next_out = (Bytef *)out;
+
+ deflate(&strm, Z_FINISH);
+
+ FILE *fx = fopen(path, "wb");
+ assert(fx);
+
+ fwrite(&size, sizeof(size_t), 1, fx);
+ fwrite(out, sizeof(char), strm.total_out, fx);
+ fclose(fx);
+
+ deflateEnd(&strm);
+
+ free(in);
+ free(out);
+#endif
+}
+
+void salis_auto_save() {
+#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);
+
+ g_info("Saving simulation state on step '%#lx'", g_steps);
+ salis_save(g_asav_pbuf);
+}
+#endif
+
+#if defined(DATA_PUSH_PATH)
+void salis_exec_sql(int blob_cnt, const void **blobs, const int *blob_sizes, 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_data, sql_str, -1, &sql_stmt, NULL);
+ assert(sql_res == SQLITE_OK);
+ free(sql_str);
+
+ for (int i = 0; i < blob_cnt; ++i) {
+ assert(blobs[i]);
+ sql_res = sqlite3_bind_blob(sql_stmt, i + 1, blobs[i], blob_sizes[i], SQLITE_STATIC);
+ assert(sql_res == SQLITE_OK);
+ }
+
+ // Only handle SQLITE_BUSY error, in which case we retry the query.
+ // Setting 'journal_mode=wal;' should help prevent busy database errors.
+ while (true) {
+ sql_res = sqlite3_step(sql_stmt);
+
+ if (sql_res == SQLITE_DONE || sql_res == SQLITE_ROW) {
+ break;
+ }
+
+ g_warn("SQLite database returned error '%d' with message:", sql_res);
+ g_warn(sqlite3_errmsg(g_sim_data));
+
+ if (sql_res == SQLITE_BUSY) {
+ g_info("Will retry query...");
+ continue;
+ }
+
+ assert(false);
+ }
+
+ sqlite3_finalize(sql_stmt);
+}
+#endif
+
+#if defined(COMMAND_BENCH) || defined(COMMAND_NEW)
+void salis_init() {
+ assert(g_info);
+ assert(g_warn);
+
+ 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
+
+#if defined(DATA_PUSH_PATH)
+ sqlite3_open(DATA_PUSH_PATH, &g_sim_data);
+ assert(g_sim_data);
+
+ // Install busy handler to retry transactions if DB is locked
+ sqlite3_busy_timeout(g_sim_data, 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
+ salis_exec_sql(0, NULL, NULL, "pragma journal_mode=wal;");
+
+ arch_push_data_header();
+ arch_push_data_line();
+#endif
+}
+#endif
+
+#if defined(COMMAND_LOAD)
+void salis_load() {
+#if defined(COMPRESS)
+ 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);
+
+ z_stream strm = { 0 };
+ strm.next_in = (Bytef *)in;
+ strm.avail_in = x_size;
+ strm.zalloc = NULL;
+ strm.zfree = NULL;
+ strm.opaque = NULL;
+
+ inflateInit(&strm);
+
+ strm.avail_out = size;
+ strm.next_out = (Bytef *)out;
+
+#if defined(NDEBUG)
+ inflate(&strm, Z_FINISH);
+#else
+ assert(inflate(&strm, Z_FINISH));
+#endif
+
+ inflateEnd(&strm);
+
+ FILE *f = fmemopen(out, size, "rb");
+#else
+ FILE *f = fopen(SIM_PATH, "rb");
+#endif
+
+ 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);
+
+#if defined(COMPRESS)
+ free(in);
+ free(out);
+#endif
+
+#if defined(DATA_PUSH_PATH)
+ sqlite3_open(DATA_PUSH_PATH, &g_sim_data);
+ assert(g_sim_data);
+
+ // Install busy handler to retry transactions if DB is locked
+ sqlite3_busy_timeout(g_sim_data, DATA_PUSH_BUSY_TIMEOUT);
+#endif
+}
+#endif
+
+int salis_thread(struct Core *core) {
+ assert(core);
+
+ for (uint64_t i = 0; i < core->thrd_idx; ++i) {
+ core_step(core);
+ }
+
+ return 0;
+}
+
+void salis_run_thread(uint64_t ns) {
+ for (int i = 0; i < CORES; ++i) {
+ g_cores[i].thrd_idx = 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() {
+ 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_LOAD) || defined(COMMAND_NEW)
+ if (g_steps % AUTOSAVE_INTERVAL == 0) {
+ salis_auto_save();
+ }
+#endif
+
+#if defined(DATA_PUSH_PATH)
+ if (g_steps % DATA_PUSH_INTERVAL == 0) {
+ arch_push_data_line();
+ }
+#endif
+
+ 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() {
+ 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() {
+#if defined(DATA_PUSH_PATH)
+ assert(g_sim_data);
+ sqlite3_close(g_sim_data);
+#endif
+
+ 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;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Architecture
+// ----------------------------------------------------------------------------
+#include "arch.c"
+
+// ----------------------------------------------------------------------------
+// UI
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_LOAD) || defined(COMMAND_NEW)
+#include "ui.c"
+#endif
+
+// ----------------------------------------------------------------------------
+// Benchmark
+// ----------------------------------------------------------------------------
+#if defined(COMMAND_BENCH)
+void log_impl(const char *format, ...) {
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+}
+
+int main() {
+ g_info = log_impl;
+ g_warn = log_impl;
+
+ g_info("Salis Benchmark Test\n\n");
+
+ salis_init();
+ salis_step(STEPS);
+
+ g_info("seed => %#lx\n", SEED);
+ g_info("g_steps => %#lx\n", g_steps);
+ g_info("g_syncs => %#lx\n", g_syncs);
+
+ for (int i = 0; i < CORES; ++i) {
+ g_info("\n");
+ g_info("core %d mall => %#lx\n", i, g_cores[i].mall);
+ g_info("core %d mut0 => %#lx\n", i, g_cores[i].muta[0]);
+ g_info("core %d mut1 => %#lx\n", i, g_cores[i].muta[1]);
+ g_info("core %d mut2 => %#lx\n", i, g_cores[i].muta[2]);
+ g_info("core %d mut3 => %#lx\n", i, g_cores[i].muta[3]);
+ g_info("core %d pnum => %#lx\n", i, g_cores[i].pnum);
+ g_info("core %d pcap => %#lx\n", i, g_cores[i].pcap);
+ g_info("core %d pfst => %#lx\n", i, g_cores[i].pfst);
+ g_info("core %d plst => %#lx\n", i, g_cores[i].plst);
+ g_info("core %d pcur => %#lx\n", i, g_cores[i].pcur);
+ g_info("core %d psli => %#lx\n", i, g_cores[i].psli);
+ g_info("core %d cycl => %#lx\n", i, g_cores[i].cycl);
+ g_info("core %d ivpt => %#lx\n", i, g_cores[i].ivpt);
+ g_info("\n");
+
+ for (int j = 0; j < 32; ++j) {
+ g_info("%02x ", g_cores[i].mvec[j]);
+ }
+
+ g_info("\n");
+ }
+
+ salis_free();
+}
+#endif