From f2c34b1d2c18270a0327fb4896fa307fae098770 Mon Sep 17 00:00:00 2001 From: Paul Oliver Date: Tue, 14 Jul 2026 17:29:02 +0200 Subject: Initial --- anc/null/0123.asm | 8 + arch/null/arch.c | 164 ++++++++ arch/null/arch_inst.c | 67 +++ arch/null/arch_inst.h | 21 + arch/null/arch_spec.h | 16 + core/arch.h | 57 +++ core/compress.c | 77 ++++ core/compress.h | 35 ++ core/logger.c | 98 +++++ core/logger.h | 9 + core/salis.c | 1100 +++++++++++++++++++++++++++++++++++++++++++++++++ core/salis.h | 147 +++++++ core/sql.c | 105 +++++ core/sql.h | 21 + docs/flowchart.odg | Bin 0 -> 23654 bytes docs/flowchart.svg | 844 +++++++++++++++++++++++++++++++++++++ salis.py | 328 +++++++++++++++ ui/daemon/links.json | 1 + ui/daemon/ui.c | 39 ++ 19 files changed, 3137 insertions(+) create mode 100644 anc/null/0123.asm create mode 100644 arch/null/arch.c create mode 100644 arch/null/arch_inst.c create mode 100644 arch/null/arch_inst.h create mode 100644 arch/null/arch_spec.h create mode 100644 core/arch.h create mode 100644 core/compress.c create mode 100644 core/compress.h create mode 100644 core/logger.c create mode 100644 core/logger.h create mode 100644 core/salis.c create mode 100644 core/salis.h create mode 100644 core/sql.c create mode 100644 core/sql.h create mode 100644 docs/flowchart.odg create mode 100644 docs/flowchart.svg create mode 100755 salis.py create mode 100644 ui/daemon/links.json create mode 100644 ui/daemon/ui.c diff --git a/anc/null/0123.asm b/anc/null/0123.asm new file mode 100644 index 0000000..91381a6 --- /dev/null +++ b/anc/null/0123.asm @@ -0,0 +1,8 @@ +; file : anc/null/0123.asm +; project : Salis-VM +; author : Paul Oliver + +null 00 +null 01 +null 02 +null 03 diff --git a/arch/null/arch.c b/arch/null/arch.c new file mode 100644 index 0000000..9faf40d --- /dev/null +++ b/arch/null/arch.c @@ -0,0 +1,164 @@ +// file : arch/null/arch.c +// project : Salis-VM +// author : Paul Oliver + +// index: +// [section] includes +// [section] getters +// [section] callbacks +// [section] validation +// [section] data +// [section] main + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include + +#include "arch.h" +#include "arch_spec.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] callbacks +// ---------------------------------------------------------------------------- +void arch_on_proc_step(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + (void)core; + (void)pix; + return; +} + +void arch_on_proc_kill(struct Core *core) { + assert(core); + assert(core->pnum > 1); + (void)core; +} + +// ---------------------------------------------------------------------------- +// [section] validation +// ---------------------------------------------------------------------------- +#if !defined(NDEBUG) +void arch_validate_core(struct Core *core) { + assert(core); + (void)core; +} +#endif + +// ---------------------------------------------------------------------------- +// [section] data +// ---------------------------------------------------------------------------- +void arch_push_data_header(void) { + log_info("Creating arch table in SQLite database"); + sql_exec(NULL, NULL, "create table arch (step int not null);"); +} + +void arch_prepare_data_line(void) { + log_info("Preparing arch data-line"); + sql_exec(NULL, NULL, "insert into arch (step) values (%ld);", g_step); +} + +void arch_push_data_line(FILE *eva_file) { + 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_step); +} + +// ---------------------------------------------------------------------------- +// [section] main +// ---------------------------------------------------------------------------- +void arch_core_init(struct Core *core) { + assert(core); + +#if defined(ARCH_SPEC_MVEC_LOOP) + uint64_t addr = SALIS_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; + } +} + +void arch_core_free(struct Core *core) { + assert(core); + (void)core; +} + +void arch_core_load(struct Core *core, FILE *f) { + assert(core); + assert(f); + (void)core; + (void)f; +} + +void arch_core_save(const struct Core *core, FILE *f) { + assert(core); + assert(f); + (void)core; + (void)f; +} diff --git a/arch/null/arch_inst.c b/arch/null/arch_inst.c new file mode 100644 index 0000000..43f5d0c --- /dev/null +++ b/arch/null/arch_inst.c @@ -0,0 +1,67 @@ +// file : arch/null/arch_inst.c +// project : Salis-VM +// author : Paul Oliver + +// index: +// [section] includes +// [section] macros +// [section] globals +// [section] definitions + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include +#include +#include +#include + +#include "arch_inst.h" + +// ---------------------------------------------------------------------------- +// [section] macros +// ---------------------------------------------------------------------------- +#define INST_LOOP \ + INST(00) INST(01) INST(02) INST(03) INST(04) INST(05) INST(06) INST(07) INST(08) INST(09) INST(0a) INST(0b) INST(0c) INST(0d) INST(0e) INST(0f) \ + INST(10) INST(11) INST(12) INST(13) INST(14) INST(15) INST(16) INST(17) INST(18) INST(19) INST(1a) INST(1b) INST(1c) INST(1d) INST(1e) INST(1f) \ + INST(20) INST(21) INST(22) INST(23) INST(24) INST(25) INST(26) INST(27) INST(28) INST(29) INST(2a) INST(2b) INST(2c) INST(2d) INST(2e) INST(2f) \ + INST(30) INST(31) INST(32) INST(33) INST(34) INST(35) INST(36) INST(37) INST(38) INST(39) INST(3a) INST(3b) INST(3c) INST(3d) INST(3e) INST(3f) \ + INST(40) INST(41) INST(42) INST(43) INST(44) INST(45) INST(46) INST(47) INST(48) INST(49) INST(4a) INST(4b) INST(4c) INST(4d) INST(4e) INST(4f) \ + INST(50) INST(51) INST(52) INST(53) INST(54) INST(55) INST(56) INST(57) INST(58) INST(59) INST(5a) INST(5b) INST(5c) INST(5d) INST(5e) INST(5f) \ + INST(60) INST(61) INST(62) INST(63) INST(64) INST(65) INST(66) INST(67) INST(68) INST(69) INST(6a) INST(6b) INST(6c) INST(6d) INST(6e) INST(6f) \ + INST(70) INST(71) INST(72) INST(73) INST(74) INST(75) INST(76) INST(77) INST(78) INST(79) INST(7a) INST(7b) INST(7c) INST(7d) INST(7e) INST(7f) + +// ---------------------------------------------------------------------------- +// [section] globals +// ---------------------------------------------------------------------------- +wchar_t *g_arch_inst_symbols = ( + L"⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟" + L"⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿" + L"⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟" + L"⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿" +); + +// ---------------------------------------------------------------------------- +// [section] definitions +// ---------------------------------------------------------------------------- +int arch_inst_cap(void) { + return ARCH_INST_CAP; +} + +wchar_t arch_inst_symbol(uint8_t inst) { + assert(inst < ARCH_INST_CAP); + return g_arch_inst_symbols[inst]; +} + +const char *arch_inst_mnemonic(uint8_t inst) { + assert(inst < ARCH_INST_CAP); + + switch (inst) { +#define INST(code) case 0x##code: return "null " #code; + INST_LOOP +#undef INST + } + + assert(false); + return NULL; +} diff --git a/arch/null/arch_inst.h b/arch/null/arch_inst.h new file mode 100644 index 0000000..f9d85ff --- /dev/null +++ b/arch/null/arch_inst.h @@ -0,0 +1,21 @@ +// file : arch/null/arch_inst.h +// project : Salis-VM +// author : Paul Oliver + +#pragma once + +// index: +// [section] macros +// [section] definitions + +// ---------------------------------------------------------------------------- +// [section] macros +// ---------------------------------------------------------------------------- +#define ARCH_INST_CAP 0x80 + +// ---------------------------------------------------------------------------- +// [section] definitions +// ---------------------------------------------------------------------------- +int arch_inst_cap(void); +wchar_t arch_inst_symbol(uint8_t inst); +const char *arch_inst_mnemonic(uint8_t inst); diff --git a/arch/null/arch_spec.h b/arch/null/arch_spec.h new file mode 100644 index 0000000..eeb6d44 --- /dev/null +++ b/arch/null/arch_spec.h @@ -0,0 +1,16 @@ +// file : arch/null/arch_spec.h +// project : Salis-VM +// author : Paul Oliver + +#pragma once + +//#define ARCH_SPEC_MVEC_LOOP +#define ARCH_SPEC_CORE_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) diff --git a/core/arch.h b/core/arch.h new file mode 100644 index 0000000..b44d193 --- /dev/null +++ b/core/arch.h @@ -0,0 +1,57 @@ +// file : core/arch.h +// project : Salis-VM +// author : Paul Oliver + +#pragma once + +// index: +// [section] structs +// [section] getters +// [section] callbacks +// [section] validation +// [section] data +// [section] main + +// ---------------------------------------------------------------------------- +// [section] structs +// ---------------------------------------------------------------------------- +struct Core; + +// ---------------------------------------------------------------------------- +// [section] getters +// ---------------------------------------------------------------------------- +uint64_t arch_proc_get_ip_addr(const struct Core *core, uint64_t pix); +uint64_t arch_proc_get_sp_addr(const struct Core *core, uint64_t pix); +uint64_t arch_proc_get_mb0_addr(const struct Core *core, uint64_t pix); +uint64_t arch_proc_get_mb0_size(const struct Core *core, uint64_t pix); +uint64_t arch_proc_get_mb1_addr(const struct Core *core, uint64_t pix); +uint64_t arch_proc_get_mb1_size(const struct Core *core, uint64_t pix); +uint64_t arch_proc_get_slice(const struct Core *core, uint64_t pix); + +// ---------------------------------------------------------------------------- +// [section] callbacks +// ---------------------------------------------------------------------------- +void arch_on_proc_step(struct Core *core, uint64_t pix); +void arch_on_proc_kill(struct Core *core); + +// ---------------------------------------------------------------------------- +// [section] validation +// ---------------------------------------------------------------------------- +#if !defined(NDEBUG) +void arch_validate_core(struct Core *core); +#endif + +// ---------------------------------------------------------------------------- +// [section] data +// ---------------------------------------------------------------------------- +void arch_push_data_header(void); +void arch_prepare_data_line(void); +void arch_push_data_line(FILE *eva_file); + +// ---------------------------------------------------------------------------- +// [section] main +// ---------------------------------------------------------------------------- +void arch_core_init(struct Core *core); +void arch_core_free(struct Core *core); +void arch_core_load(struct Core *core, FILE *f); +void arch_core_save(const struct Core *core, FILE *f); diff --git a/core/compress.c b/core/compress.c new file mode 100644 index 0000000..6ca72c6 --- /dev/null +++ b/core/compress.c @@ -0,0 +1,77 @@ +// file : core/compress.c +// project : Salis-VM +// author : Paul Oliver + +// index: +// [section] includes +// [section] definitions + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include +#include + +#include "compress.h" + +// ---------------------------------------------------------------------------- +// [section] definitions +// ---------------------------------------------------------------------------- +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; +} + +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_deflate_end(struct DeflateParams *params) { + assert(params); + deflateEnd(¶ms->strm); +} + +void comp_inflate_end(struct InflateParams *params) { + assert(params); + inflateEnd(¶ms->strm); +} diff --git a/core/compress.h b/core/compress.h new file mode 100644 index 0000000..64d7dde --- /dev/null +++ b/core/compress.h @@ -0,0 +1,35 @@ +// file : core/compress.h +// project : Salis-VM +// author : Paul Oliver + +#pragma once + +// index: +// [section] structs +// [section] declarations + +// ---------------------------------------------------------------------------- +// [section] structs +// ---------------------------------------------------------------------------- +struct DeflateParams { + z_stream strm; + size_t size; + Bytef *in; + Bytef *out; +}; + +struct InflateParams { + z_stream strm; + size_t avail_in; + size_t size; + Bytef *in; + Bytef *out; +}; + +// ---------------------------------------------------------------------------- +// [section] declarations +// ---------------------------------------------------------------------------- +int comp_deflate(struct DeflateParams *params); +int comp_inflate(struct InflateParams *params); +void comp_deflate_end(struct DeflateParams *params); +void comp_inflate_end(struct InflateParams *params); diff --git a/core/logger.c b/core/logger.c new file mode 100644 index 0000000..47182ce --- /dev/null +++ b/core/logger.c @@ -0,0 +1,98 @@ +// file : core/logger.c +// project : Salis-VM +// author : Paul Oliver + +// index: +// [section] includes +// [section] macros +// [section] definitions + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include +#include +#include +#include +#include + +#include "logger.h" + +// ---------------------------------------------------------------------------- +// [section] macros +// ---------------------------------------------------------------------------- +#define LOG_BUFFER_SIZE 0x800 + +#define COLOR_CLEAR "\033[0m" +#define COLOR_TRACE "\033[1;34m" +#define COLOR_INFO "\033[1;32m" +#define COLOR_ATTENTION "\033[1;33m" + +#define LEVEL_TRACE "TRACE" +#define LEVEL_INFO "INFO" +#define LEVEL_ATTENTION "ATTENTION" + +// ---------------------------------------------------------------------------- +// [section] definitions +// ---------------------------------------------------------------------------- +static void log_default(const char *color_code, const char *level, const char *format, va_list args) { + assert(level); + assert(format); + + char log_buffer[LOG_BUFFER_SIZE]; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + int msec = (int)(ts.tv_nsec / 1000000l); + struct tm tm = *localtime(&ts.tv_sec); + pid_t pid = getpid(); + + int rem = snprintf( + log_buffer, + LOG_BUFFER_SIZE, + "\r%s%d-%02d-%02d %02d:%02d:%02d.%03d %07d [%s]%s ", + color_code ? 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, + color_code ? COLOR_CLEAR : "" + ); + + vsnprintf(log_buffer + rem, LOG_BUFFER_SIZE - rem, format, args); + printf("%s\n", log_buffer); + fflush(stdout); +} + +void log_trace(const char *format, ...) { + assert(format); + +#if defined(LOG_TRACE) + va_list args; + va_start(args, format); + log_default(COLOR_TRACE, LEVEL_TRACE, format, args); + va_end(args); +#else + (void)format; +#endif +} + +void log_info(const char *format, ...) { + assert(format); + va_list args; + va_start(args, format); + log_default(COLOR_INFO, LEVEL_INFO, format, args); + va_end(args); +} + +void log_attention(const char *format, ...) { + assert(format); + va_list args; + va_start(args, format); + log_default(COLOR_ATTENTION, LEVEL_ATTENTION, format, args); + va_end(args); +} diff --git a/core/logger.h b/core/logger.h new file mode 100644 index 0000000..e0da38b --- /dev/null +++ b/core/logger.h @@ -0,0 +1,9 @@ +// file : core/logger.h +// project : Salis-VM +// author : Paul Oliver + +#pragma once + +void log_trace(const char *format, ...); +void log_info(const char *format, ...); +void log_attention(const char *format, ...); diff --git a/core/salis.c b/core/salis.c new file mode 100644 index 0000000..93dc842 --- /dev/null +++ b/core/salis.c @@ -0,0 +1,1100 @@ +// file : core/salis.c +// project : Salis-VM +// author : Paul Oliver + +// index: +// [section] includes +// [section] macros +// [section] globals +// [section] memory vector functions +// [section] mutator functions +// [section] process functions +// [section] core functions +// [section] salis functions + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arch.h" +#include "arch_inst.h" +#include "arch_spec.h" +#include "compress.h" +#include "logger.h" +#include "salis.h" +#include "sql.h" + +// ---------------------------------------------------------------------------- +// [section] macros +// ---------------------------------------------------------------------------- +#define INST_MASK 0x7f +#define IPCM_FLAG 0x80 +#define MALL_FLAG 0x80 + +#define STEP_GROUP_TIME_MIN_MS 0x400 +#define STEP_GROUP_TIME_MAX_MS 0x800 + +// ---------------------------------------------------------------------------- +// [section] globals +// ---------------------------------------------------------------------------- +struct Core g_cores[CORES]; +uint64_t g_step; +uint64_t g_sync; +const struct Proc g_null_proc; + +char g_asav_pbuf[AUTOSAVE_NAME_LEN]; +char g_evas_pbuf[EVA_SAVE_NAME_LEN]; + +atomic_bool g_running; +thrd_t g_thrd; + +// ---------------------------------------------------------------------------- +// [section] memory vector functions +// ---------------------------------------------------------------------------- +#if defined(ARCH_SPEC_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(ARCH_SPEC_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(ARCH_SPEC_MVEC_LOOP) + uint64_t loop_addr = mvec_loop(addr); +#else + assert(addr < MVEC_SIZE); + uint64_t loop_addr = addr; +#endif + + core->mvec[loop_addr] |= MALL_FLAG; + core->aeva.data[loop_addr]++; + core->mall++; +} + + +void mvec_free(struct Core *core, uint64_t addr) { + assert(core); + assert(mvec_is_alloc(core, addr)); + +#if defined(ARCH_SPEC_MVEC_LOOP) + uint64_t loop_addr = mvec_loop(addr); +#else + assert(addr < MVEC_SIZE); + uint64_t loop_addr = addr; +#endif + + core->mvec[loop_addr] ^= MALL_FLAG; + core->aeva.data[loop_addr]++; + core->mall--; +} + +uint8_t mvec_get_byte(const struct Core *core, uint64_t addr) { + assert(core); + +#if defined(ARCH_SPEC_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(ARCH_SPEC_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 < ARCH_INST_CAP); + +#if defined(ARCH_SPEC_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(ARCH_SPEC_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_get_mb0_addr(core, pix); + uint64_t mb0s = arch_proc_get_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_get_mb1_addr(core, pix); + uint64_t mb1s = arch_proc_get_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(ARCH_SPEC_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 +// ---------------------------------------------------------------------------- +// The following implements SplitMix64 PRNG for cosmic-ray events: +// https://en.wikipedia.org/wiki/Xorshift +#if SEED != 0 +#if defined(COMMAND_NEW) +static 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 + +static 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(ARCH_SPEC_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 circular 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)); + + // Register process birth event + uint64_t child_addr = arch_proc_get_mb0_addr(core, core->plst); + uint64_t child_size = arch_proc_get_mb0_size(core, core->plst); + + for (uint64_t i = 0; i < child_size; i++) { + uint64_t addr = child_addr + i; +#if defined(ARCH_SPEC_MVEC_LOOP) + core->beva.data[mvec_loop(addr)]++; +#else + core->beva.data[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_null_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 +// ---------------------------------------------------------------------------- +static void core_save(const struct Core *core, FILE *f) { + assert(core); + assert(f); + + fwrite(&core->step, sizeof(uint64_t), 1, f); + 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->ivav, sizeof(uint64_t), SYNC_INTERVAL, f); + fwrite(core->iviv, sizeof(uint8_t), SYNC_INTERVAL, f); + + fwrite(core->mvec, sizeof(uint8_t), MVEC_SIZE, f); + fwrite(core->pvec, sizeof(struct Proc), core->pcap, f); + + fwrite(&core->amb0, sizeof(double), 1, f); + fwrite(&core->amb1, sizeof(double), 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->aeva.data, sizeof(uint64_t), MVEC_SIZE, f); + fwrite(core->eeva.data, sizeof(uint64_t), MVEC_SIZE, f); + fwrite(core->beva.data, sizeof(uint64_t), MVEC_SIZE, f); + + arch_core_save(core, f); +} + +#if defined(COMMAND_NEW) +static void core_assemble_ancestor(struct Core *core) { + assert(core); + +#if defined(ARCH_SPEC_MVEC_LOOP) + uint64_t addr = SALIS_UINT64_HALF; +#else + uint64_t addr = 0; +#endif + + uint8_t anc_bytes[] = ANC_BYTES; + + for (uint64_t i = 0; i < ANC_SIZE; 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]); + } + } +} + +void core_event_array_init(struct EventArray *eva) { + assert(eva); + eva->params.size = sizeof(uint64_t) * MVEC_SIZE; + eva->params.in = (Bytef *)eva->data; + eva->params.out = (Bytef *)eva->comp; +} + +static 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->psli = arch_proc_get_slice(core, core->pcur); + core->ivav = calloc(SYNC_INTERVAL, sizeof(uint64_t)); + core->iviv = calloc(SYNC_INTERVAL, sizeof(uint8_t)); + core->pvec = calloc(core->pcap, sizeof(struct Proc)); + assert(core->pvec); + + core_event_array_init(&core->aeva); + core_event_array_init(&core->eeva); + core_event_array_init(&core->beva); + + core_assemble_ancestor(core); + arch_core_init(core); +} +#endif + +#if defined(COMMAND_LOAD) +static void core_load(struct Core *core, FILE *f) { + assert(core); + assert(f); + + fread(&core->step, sizeof(uint64_t), 1, f); + 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->ivav = calloc(SYNC_INTERVAL, sizeof(uint64_t)); + core->iviv = calloc(SYNC_INTERVAL, sizeof(uint8_t)); + core->pvec = calloc(core->pcap, sizeof(struct Proc)); + assert(core->ivav); + assert(core->iviv); + assert(core->pvec); + + fread(core->ivav, sizeof(uint64_t), SYNC_INTERVAL, f); + fread(core->iviv, sizeof(uint8_t), SYNC_INTERVAL, f); + fread(core->mvec, sizeof(uint8_t), MVEC_SIZE, f); + fread(core->pvec, sizeof(struct Proc), core->pcap, f); + + fread(&core->amb0, sizeof(double), 1, f); + fread(&core->amb1, sizeof(double), 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); + + fread(core->aeva.data, sizeof(uint64_t), MVEC_SIZE, f); + fread(core->eeva.data, sizeof(uint64_t), MVEC_SIZE, f); + fread(core->beva.data, sizeof(uint64_t), MVEC_SIZE, f); + + arch_core_load(core, f); +} +#endif + +static 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 & 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(struct Core *core, uint8_t inst, uint64_t addr) { + assert(core); + assert(core->ivpt < SYNC_INTERVAL); + assert((inst & IPCM_FLAG) == 0); + uint8_t *iinst = &core->iviv[core->ivpt]; + uint64_t *iaddr = &core->ivav[core->ivpt]; + assert(*iinst == 0); + assert(*iaddr == 0); + *iinst = inst | IPCM_FLAG; + *iaddr = addr; +} + +static void core_step(struct Core *core) { + assert(core); + assert(core->psli); + assert(mvec_proc_is_live(core, core->pcur)); + + core_pull_ipcm(core); + core->ivpt++; + core->ivpt %= SYNC_INTERVAL; + + // Register execution event + uint64_t pcur_ip = arch_proc_get_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 { + assert(!mvec_is_in_mb0_of_proc(core, pcur_ip, core->pcur)); + assert(!mvec_is_in_mb1_of_proc(core, pcur_ip, core->pcur)); + assert(!mvec_is_alloc(core, pcur_ip)); + core->edea++; + } + +#if defined(ARCH_SPEC_MVEC_LOOP) + core->eeva.data[mvec_loop(pcur_ip)]++; +#else + if (pcur_ip < MVEC_SIZE) { + core->eeva.data[pcur_ip]++; + } +#endif + + // Step process and decrement time slice counter + arch_on_proc_step(core, core->pcur); + core->step++; + core->psli--; + + if (core->psli) { + return; + } + + // Time slice of process has ended; start time slice of next process + bool cycle_ended = core->pcur == core->plst; + core->pcur = cycle_ended ? core->pfst : core->pcur + 1; + core->psli = arch_proc_get_slice(core, core->pcur); + + if (cycle_ended) { + // Run the reaper at the end of every cycle + // Kill old processes until at least 50% of memory is not allocated + while (core->mall > MVEC_SIZE / 2 && core->pnum > 1) { + proc_kill(core); + } + +#if SEED != 0 + muta_cosmic_ray(core); +#endif + + core->cycl++; + } +} + +static int core_thread(struct Core *core) { + assert(core); + assert(core->step == g_step); + uint64_t step_group_size = 1; + + // Minimize calls to atomic_load_explicit by grouping calls + // to core_step. Check state of atomic variable once per period time_delta_ms, + // where STEP_GROUP_TIME_MIN_MS < time_delta_ms < STEP_GROUP_TIME_MAX_MS. + while (atomic_load_explicit(&g_running, memory_order_relaxed)) { + struct timespec time_start; + clock_gettime(CLOCK_MONOTONIC_COARSE, &time_start); + + for (uint64_t i = 0; i < step_group_size; i++) { + core_step(core); + + if (core->step % SYNC_INTERVAL == 0) { + log_trace("Exiting core thread at step %#lx: sync-interval reached", core->step); + log_trace("Step group size: %#lx", step_group_size); + return 0; + } + } + + struct timespec time_end; + clock_gettime(CLOCK_MONOTONIC_COARSE, &time_end); + int time_delta_ms = ((time_end.tv_sec - time_start.tv_sec) * 1000) + ((time_end.tv_nsec - time_start.tv_nsec) / 1000000); + + if (time_delta_ms < STEP_GROUP_TIME_MIN_MS) { + step_group_size *= 2; + } else if (time_delta_ms > STEP_GROUP_TIME_MAX_MS && step_group_size > 1) { + step_group_size /= 2; + } + } + + log_trace("Exiting core thread at step %#lx: simulation paused", core->step); + log_trace("Step group size: %#lx", step_group_size); + return 0; +} + +#if !defined(NDEBUG) +static void core_validate(struct Core *core) { + assert(core); + + // Validate core fields + assert(core->step == g_step); + assert(core->cycl <= g_step); + assert(core->pnum <= core->pcap); + assert(core->pnum == core->plst + 1 - core->pfst); + assert(core->plst >= core->pfst); + assert(core->pcur >= core->pfst && core->pcur <= core->plst); + assert(core->psli != 0); + + // Validate memory allocation + 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); + + // Validate IPCM state + for (uint64_t i = 0; i < SYNC_INTERVAL; i++) { + uint8_t iinst = core->iviv[i]; + + if ((iinst & IPCM_FLAG) == 0) { + uint64_t iaddr = core->ivav[i]; + assert(iinst == 0); + assert(iaddr == 0); + } + } + + assert(core->ivpt == g_step % SYNC_INTERVAL); + + // Validate VM architecture specifics + arch_validate_core(core); +} +#endif + +static void core_free(struct Core *core) { + assert(core); + assert(core->ivav); + assert(core->iviv); + assert(core->pvec); + + arch_core_free(core); + + free(core->pvec); + free(core->iviv); + free(core->ivav); + + core->pvec = NULL; + core->iviv = NULL; + core->ivav = NULL; +} + +// ---------------------------------------------------------------------------- +// [section] salis functions +// ---------------------------------------------------------------------------- +static void salis_sync(void) { + assert(g_step % SYNC_INTERVAL == 0); + log_trace("Synchronizing cores @step: %#lx; @sync: %#lx", g_step, g_sync); + +#if !defined(NDEBUG) + for (int i = 0; i < CORES; i++) { + assert(g_cores[i].ivpt == 0); + } +#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_sync++; +} + +static void salis_save(const char *path) { + assert(path); + log_info("Saving simulation state to '%s' on step %#lx", path, g_step); + + size_t size = 0; + char *in = NULL; + FILE *f = open_memstream(&in, &size); + assert(f); + + for (int i = 0; i < CORES; i++) { + core_save(&g_cores[i], f); + } + + fwrite(&g_step, sizeof(uint64_t), 1, f); + fwrite(&g_sync, sizeof(uint64_t), 1, f); + fclose(f); + + char *out = malloc(size); + assert(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); +} + +static void salis_auto_save(void) { + assert(g_step % AUTOSAVE_INTERVAL == 0); + int rem = snprintf(g_asav_pbuf, AUTOSAVE_NAME_LEN, "%s-%016lx", SIM_PATH, g_step); + assert(rem >= 0); + assert(rem < AUTOSAVE_NAME_LEN); + (void)rem; + salis_save(g_asav_pbuf); +} + +#if defined(COMMAND_NEW) +static void salis_push_data_header(void) { + assert(g_sim_db); + log_info("Creating core 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 core (" +#define FOR_CORE(i) \ + "step_" #i " int not null, " \ + "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, " \ + "aev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i " int not null, aev_" #i " blob, " \ + "eev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i " int not null, eev_" #i " blob, " \ + "bev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i " int not null, bev_" #i " blob, " + FOR_CORES +#undef FOR_CORE + "step int not null" + ");" + ); + + arch_push_data_header(); +} +#endif + +static int salis_measure_ambs_thread(struct Core *core) { + assert(core); + core->amb0 = 0.; + core->amb1 = 0.; + + for (uint64_t j = core->pfst; j < core->plst; j++) { + core->amb0 += (double)arch_proc_get_mb0_size(core, j); + core->amb1 += (double)arch_proc_get_mb1_size(core, j); + } + + core->amb0 /= core->pnum; + core->amb1 /= core->pnum; + return 0; +} + +static void salis_prepare_data_line(void) { + assert(g_sim_db); + + // Measure average memory block sizes and compress event-arrays in parallel + for (int i = 0; i < CORES; i++) { + struct Core *core = &g_cores[i]; + thrd_create(&core->amb_thrd, (thrd_start_t)salis_measure_ambs_thread, core); + thrd_create(&core->aeva.thrd, (thrd_start_t)comp_deflate, &core->aeva.params); + thrd_create(&core->eeva.thrd, (thrd_start_t)comp_deflate, &core->eeva.params); + thrd_create(&core->beva.thrd, (thrd_start_t)comp_deflate, &core->beva.params); + } + + for (int i = 0; i < CORES; i++) { + struct Core *core = &g_cores[i]; + thrd_join(core->amb_thrd, NULL); + thrd_join(core->aeva.thrd, NULL); + thrd_join(core->eeva.thrd, NULL); + thrd_join(core->beva.thrd, NULL); + core->aeva.blob_size = core->aeva.params.strm.total_out; + core->eeva.blob_size = core->eeva.params.strm.total_out; + core->beva.blob_size = core->beva.params.strm.total_out; + } + + arch_prepare_data_line(); +} + +static void salis_push_data_line(void) { + assert(g_sim_db); + assert(g_step % DATA_PUSH_INTERVAL == 0); + salis_prepare_data_line(); + log_info("Pushing row to core table in SQLite database"); + sql_exec( + NULL, NULL, + "insert into core (" +#define FOR_CORE(i) \ + "step_" #i ", " \ + "cycl_" #i ", " \ + "mall_" #i ", " \ + "pnum_" #i ", " \ + "pfst_" #i ", " \ + "plst_" #i ", " \ + "amb0_" #i ", " \ + "amb1_" #i ", " \ + "emb0_" #i ", " \ + "emb1_" #i ", " \ + "eliv_" #i ", " \ + "edea_" #i ", " \ + "aev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i ", " \ + "eev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i ", " \ + "bev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i ", " + FOR_CORES +#undef FOR_CORE + "step" + ") values (" +#define FOR_CORE(i) \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%f, " \ + "%f, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " \ + "%ld, " + FOR_CORES +#undef FOR_CORE + "%ld" + ");", +#define EVENT_ARRAY(core, index, ev) \ + g_cores[core].ev##_blob_size, +#define FOR_CORE(i) \ + g_cores[i].step, \ + g_cores[i].cycl, \ + g_cores[i].mall, \ + g_cores[i].pnum, \ + g_cores[i].pfst, \ + g_cores[i].plst, \ + g_cores[i].amb0, \ + g_cores[i].amb1, \ + g_cores[i].emb0, \ + g_cores[i].emb1, \ + g_cores[i].eliv, \ + g_cores[i].edea, \ + g_cores[i].aeva.blob_size, \ + g_cores[i].eeva.blob_size, \ + g_cores[i].beva.blob_size, + FOR_CORES +#undef FOR_CORE + g_step + ); + + // Store EVA data + int rem = snprintf(g_evas_pbuf, EVA_SAVE_NAME_LEN, "%s/evas-%016lx", SIM_EVAS, g_step); + assert(rem >= 0); + assert(rem < EVA_SAVE_NAME_LEN); + (void)rem; + + log_info("Saving event-array data to file: %s", g_evas_pbuf); + FILE *eva_file = fopen(g_evas_pbuf, "wb"); + assert(eva_file); + + for (int i = 0; i < CORES; i++) { + struct Core *core = &g_cores[i]; + fwrite(core->aeva.comp, sizeof(Bytef), core->aeva.blob_size, eva_file); + fwrite(core->eeva.comp, sizeof(Bytef), core->eeva.blob_size, eva_file); + fwrite(core->beva.comp, sizeof(Bytef), core->beva.blob_size, eva_file); + comp_deflate_end(&core->aeva.params); + comp_deflate_end(&core->eeva.params); + comp_deflate_end(&core->beva.params); + } + + arch_push_data_line(eva_file); + fclose(eva_file); +} + +#if defined(COMMAND_NEW) +void salis_init(void) { + log_info("Creating a new simulation"); + + uint64_t seed = SEED; + + for (int i = 0; i < CORES; i++) { + core_init(&g_cores[i], &seed); + } + + sql_open(); + salis_push_data_header(); + salis_push_data_line(); + salis_auto_save(); +} +#endif + +#if defined(COMMAND_LOAD) +void salis_load(void) { + log_info("Loading simulation from: %s", SIM_PATH); + 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(&g_cores[i], f); + } + + fread(&g_step, sizeof(uint64_t), 1, f); + fread(&g_sync, sizeof(uint64_t), 1, f); + fclose(f); + free(in); + free(out); + + sql_open(); +} +#endif + +static void salis_check_intervals(void) { + if (g_step % SYNC_INTERVAL == 0) { + salis_sync(); + } + + if (g_step % AUTOSAVE_INTERVAL == 0) { + salis_auto_save(); + } + + if (g_step % DATA_PUSH_INTERVAL == 0) { + salis_push_data_line(); + } +} + +#if !defined(NDEBUG) +static void salis_validate(void) { + log_trace("Validating simulation state"); + assert(g_step / SYNC_INTERVAL == g_sync); + + for (int i = 0; i < CORES; i++) { + core_validate(&g_cores[i]); + } +} +#endif + +static int salis_thread(void *data) { + assert(!data); + assert(atomic_load_explicit(&g_running, memory_order_relaxed)); + (void)data; + + struct timespec time_start; + clock_gettime(CLOCK_MONOTONIC_COARSE, &time_start); + uint64_t step_start = g_step; + + while (atomic_load_explicit(&g_running, memory_order_relaxed)) { + log_trace("Launching core threads"); + + for (int i = 0; i < CORES; i++) { + struct Core *core = &g_cores[i]; + thrd_create(&core->thrd, (thrd_start_t)core_thread, core); + } + + for (int i = 0; i < CORES; i++) { + struct Core *core = &g_cores[i]; + thrd_join(core->thrd, NULL); + } + + // Align step-count on all cores + for (int i = 0; i < CORES; i++) { + struct Core *core = &g_cores[i]; + + if (core->step > g_step) { + g_step = core->step; + } + } + + for (int i = 0; i < CORES; i++) { + struct Core *core = &g_cores[i]; + + while (core->step < g_step) { + core_step(core); + } + } + + salis_check_intervals(); + +#if !defined(NDEBUG) + salis_validate(); +#endif + + // Log simulation steps/s + struct timespec time_end; + clock_gettime(CLOCK_MONOTONIC_COARSE, &time_end); + int time_delta = time_end.tv_sec - time_start.tv_sec; + + if (time_delta > 0) { + uint64_t step_end = g_step; + uint64_t step_delta = step_end - step_start; + time_start = time_end; + step_start = step_end; + log_info("Running simulation @%#018lx steps/s", step_delta); + } + } + + return 0; +} + +void salis_start(void) { + assert(!atomic_load_explicit(&g_running, memory_order_relaxed)); + log_trace("Launching main thread"); + atomic_store_explicit(&g_running, true, memory_order_relaxed); + thrd_create(&g_thrd, (thrd_start_t)salis_thread, NULL); +} + +void salis_join(void) { + assert(atomic_load_explicit(&g_running, memory_order_relaxed)); + log_trace("Waiting for main thread"); + thrd_join(g_thrd, NULL); +} + +void salis_signal_stop(void) { + assert(atomic_load_explicit(&g_running, memory_order_relaxed)); + log_trace("Signaling main thread to stop"); + atomic_store_explicit(&g_running, false, memory_order_relaxed); +} + +void salis_step(uint64_t steps) { + assert(!atomic_load_explicit(&g_running, memory_order_relaxed)); + log_trace("Stepping simulation %lu times", steps); + + for (uint64_t i = 0; i < steps; i++) { + for (int i = 0; i < CORES; i++) { + core_step(&g_cores[i]); + } + + g_step++; + + salis_check_intervals(); + } + +#if !defined(NDEBUG) + salis_validate(); +#endif +} + +void salis_free(void) { + assert(!atomic_load_explicit(&g_running, memory_order_relaxed)); + log_info("Freeing simulation resources"); + salis_save(SIM_PATH); + sql_close(); + + for (int i = 0; i < CORES; i++) { + core_free(&g_cores[i]); + } +} diff --git a/core/salis.h b/core/salis.h new file mode 100644 index 0000000..e61752b --- /dev/null +++ b/core/salis.h @@ -0,0 +1,147 @@ +// file : core/salis.h +// project : Salis-VM +// author : Paul Oliver + +#pragma once + +// index: +// [section] structs +// [section] extern globals +// [section] memory vector declarations +// [section] mutator declarations +// [section] process declarations +// [section] core declarations +// [section] salis declarations + +// ---------------------------------------------------------------------------- +// [section] macros +// ---------------------------------------------------------------------------- +#define SALIS_UINT64_HALF 0x8000000000000000ul +#define SALIS_EVENT_ARRAY_SIZE_COL_MARKER "_evasize_" + +// ---------------------------------------------------------------------------- +// [section] structs +// ---------------------------------------------------------------------------- +struct Proc { +#define ARCH_SPEC_PROC_FIELD(type, name) type name; + ARCH_SPEC_PROC_FIELDS +#undef ARCH_SPEC_PROC_FIELD +}; + +struct EventArray { + uint64_t blob_size; + uint64_t data[MVEC_SIZE]; + uint64_t comp[MVEC_SIZE]; + struct DeflateParams params; + thrd_t thrd; +}; + +struct Core { + uint64_t step; + 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; + + uint64_t ivpt; + uint64_t *ivav; + uint8_t *iviv; + + uint8_t mvec[MVEC_SIZE]; // The World + struct Proc *pvec; // The organisms + + // Data aggregation fields + thrd_t amb_thrd; + double amb0; + double amb1; + + uint64_t emb0; + uint64_t emb1; + uint64_t eliv; + uint64_t edea; + + struct EventArray aeva; + struct EventArray eeva; + struct EventArray beva; + + // VM architecture specific fields +#define ARCH_SPEC_CORE_FIELD(type, name) type name; + ARCH_SPEC_CORE_FIELDS +#undef ARCH_SPEC_CORE_FIELD + + thrd_t thrd; +}; + +// ---------------------------------------------------------------------------- +// [section] extern globals +// ---------------------------------------------------------------------------- +extern struct Core g_cores[CORES]; +extern uint64_t g_step; + +// ---------------------------------------------------------------------------- +// [section] memory vector declarations +// ---------------------------------------------------------------------------- +#if defined(ARCH_SPEC_MVEC_LOOP) +uint64_t mvec_loop(uint64_t addr); +#endif + +bool mvec_is_alloc(const struct Core *core, uint64_t addr); +void mvec_alloc(struct Core *core, uint64_t addr); +void mvec_free(struct Core *core, uint64_t addr); + +uint8_t mvec_get_byte(const struct Core *core, uint64_t addr); +uint8_t mvec_get_inst(const struct Core *core, uint64_t addr); +void mvec_set_inst(struct Core *core, uint64_t addr, uint8_t inst); + +#if defined(ARCH_SPEC_MUTA_FLIP) +void mvec_flip_bit(struct Core *core, uint64_t addr, int bit); +#endif + +bool mvec_proc_is_live(const struct Core *core, uint64_t pix); +bool mvec_is_in_mb0_of_proc(const struct Core *core, uint64_t addr, uint64_t pix); +bool mvec_is_in_mb1_of_proc(const struct Core *core, uint64_t addr, uint64_t pix); +bool mvec_is_proc_owner(const struct Core *core, uint64_t addr, uint64_t pix); +uint64_t mvec_get_owner(const struct Core *core, uint64_t addr); + +// ---------------------------------------------------------------------------- +// [section] mutator declarations +// ---------------------------------------------------------------------------- +uint64_t muta_next(struct Core *core); +void muta_cosmic_ray(struct Core *core); + +// ---------------------------------------------------------------------------- +// [section] process declarations +// ---------------------------------------------------------------------------- +void proc_new(struct Core *core, const struct Proc *proc); +void proc_kill(struct Core *core); +const struct Proc *proc_get(const struct Core *core, uint64_t pix); +struct Proc *proc_fetch(struct Core *core, uint64_t pix); + +// ---------------------------------------------------------------------------- +// [section] core declarations +// ---------------------------------------------------------------------------- +void core_event_array_init(struct EventArray *eva); +void core_push_ipcm(struct Core *core, uint8_t inst, uint64_t addr); + +// ---------------------------------------------------------------------------- +// [section] salis declarations +// ---------------------------------------------------------------------------- +#if defined(COMMAND_NEW) +void salis_init(void); +#endif + +#if defined(COMMAND_LOAD) +void salis_load(void); +#endif + +void salis_start(void); +void salis_join(void); +void salis_signal_stop(void); +void salis_step(uint64_t steps); +void salis_free(void); diff --git a/core/sql.c b/core/sql.c new file mode 100644 index 0000000..59fc38e --- /dev/null +++ b/core/sql.c @@ -0,0 +1,105 @@ +// file : core/sql.c +// project : Salis-VM +// author : Paul Oliver + +// index: +// [section] includes +// [section] macros +// [section] globals +// [section] definitions + +// ---------------------------------------------------------------------------- +// [section] includes +// ---------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include + +#include "logger.h" +#include "sql.h" + +// ---------------------------------------------------------------------------- +// [section] macros +// ---------------------------------------------------------------------------- +#define DATA_PUSH_BUSY_TIMEOUT 600000 + +// ---------------------------------------------------------------------------- +// [section] globals +// ---------------------------------------------------------------------------- +sqlite3 *g_sim_db; + +// ---------------------------------------------------------------------------- +// [section] definitions +// ---------------------------------------------------------------------------- +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); +} + +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_attention("SQLite database returned error %d with message:", sql_res); + log_attention(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); +} diff --git a/core/sql.h b/core/sql.h new file mode 100644 index 0000000..675d096 --- /dev/null +++ b/core/sql.h @@ -0,0 +1,21 @@ +// file : core/sql.h +// project : Salis-VM +// author : Paul Oliver + +#pragma once + +// index: +// [section] extern globals +// [section] declarations + +// ---------------------------------------------------------------------------- +// [section] extern globals +// ---------------------------------------------------------------------------- +extern sqlite3 *g_sim_db; + +// ---------------------------------------------------------------------------- +// [section] declarations +// ---------------------------------------------------------------------------- +void sql_open(void); +void sql_close(void); +void sql_exec(void (*callback)(sqlite3_stmt *sql_stmt, void *data), void *data, const char *sql_format, ...); diff --git a/docs/flowchart.odg b/docs/flowchart.odg new file mode 100644 index 0000000..73c4b26 Binary files /dev/null and b/docs/flowchart.odg differ diff --git a/docs/flowchart.svg b/docs/flowchart.svg new file mode 100644 index 0000000..b3d9284 --- /dev/null +++ b/docs/flowchart.svg @@ -0,0 +1,844 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salis.py b/salis.py new file mode 100755 index 0000000..683da7d --- /dev/null +++ b/salis.py @@ -0,0 +1,328 @@ +#!/usr/bin/env -S PYTHONDONTWRITEBYTECODE=1 python + +# file : salis.py +# project : Salis-VM +# author : Paul Oliver + +# index: +# [section] imports +# [section] argument parser +# [section] build class +# [section] logger +# [section] options dict formatter +# [section] entry-point; source options +# [section] load instruction set +# [section] assemble ancestor +# [section] defines +# [section] build and launch + +# ------------------------------------------------------------------------------ +# [section] imports +# ------------------------------------------------------------------------------ +import ctypes +import json +import os +import random +import shutil +import signal +import subprocess +import sys + +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") + +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 1024 <= ival <= 49151: 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), 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", "force", (new,), fmt_id, {"action": "store_true", "help": "overwrite existing simulation of given name", "required": False}), + ("g", "c-compiler", (new, load), fmt_id, {"choices": ("clang", "gcc", "tcc"), "help": "C compiler to use", "default": "gcc", "required": False, "type": str}), + ("H", "home", (new, load), fmt_id, {"metavar": "PATH", "help": "salis home directory", "default": f"{os.environ["HOME"]}/.salis", "required": False, "type": str}), + ("l", "log-trace", (new, load), fmt_id, {"action": "store_true", "help": "log verbose trace messages", "required": False}), + ("M", "muta-pow", (new,), fmt_id, {"metavar": "POW", "help": "mutator range exponent; each step a cosmic ray hits 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), fmt_id, {"metavar": "NAME", "help": "name of new or loaded simulation", "default": "def.sim", "required": False, "type": str}), + ("o", "optimized", (new, load), fmt_id, {"action": "store_true", "help": "build with optimizations", "required": False}), + ("p", "pre-cmd", (new, load), 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), fmt_id, {"action": "store_true", "help": "keep temporary directory on exit", "required": False}), + ("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", "vm-arch", (new,), fmt_id, {"metavar": "ARCH", "help": "VM architecture", "default": "null", "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; interval = 2^{POW} >= {sync_pow}", "default": 36, "required": False, "type": pos}), +) + +[sub_parser.add_argument(f"-{sopt}", f"--{lopt}", **kwargs) for sopt, lopt, sub_parsers, _, kwargs in options for sub_parser in sub_parsers] +args = parser.parse_args() + +# ------------------------------------------------------------------------------ +# [section] build class +# ------------------------------------------------------------------------------ +class Build: + def __init__(self, path, is_library=False, is_cpp=False, flags=None, defines=None, links=None): + self.path = path + self.is_library = is_library + self.is_cpp = is_cpp + + self.flags = flags or [] + self.defines = defines or [] + self.links = links or [] + + self.basename = os.path.splitext(os.path.basename(self.path))[0] + self.extension = ".so" if self.is_library else "" + self.tempdir = TemporaryDirectory(prefix="salis_", delete=not args.keep_temp_dir) + self.binfile = f"{self.tempdir.name}/{self.basename}{self.extension}" + + self.flags += ["-Wall", "-Wextra", "-Werror", "-Wmissing-prototypes", "-Wmissing-declarations", "-Wno-overlength-strings", "-pedantic"] + self.flags += ["-fPIC", "-shared"] if self.is_library else [] + self.flags += ["-O3", "-flto", "-march=native", "-mtune=native"] if args.optimized else ["-ggdb"] + self.defines += ["-DNDEBUG"] if args.optimized else [] + + self.compiler = args.cpp_compiler if self.is_cpp else args.c_compiler + self.build_cmd = [self.compiler, *self.flags, *self.defines, *self.links, self.path, "-o", self.binfile] + + def build(self): + subprocess.run(self.build_cmd, check=True) + + # Clear exec-stack on dynamic libraries when using TCC. + # Otherwise python will complain when loading the library with CDLL. + # NOTE: Using TCC thus requires `patchelf` to be accessible. + if self.is_library and args.c_compiler == "tcc": + subprocess.run(["patchelf", "--clear-execstack", self.binfile], check=True) + + def log(self, log): + log.trace(f"Built source '{self.path}' to output: {self.binfile}") + log.trace(f"Using build command: {self.build_cmd}") + + def run(self): + run_cmd = args.pre_cmd.split() if args.pre_cmd else [] + run_cmd.append(self.binfile) + proc = subprocess.Popen(run_cmd, stdout=sys.stdout, stderr=sys.stderr) + + # When a signal is received (e.g. SIGINT), is propagates through the entire process group. + # Handle signal on the interpreter and allow the simulator to shut down gracefully. + try: + signal.pause() + except KeyboardInterrupt: + proc.wait() + + code = proc.returncode + + if code != 0: raise RuntimeError(f"Binary returned code: {code}") + +# ------------------------------------------------------------------------------ +# [section] logger +# ------------------------------------------------------------------------------ +logger_defines = ["-DLOG_TRACE"] if args.log_trace else [] +logger_shared = Build("core/logger.c", is_library=True, defines=logger_defines) +logger_shared.build() +logger_dll = ctypes.CDLL(logger_shared.binfile) + +# Pull in logging functions from C +# This way there's just a single logging system to care about +log = SimpleNamespace() +log.trace = lambda msg: logger_dll.log_trace(msg.encode()) +log.info = lambda msg: logger_dll.log_info(msg.encode()) +log.attention = lambda msg: logger_dll.log_attention(msg.encode()) +logger_shared.log(log) + +# ------------------------------------------------------------------------------ +# [section] options dict formatter +# ------------------------------------------------------------------------------ +fmt_h2u = lambda field: field.replace("-", "_") +fmt_u2h = lambda field: field.replace("_", "-") + +def fmt_field(field, val): + for _, lopt, _, fmt, _ in options: + if lopt == fmt_u2h(field): + return fmt(val) + + return fmt_id(val) + +def fmt_opts(opts): + opts_out = {field: fmt_field(field, val) for field, val in opts.items()} + return json.dumps(opts_out, indent=2) + +# ------------------------------------------------------------------------------ +# [section] entry-point; source options +# ------------------------------------------------------------------------------ +log.info(f"Called '{prog} {args.command}' with the following options: {fmt_opts(vars(args))}") +paths = SimpleNamespace() + +def gen_paths(): + paths.sim_dir = f"{args.home}/{args.name}" + paths.sim_data = f"{paths.sim_dir}/{args.name}.sqlite3" + paths.sim_evas = f"{paths.sim_dir}/evas" + paths.sim_opts = f"{paths.sim_dir}/opts.json" + paths.sim_path = f"{paths.sim_dir}/{args.name}" + +def source_options(): + if not os.path.isdir(paths.sim_dir): + raise RuntimeError(f"No simulation found named {args.name}") + + with open(paths.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 '{paths.sim_opts}': {fmt_opts(opts)}") + +# Create new simulation +if args.command == "new": + gen_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 0 < args.auto_save_pow < args.sync_pow: + raise RuntimeError("Autosave power must be equal or greater than core sync power") + + if os.path.isdir(paths.sim_dir) and args.force: + log.attention(f"Force flag used! Wiping old simulation at: {paths.sim_dir}") + shutil.rmtree(paths.sim_dir) + + if os.path.isdir(paths.sim_dir): + raise RuntimeError(f"Simulation directory found at: {paths.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: {paths.sim_dir}") + log.info(f"Creating new event-array storage directory at: {paths.sim_evas}") + log.info(f"Creating configuration file at: {paths.sim_opts}") + os.mkdir(paths.sim_dir) + os.mkdir(paths.sim_evas) + + opts = {fmt_h2u(lopt): getattr(args, fmt_h2u(lopt)) for _, lopt, sub_parsers, _, kwargs in options if new in sub_parsers and load not in sub_parsers} + + with open(paths.sim_opts, "w") as f: + f.write(f"{fmt_opts(opts)}\n") + +# Load saved simulation +if args.command == "load": + gen_paths() + source_options() + +# ------------------------------------------------------------------------------ +# [section] load instruction set +# ------------------------------------------------------------------------------ +arch_inst_path = f"arch/{args.vm_arch}/arch_inst.c" +arch_build = Build(arch_inst_path, is_library=True) +arch_build.build() +arch_build.log(log) + +arch_dll = ctypes.CDLL(arch_build.binfile) +arch_dll.arch_inst_cap.restype = ctypes.c_int +arch_dll.arch_inst_mnemonic.argtypes = [ctypes.c_uint8] +arch_dll.arch_inst_mnemonic.restype = ctypes.c_char_p + +# ------------------------------------------------------------------------------ +# [section] assemble ancestor +# ------------------------------------------------------------------------------ +anc_path = f"{args.anc_path}/{args.vm_arch}/{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: line and not line.startswith(";") and not line.isspace(), lines) +lines = map(lambda line: tuple(line.split()), lines) + +inst_cap = arch_dll.arch_inst_cap() +mnemo_map = {tuple(arch_dll.arch_inst_mnemonic(byte).decode().split()): byte for byte in range(inst_cap)} +anc_bytes = [mnemo_map[line] for line in lines] +anc_repr = f"{{{",".join(map(str, anc_bytes))}}}" + +log.info(f"Compiled ancestor file '{anc_path}' into byte array: {anc_repr}") + +# ------------------------------------------------------------------------------ +# [section] defines +# ------------------------------------------------------------------------------ +defines = [ + f"-DANC=\"{args.anc}\"", + f"-DANC_BYTES={anc_repr}", + f"-DANC_SIZE={len(anc_bytes)}", + f"-DARCH=\"{args.vm_arch}\"", + f"-DAUTOSAVE_INTERVAL={2 ** args.auto_save_pow}ul", + f"-DAUTOSAVE_NAME_LEN={len(paths.sim_path) + 18}", + f"-DCLONES={args.clones}", + f"-DCOMMAND_{args.command.upper()}", + f"-DCORES={args.cores}", + f"-DDATA_PUSH_INTERVAL={2 ** args.data_push_pow}ul", + f"-DDATA_PUSH_PATH=\"{paths.sim_data}\"", + f"-DEVA_SAVE_NAME_LEN={len(paths.sim_evas) + 23}", + f"-DFOR_CORES={" ".join(f"FOR_CORE({i})" for i in range(args.cores))}", + f"-DMUTA_RANGE={2 ** args.muta_pow}ul", + f"-DMVEC_SIZE={2 ** args.mvec_pow}ul", + f"-DNAME=\"{args.name}\"", + f"-DSEED={args.seed}ul", + f"-DSIM_DATA=\"{paths.sim_data}\"", + f"-DSIM_DIR=\"{paths.sim_dir}\"", + f"-DSIM_EVAS=\"{paths.sim_evas}\"", + f"-DSIM_OPTS=\"{paths.sim_opts}\"", + f"-DSIM_PATH=\"{paths.sim_path}\"", + f"-DSYNC_INTERVAL={2 ** args.sync_pow}ul", +] + +# ------------------------------------------------------------------------------ +# [section] build and launch +# ------------------------------------------------------------------------------ +ui_path = f"{args.ui_path}/{args.ui}" +ui_flags = [f"-Iarch/{args.vm_arch}", "-Icore", f"arch/{args.vm_arch}/arch.c", arch_inst_path, "core/compress.c", "core/logger.c", "core/salis.c", "core/sql.c"] +ui_defines = defines + logger_defines + +with open(f"{ui_path}/links.json", "r") as f: + ui_links = json.load(f) + +ui_links += ["-lsqlite3", "-lz"] +ui_build = Build(f"{ui_path}/ui.c", flags=ui_flags, defines=ui_defines, links=ui_links) +ui_build.build() +ui_build.log(log) + +if not args.build_only: + ui_build.run() diff --git a/ui/daemon/links.json b/ui/daemon/links.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/ui/daemon/links.json @@ -0,0 +1 @@ +[] diff --git a/ui/daemon/ui.c b/ui/daemon/ui.c new file mode 100644 index 0000000..4fea1ef --- /dev/null +++ b/ui/daemon/ui.c @@ -0,0 +1,39 @@ +// file : ui/daemon/ui.c +// project : Salis-VM +// author : Paul Oliver + +#include +#include +#include +#include +#include + +#include "arch_spec.h" +#include "compress.h" +#include "logger.h" +#include "salis.h" + +static void quit(int signo) { + log_attention("Signal %d received, will stop simulator soon", signo); + salis_signal_stop(); +} + +int main(void) { + log_info("Initializing daemon UI"); + log_info("Installing signal handlers"); + signal(SIGINT, quit); + signal(SIGTERM, quit); + +#if defined(COMMAND_NEW) + salis_init(); +#endif + +#if defined(COMMAND_LOAD) + salis_load(); +#endif + + salis_start(); + salis_join(); + salis_free(); + return 0; +} -- cgit v1.3.1