diff options
| -rw-r--r-- | README.md | 30 | ||||
| -rw-r--r-- | ancs/dummy/0123.asm | 6 | ||||
| -rw-r--r-- | ancs/salis-v1/1n.asm | 5 | ||||
| -rw-r--r-- | ancs/salis-v1/55b.asm | 74 | ||||
| -rw-r--r-- | arch/dummy/arch.j2.c | 130 | ||||
| -rw-r--r-- | arch/dummy/arch_vars.py | 21 | ||||
| -rw-r--r-- | arch/salis-v1/arch.j2.c | 890 | ||||
| -rw-r--r-- | arch/salis-v1/arch_vars.py | 98 | ||||
| -rw-r--r-- | bench.j2.c (renamed from src/bench.c) | 23 | ||||
| -rw-r--r-- | core.j2.c | 838 | ||||
| -rwxr-xr-x | salis | 406 | ||||
| -rwxr-xr-x | salis.py | 356 | ||||
| -rw-r--r-- | src/arch/dummy.c | 152 | ||||
| -rw-r--r-- | src/arch/salis-v1.c | 875 | ||||
| -rw-r--r-- | src/graphics.c | 238 | ||||
| -rw-r--r-- | src/salis.c | 811 | ||||
| -rw-r--r-- | ui/curses/ui.j2.c (renamed from src/ui/curses.c) | 564 | ||||
| -rw-r--r-- | ui/curses/ui_vars.py | 2 | ||||
| -rw-r--r-- | ui/daemon/ui.j2.c (renamed from src/ui/daemon.c) | 29 | ||||
| -rw-r--r-- | ui/daemon/ui_vars.py | 2 |
20 files changed, 2786 insertions, 2764 deletions
@@ -7,8 +7,8 @@ *SALIS* is a platform for conducting artificial life experiments. It enables the development of Tierra-like virtual machines, with certain limitations. For newcomers, I recommend exploring Tierra first. The following resources provide -valuable context and insight into both the motivations and implementation of -both Tierra and this project: +valuable context and insight into the motivations and implementation of both +Tierra and this project: - [Video about Tierra](https://www.youtube.com/watch?v=Wl5rRGVD0QI) - [Read about Tierra](https://tomray.me/pubs/doc/index.html#What) @@ -23,28 +23,28 @@ with the original Tierra simulator, check out the following resources: - [SALIS V1 introductory playlist](https://www.youtube.com/watch?v=jCFmOCvy6po&list=PLrEmYrpTcDJY2NdGL6B7wIRbKGp_6NkxY) ## Usage -*SALIS* simulations are initialized using the provided `salis` shell script. +*SALIS* simulations are initialized using the provided `salis.py` python script. Use `salis new [...]` to start new simulations and `salis load [...]` to load saved simulations. For a full list of available arguments for each command, run `salis new --help` and `salis load --help`, respectively. -The shell script compiles a temporary executable on the fly (compilation +The python script compiles a temporary executable on the fly (compilation typically takes less than a second) based on the specified arguments and launches it immediately. -Different architectures can be implemented as standalone C files in the -`src/arch/` directory. When creating a new simulation, you can select a -specific architecture using the `--arch` argument. +Different architectures can be implemented as standalone C templates in the +`arch/` directory. When creating a new simulation, you can select a specific +architecture using the `--arch` argument. -Similarly, different user interfaces are implemented as C files within the -`src/ui/` directory. For example, the `curses.c` UI launches a terminal-based +Similarly, different user interfaces are implemented as C templates within the +`ui/` directory. For example, the `curses` UI launches a terminal-based simulation visualizer, allowing easy exploration of *SALIS* memory cores and -processes. In contrast, the `daemon.c` UI provides minimal output, making it +processes. In contrast, the `daemon` UI provides minimal output, making it ideal for running *SALIS* as a background service. Unlike the `--arch` argument, you can choose a different `--ui` argument each time you load a saved simulation. -For example, the following command will launch a new *SALIS* simulation with 4 +As an example, the following command will launch a new *SALIS* simulation with 4 copies of the `55a` ancestor organisms pre-compiled in each memory core. It will use the `salis-v1` architecture, run on 8 memory cores, with each core having a size of 2^22 bytes. The PRNG seed is set to `123456789`: @@ -52,9 +52,15 @@ having a size of 2^22 bytes. The PRNG seed is set to `123456789`: user@host$ ./salis new -A55a -asalis-v1 -c8 -C4 -m22 -nworld-1 -s123456789 -o ``` -Upon exit, the simulation data will be automatically saved to +Upon exit, the simulation state will be automatically saved to `${HOME}/.salis/world-1/`. As long as the contents of this directory are not removed, you can reload the saved simulation with the following command: ```console user@host$ ./salis load -n world-1 -o ``` + +## Requirements +- C compiler - ideally GCC +- Python3 +- Jinja2 - installed globally or within an active virtual environment + diff --git a/ancs/dummy/0123.asm b/ancs/dummy/0123.asm new file mode 100644 index 0000000..b32d8bf --- /dev/null +++ b/ancs/dummy/0123.asm @@ -0,0 +1,6 @@ +; Dummy ancestor for testing + +dummy 00 +dummy 01 +dummy 02 +dummy 03 diff --git a/ancs/salis-v1/1n.asm b/ancs/salis-v1/1n.asm deleted file mode 100644 index b441922..0000000 --- a/ancs/salis-v1/1n.asm +++ /dev/null @@ -1,5 +0,0 @@ -; Project: Salis -; Author: Paul Oliver -; Email: contact@pauloliver.dev - -noop diff --git a/ancs/salis-v1/55b.asm b/ancs/salis-v1/55b.asm deleted file mode 100644 index 7c830e7..0000000 --- a/ancs/salis-v1/55b.asm +++ /dev/null @@ -1,74 +0,0 @@ -; Project: Salis -; Author: Paul Oliver -; Email: contact@pauloliver.dev - -; Based on the original 55.anc ancestor from salis-v1: -; https://git.pauloliver.dev/salis-v1/tree/bin/genomes/55.anc -; This organism replicates bidirectionally. - -; begin template -lokb - -; measure gene -adrb -keyb -adrf -keyb -nop1 -incn -nop1 -subn -nop1 -nop1 - -; alloc gene -lokc -notn -nop3 -pshn -nop1 -pshn -nop3 -ifnz -nop3 -jmpf -keyd -allb -nop1 -nop2 -jmpf -keye -lokd -allf -nop1 -nop2 - -; copy gene -loke -load -nop0 -nop3 -wrte -nop2 -nop3 -incn -incn -nop2 -decn -nop1 -ifnz -nop1 -jmpb -keye - -; split gene -splt -popn -nop3 -popn -nop1 -jmpb -keyc - -; end template -lokb diff --git a/arch/dummy/arch.j2.c b/arch/dummy/arch.j2.c new file mode 100644 index 0000000..d62d411 --- /dev/null +++ b/arch/dummy/arch.j2.c @@ -0,0 +1,130 @@ +// Author: Paul Oliver <contact@pauloliver.dev> +// Project: salis-v3 + +// Defines a minimal viable architecture for the Salis VM. +// Useful for debugging and benchmarking. May be used as a template when +// implementing a new architecture. + +{% if args.command in ["bench", "new"] and anc_bytes is defined %} +void arch_anc_init(struct Core *core) { + assert(core); + + {% if arch_vars.mvec_loop %} + uint64_t addr = {{ uint64_half }}; + {% else %} + uint64_t addr = 0; + {% endif %} + + for (uint64_t i = 0; i < {{ args.clones }}; ++i) { + uint64_t addr_clone = addr + (({{ mvec_size }} / {{ args.clones }})) * i; + + struct Proc *panc = proc_fetch(core, i); + + panc->mb0a = addr_clone; + panc->mb0s = {{ anc_bytes|length }}; + panc->ip = addr_clone; + panc->sp = addr_clone; + } +} +{% endif %} + +uint64_t arch_proc_mb0_addr(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->mb0a; +} + +uint64_t arch_proc_mb0_size(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->mb0s; +} + +uint64_t arch_proc_mb1_addr(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->mb1a; +} + +uint64_t arch_proc_mb1_size(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->mb1s; +} + +uint64_t arch_proc_ip_addr(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->ip; +} + +uint64_t arch_proc_sp_addr(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->sp; +} + +uint64_t arch_proc_slice(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + (void)core; + (void)pix; + + return 1; +} + +void arch_on_proc_kill(struct Core *core) { + assert(core); + assert(core->pnum > 1); + + (void)core; +} + +void arch_proc_step(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + (void)core; + (void)pix; + + return; +} + +{% if not args.optimized %} +void arch_validate_proc(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + (void)core; + (void)pix; + + assert(true); +} +{% endif %} + +wchar_t arch_symbol(uint8_t inst) { + switch (inst) { + {% for i in arch_vars.inst_set %} + case {{ loop.index0 }}: return L'{{ i[1] }}'; + {% endfor %} + } +} + +const char *arch_mnemonic(uint8_t inst) { + switch (inst) { + {% for i in arch_vars.inst_set %} + case {{ loop.index0 }}: return "{{ i[0]|join(' ') }}"; + {% endfor %} + } +} + +{% if data_push_path is defined %} +void arch_push_data_header() { + assert(g_sim_data); +} + +void arch_push_data_line() { + assert(g_sim_data); +} +{% endif %} diff --git a/arch/dummy/arch_vars.py b/arch/dummy/arch_vars.py new file mode 100644 index 0000000..3a18200 --- /dev/null +++ b/arch/dummy/arch_vars.py @@ -0,0 +1,21 @@ +core_fields = [] +mvec_loop = True + +proc_fields = [ + ("uint64_t", "ip"), + ("uint64_t", "sp"), + ("uint64_t", "mb0a"), + ("uint64_t", "mb0s"), + ("uint64_t", "mb1a"), + ("uint64_t", "mb1s"), +] + +inst_set = [ + (["dummy", f"{i:02x}"], symbol) + for i, symbol in enumerate( + "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟" + "⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿" + "⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟" + "⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿" + ) +] diff --git a/arch/salis-v1/arch.j2.c b/arch/salis-v1/arch.j2.c new file mode 100644 index 0000000..aaa5284 --- /dev/null +++ b/arch/salis-v1/arch.j2.c @@ -0,0 +1,890 @@ +// Author: Paul Oliver <contact@pauloliver.dev> +// Project: Salis + +// Based on the original salis-v1 VM architecture: +// https://git.pauloliver.dev/salis-v1/about/ + +{% set inst_count = arch_vars.inst_set|length %} + +enum { + {% for i in arch_vars.inst_set %} + {{ i[0]|join(' ') }}, + {% endfor %} +}; + +{% if args.command in ["bench", "new"] and anc_bytes is defined %} +void arch_anc_init(struct Core *core) { + assert(core); + + {% if arch_vars.mvec_loop %} + uint64_t addr = {{ uint64_half }}; + {% else %} + uint64_t addr = 0; + {% endif %} + + for (uint64_t i = 0; i < {{ args.clones }}; ++i) { + uint64_t addr_clone = addr + (({{ mvec_size }} / {{ args.clones }})) * i; + + struct Proc *panc = proc_fetch(core, i); + + panc->mb0a = addr_clone; + panc->mb0s = {{ anc_bytes|length }}; + panc->ip = addr_clone; + panc->sp = addr_clone; + } +} +{% endif %} + +uint64_t arch_proc_mb0_addr(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->mb0a; +} + +uint64_t arch_proc_mb0_size(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->mb0s; +} + +uint64_t arch_proc_mb1_addr(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->mb1a; +} + +uint64_t arch_proc_mb1_size(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->mb1s; +} + +uint64_t arch_proc_ip_addr(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->ip; +} + +uint64_t arch_proc_sp_addr(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + return proc_get(core, pix)->sp; +} + +uint64_t arch_proc_slice(const struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + (void)core; + (void)pix; + + return 1; +} + +void _free_memory_block(struct Core *core, uint64_t addr, uint64_t size) { + assert(core); + assert(size); + + for (uint64_t i = 0; i < size; ++i) { + mvec_free(core, addr + i); + } +} + +void arch_on_proc_kill(struct Core *core) { + assert(core); + assert(core->pnum > 1); + + struct Proc *pfst = proc_fetch(core, core->pfst); + + _free_memory_block(core, pfst->mb0a, pfst->mb0s); + + if (pfst->mb1s) { + _free_memory_block(core, pfst->mb1a, pfst->mb1s); + } + + memcpy(pfst, &g_dead_proc, sizeof(struct Proc)); +} + +uint8_t _get_inst(const struct Core *core, uint64_t addr) { + assert(core); + + return mvec_get_inst(core, addr) % {{ inst_count }}; +} + +void _increment_ip(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + + proc->ip++; + proc->sp = proc->ip; +} + +bool _is_between(uint8_t inst, uint8_t lo, uint8_t hi) { + assert(inst < {{ inst_count }}); + assert(lo < {{ inst_count }}); + assert(hi < {{ inst_count }}); + assert(lo < hi); + + return (inst >= lo) && (inst <= hi); +} + +bool _is_key(uint8_t inst) { + assert(inst < {{ inst_count }}); + + return _is_between(inst, keya, keyp); +} + +bool _is_lock(uint8_t inst) { + assert(inst < {{ inst_count }}); + + return _is_between(inst, loka, lokp); +} + +bool _is_rmod(uint8_t inst) { + assert(inst < {{ inst_count }}); + + return _is_between(inst, nop0, nop3); +} + +bool _key_lock_match(uint8_t key, uint8_t lock) { + assert(key < {{ inst_count }}); + assert(lock < {{ inst_count }}); + assert(_is_key(key)); + + return (key - keya) == (lock - loka); +} + +bool _seek(struct Core *core, uint64_t pix, bool fwrd) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint8_t next = _get_inst(core, proc->ip + 1); + + if (!_is_key(next)) { + _increment_ip(core, pix); + return false; + } + + uint8_t spin = _get_inst(core, proc->sp); + + if (_key_lock_match(next, spin)) { + return true; + } + + if (fwrd) { + proc->sp++; + } else { + proc->sp--; + } + + return false; +} + +void _jump(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + + {% if not args.optimized %} + uint8_t next = _get_inst(core, proc->ip + 1); + uint8_t spin = _get_inst(core, proc->sp); + assert(_is_key(next)); + assert(_is_lock(spin)); + assert(_key_lock_match(next, spin)); + {% endif %} + + proc->ip = proc->sp; +} + +void _get_reg_addr_list(struct Core *core, uint64_t pix, uint64_t **rlist, int rcount, bool offset) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + assert(rlist); + assert(rcount); + assert(rcount < 4); + + struct Proc *proc = proc_fetch(core, pix); + uint64_t madr = proc->ip + (offset ? 2 : 1); + + for (int i = 0; i < rcount; ++i) { + rlist[i] = &proc->r0x; + } + + for (int i = 0; i < rcount; ++i) { + uint64_t mnxt = madr + i; + uint8_t mins = _get_inst(core, mnxt); + + if (!_is_rmod(mins)) { + break; + } + + switch (mins) { + case nop0: + rlist[i] = &proc->r0x; + break; + case nop1: + rlist[i] = &proc->r1x; + break; + case nop2: + rlist[i] = &proc->r2x; + break; + case nop3: + rlist[i] = &proc->r3x; + break; + } + } +} + +void _addr(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint64_t *reg; + + {% if not args.optimized %} + uint8_t next = _get_inst(core, proc->ip + 1); + uint8_t spin = _get_inst(core, proc->sp); + assert(_is_key(next)); + assert(_is_lock(spin)); + assert(_key_lock_match(next, spin)); + {% endif %} + + _get_reg_addr_list(core, pix, ®, 1, true); + *reg = proc->sp; + + _increment_ip(core, pix); +} + +void _ifnz(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint64_t *reg; + + _get_reg_addr_list(core, pix, ®, 1, false); + + uint64_t jmod = _is_rmod(_get_inst(core, proc->ip + 1)) ? 1 : 0; + uint64_t rmod = *reg ? 1 : 2; + + proc->ip += jmod + rmod; + proc->sp = proc->ip; +} + +void _free_child_memory_of(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + + assert(proc->mb1s); + + _free_memory_block(core, proc->mb1a, proc->mb1s); + + proc->mb1a = 0; + proc->mb1s = 0; +} + +// Organisms allocate new memory blocks by means of their seek pointer (sp), +// which sweeps memory 1 byte per simulation step, extending the block as it goes. +// In case allocated memory is found mid-way, current allocation is discarded. +void _alloc(struct Core *core, uint64_t pix, bool fwrd) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint64_t *regs[2]; + + _get_reg_addr_list(core, pix, regs, 2, false); + + uint64_t bsize = *regs[0]; + + // Do nothing if block-size is zero + if (!bsize) { + _increment_ip(core, pix); + return; + } + + // Do nothing if seek pointer is not adjacent to allocated memory block + // This is an error condition + if (proc->mb1s) { + uint64_t exp_addr = proc->mb1a; + + if (fwrd) { + exp_addr += proc->mb1s; + } else { + exp_addr--; + } + + if (proc->sp != exp_addr) { + _increment_ip(core, pix); + return; + } + } + + // Allocation was successful + // Store block address on register + if (proc->mb1s == bsize) { + _increment_ip(core, pix); + *regs[1] = proc->mb1a; + return; + } + + // Seek pointer collided with another allocated block + // Discard and keep trying + if (mvec_is_alloc(core, proc->sp)) { + if (proc->mb1s) { + _free_child_memory_of(core, pix); + } + + if (fwrd) { + proc->sp++; + } else { + proc->sp--; + } + + return; + } + + // Free (non-allocated) byte found + // Enlarge child block 1 byte + mvec_alloc(core, proc->sp); + + if (!proc->mb1s || !fwrd) { + proc->mb1a = proc->sp; + } + + proc->mb1s++; + + // Move seek pointer + if (fwrd) { + proc->sp++; + } else { + proc->sp--; + } +} + +void _bswap(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + + if (proc->mb1s) { + uint64_t tmpa = proc->mb0a; + uint64_t tmps = proc->mb0s; + + proc->mb0a = proc->mb1a; + proc->mb0s = proc->mb1s; + proc->mb1a = tmpa; + proc->mb1s = tmps; + } + + _increment_ip(core, pix); +} + +void _bclear(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + + if (proc->mb1s) { + _free_child_memory_of(core, pix); + } + + _increment_ip(core, pix); +} + +void _split(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + + if (proc->mb1s) { + struct Proc child = {0}; + + child.ip = proc->mb1a; + child.sp = proc->mb1a; + child.mb0a = proc->mb1a; + child.mb0s = proc->mb1s; + + proc->mb1a = 0; + proc->mb1s = 0; + + // A new organism is born :) + proc_new(core, &child); + } else { + assert(!proc->mb1a); + } + + _increment_ip(core, pix); +} + +void _3rop(struct Core *core, uint64_t pix, uint8_t inst) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + uint64_t *regs[3]; + + _get_reg_addr_list(core, pix, regs, 3, false); + + // Organisms can do arithmetic using any sequence of 3 registers + switch (inst) { + case addn: + *regs[0] = *regs[1] + *regs[2]; + break; + case subn: + *regs[0] = *regs[1] - *regs[2]; + break; + case muln: + *regs[0] = *regs[1] * *regs[2]; + break; + case divn: + // Division by zero + // Do nothing + if (*regs[2]) { + *regs[0] = *regs[1] / *regs[2]; + } + + break; + default: + assert(false); + } + + _increment_ip(core, pix); +} + +void _1rop(struct Core *core, uint64_t pix, uint8_t inst) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + uint64_t *reg; + + _get_reg_addr_list(core, pix, ®, 1, false); + + switch (inst) { + case incn: + (*reg)++; + break; + case decn: + (*reg)--; + break; + case notn: + *reg = !(*reg); + break; + case shfl: + *reg <<= 1; + break; + case shfr: + *reg >>= 1; + break; + case zero: + *reg = 0; + break; + case unit: + *reg = 1; + break; + default: + assert(false); + } + + _increment_ip(core, pix); +} + +void _push(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint64_t *reg; + + _get_reg_addr_list(core, pix, ®, 1, false); + + proc->s7 = proc->s6; + proc->s6 = proc->s5; + proc->s5 = proc->s4; + proc->s4 = proc->s3; + proc->s3 = proc->s2; + proc->s2 = proc->s1; + proc->s1 = proc->s0; + proc->s0 = *reg; + + _increment_ip(core, pix); +} + +void _pop(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint64_t *reg; + + _get_reg_addr_list(core, pix, ®, 1, false); + + *reg = proc->s0; + proc->s0 = proc->s1; + proc->s1 = proc->s2; + proc->s2 = proc->s3; + proc->s3 = proc->s4; + proc->s4 = proc->s5; + proc->s5 = proc->s6; + proc->s6 = proc->s7; + proc->s7 = 0; + + _increment_ip(core, pix); +} + +int _sp_dir(uint64_t src, uint64_t dst) { + if (src == dst) { + return 0; + } else if (src - dst <= dst - src) { + return -1; + } else { + return 1; + } +} + +void _load(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint64_t *regs[2]; + + _get_reg_addr_list(core, pix, regs, 2, false); + + int sp_dir = _sp_dir(proc->sp, *regs[0]); + + if (sp_dir == 1) { + proc->sp++; + } else if (sp_dir == -1) { + proc->sp--; + } else { + *regs[1] = mvec_get_inst(core, *regs[0]); + _increment_ip(core, pix); + } +} + +bool _is_writeable_by(const struct Core *core, uint64_t addr, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + return !mvec_is_alloc(core, addr) || mvec_is_proc_owner(core, addr, pix); +} + +void _write(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint64_t *regs[2]; + + _get_reg_addr_list(core, pix, regs, 2, false); + + int sp_dir = _sp_dir(proc->sp, *regs[0]); + + if (sp_dir == 1) { + proc->sp++; + } else if (sp_dir == -1) { + proc->sp--; + } else { + if (_is_writeable_by(core, *regs[0], pix)) { + mvec_set_inst(core, *regs[0], *regs[1] % {{ inst_cap }}); + } + + _increment_ip(core, pix); + } +} + +void _2rop(struct Core *core, uint64_t pix, uint8_t inst) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + uint64_t *regs[2]; + + _get_reg_addr_list(core, pix, regs, 2, false); + + switch (inst) { + case dupl: + *regs[1] = *regs[0]; + break; + case swap: + { + uint64_t tmp = *regs[0]; + *regs[0] = *regs[1]; + *regs[1] = tmp; + } + + break; + default: + assert(false); + } + + _increment_ip(core, pix); +} + +void arch_proc_step(struct Core *core, uint64_t pix) { + assert(core); + assert(mvec_proc_is_live(core, pix)); + + struct Proc *proc = proc_fetch(core, pix); + uint8_t inst = _get_inst(core, proc->ip); + + switch (inst) { + case jmpb: + if (_seek(core, pix, false)) { + _jump(core, pix); + } + + break; + case jmpf: + if (_seek(core, pix, true)) { + _jump(core, pix); + } + + break; + case adrb: + if (_seek(core, pix, false)) { + _addr(core, pix); + } + + break; + case adrf: + if (_seek(core, pix, true)) { + _addr(core, pix); + } + + break; + case ifnz: + _ifnz(core, pix); + break; + case allb: + _alloc(core, pix, false); + break; + case allf: + _alloc(core, pix, true); + break; + case bswp: + _bswap(core, pix); + break; + case bclr: + _bclear(core, pix); + break; + case splt: + _split(core, pix); + break; + case addn: + case subn: + case muln: + case divn: + _3rop(core, pix, inst); + break; + case incn: + case decn: + case notn: + case shfl: + case shfr: + case zero: + case unit: + _1rop(core, pix, inst); + break; + case pshn: + _push(core, pix); + break; + case popn: + _pop(core, pix); + break; + case load: + _load(core, pix); + break; + case wrte: + _write(core, pix); + break; + case dupl: + case swap: + _2rop(core, pix, inst); + break; + default: + _increment_ip(core, pix); + break; + } + + return; +} + +{% if not args.optimized %} +void arch_validate_proc(const struct Core *core, uint64_t pix) { + assert(core); + + const struct Proc *proc = proc_get(core, pix); + + assert(proc->mb0s); + + if (proc->mb1a) { + assert(proc->mb1s); + } + + for (uint64_t i = 0; i < proc->mb0s; ++i) { + uint64_t addr = proc->mb0a + i; + assert(mvec_is_alloc(core, addr)); + assert(mvec_is_proc_owner(core, addr, pix)); + } + + for (uint64_t i = 0; i < proc->mb1s; ++i) { + uint64_t addr = proc->mb1a + i; + assert(mvec_is_alloc(core, addr)); + assert(mvec_is_proc_owner(core, addr, pix)); + } +} +{% endif %} + +wchar_t arch_symbol(uint8_t inst) { + switch (inst % {{ inst_count }}) { + {% for i in arch_vars.inst_set %} + case {{ i[0]|join(' ') }}: return L'{{ i[1] }}'; + {% endfor %} + } + + assert(false); + return L'\0'; +} + +const char *arch_mnemonic(uint8_t inst) { + switch (inst % {{ inst_count }}) { + {% for i in arch_vars.inst_set %} + case {{ i[0]|join(' ') }}: return "{{ i[0]|join(' ') }}"; + {% endfor %} + } + + assert(false); + return NULL; +} + +{% if data_push_path is defined %} +void arch_push_data_header() { + assert(g_sim_data); + + const char *sql = ( + "create table data(" + "step int not null, " + {% for i in range(args.cores) %} + "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, " + "avrg_mb0s_{{ i }} real not null, " + "avrg_mb1s_{{ i }} real not null, " + {% set outer_loop = loop %} + {% for j in arch_vars.inst_set %} + "inst_{{ j[0]|join(' ') }}_{{ i }} int not null{% if not outer_loop.last or not loop.last %},{% endif %} " + {% endfor %} + {% endfor %} + ");" + ); + + {% set sql_call %}sqlite3_exec(g_sim_data, sql, NULL, NULL, NULL){% endset %} + + {% if not args.optimized %} + assert({{ sql_call }} == 0); + {% else %} + {{ sql_call }}; + {% endif %} +} + +void arch_push_data_line() { + assert(g_sim_data); + + // Gather data on all cores + uint64_t inst_total[{{ args.cores }}][{{ inst_count }}] = { 0 }; + + double avrg_mb0s[{{ args.cores }}] = { 0 }; + double avrg_mb1s[{{ args.cores }}] = { 0 }; + + for (int i = 0; i < {{ args.cores }}; ++i) { + const struct Core *core = &g_cores[i]; + + // Count number of instructions + for (uint64_t j = 0; j < {{ mvec_size }}; ++j) { + uint8_t inst = mvec_get_inst(core, j) % {{ inst_count }}; + ++inst_total[i][inst]; + } + + // Avregare memory block sizes + for (uint64_t j = core->pfst; j <= core->plst; ++j) { + const struct Proc *proc = proc_get(core, j); + + avrg_mb0s[i] += (double)proc->mb0s; + avrg_mb1s[i] += (double)proc->mb1s; + } + + avrg_mb0s[i] /= core->pnum; + avrg_mb1s[i] /= core->pnum; + } + + // Insert new row + char *sql = NULL; + + asprintf( + &sql, + "insert into data (" + "step, " + {% for i in range(args.cores) %} + "cycl_{{ i }}, " + "mall_{{ i }}, " + "pnum_{{ i }}, " + "pfst_{{ i }}, " + "plst_{{ i }}, " + "avrg_mb0s_{{ i }}, " + "avrg_mb1s_{{ i }}, " + {% set outer_loop = loop %} + {% for j in arch_vars.inst_set %} + "inst_{{ j[0]|join(' ') }}_{{ i }}{% if not outer_loop.last or not loop.last %},{% endif %} " + {% endfor %} + {% endfor %} + ") values (" + "%ld, " + {% for i in range(args.cores) %} + "%ld, %ld, %ld, %ld, %ld, %f, %f, " + {% set outer_loop = loop %} + {% for _ in arch_vars.inst_set %} + "%ld{% if not outer_loop.last or not loop.last %},{% endif %} " + {% endfor %} + {% endfor %} + ");", + g_steps, + {% for i in range(args.cores) %} + g_cores[{{ i }}].cycl, + g_cores[{{ i }}].mall, + g_cores[{{ i }}].pnum, + g_cores[{{ i }}].pfst, + g_cores[{{ i }}].plst, + avrg_mb0s[{{ i }}], + avrg_mb1s[{{ i }}], + {% set outer_loop = loop %} + {% for j in arch_vars.inst_set %} + inst_total[{{ i }}][{{ j[0]|join(' ') }}]{% if not outer_loop.last or not loop.last %},{% endif %} // inst + {% endfor %} + {% endfor %} + ); + + {% set sql_call %}sqlite3_exec(g_sim_data, sql, NULL, NULL, NULL){% endset %} + + {% if not args.optimized %} + assert({{ sql_call }} == 0); + {% else %} + {{ sql_call }}; + {% endif %} + + // Free query string returned by 'asprintf()' + free(sql); +} +{% endif %} diff --git a/arch/salis-v1/arch_vars.py b/arch/salis-v1/arch_vars.py new file mode 100644 index 0000000..07301c3 --- /dev/null +++ b/arch/salis-v1/arch_vars.py @@ -0,0 +1,98 @@ +core_fields = [] +mvec_loop = False + +proc_fields = [ + ("uint64_t", "ip"), + ("uint64_t", "sp"), + ("uint64_t", "mb0a"), + ("uint64_t", "mb0s"), + ("uint64_t", "mb1a"), + ("uint64_t", "mb1s"), + ("uint64_t", "r0x"), + ("uint64_t", "r1x"), + ("uint64_t", "r2x"), + ("uint64_t", "r3x"), + ("uint64_t", "s0"), + ("uint64_t", "s1"), + ("uint64_t", "s2"), + ("uint64_t", "s3"), + ("uint64_t", "s4"), + ("uint64_t", "s5"), + ("uint64_t", "s6"), + ("uint64_t", "s7"), +] + +# salis-v1 instruction set +inst_set = [ + (["noop"], " "), + (["nop0"], "0"), + (["nop1"], "1"), + (["nop2"], "2"), + (["nop3"], "3"), + # ------------- + (["jmpb"], "("), + (["jmpf"], ")"), + (["adrb"], "["), + (["adrf"], "]"), + (["ifnz"], "?"), + # ------------- + (["allb"], "{"), + (["allf"], "}"), + (["bswp"], "%"), + (["bclr"], "|"), + (["splt"], "$"), + # ------------- + (["addn"], "+"), + (["subn"], "-"), + (["muln"], "*"), + (["divn"], "/"), + (["incn"], "^"), + (["decn"], "v"), + (["notn"], "!"), + (["shfl"], "<"), + (["shfr"], ">"), + (["zero"], "z"), + (["unit"], "u"), + # ------------- + (["pshn"], "#"), + (["popn"], "~"), + # ------------- + (["load"], "."), + (["wrte"], ":"), + (["dupl"], "="), + (["swap"], "x"), + # ------------- + (["keya"], "a"), + (["keyb"], "b"), + (["keyc"], "c"), + (["keyd"], "d"), + (["keye"], "e"), + (["keyf"], "f"), + (["keyg"], "g"), + (["keyh"], "h"), + (["keyi"], "i"), + (["keyj"], "j"), + (["keyk"], "k"), + (["keyl"], "l"), + (["keym"], "m"), + (["keyn"], "n"), + (["keyo"], "o"), + (["keyp"], "p"), + # ------------- + (["loka"], "A"), + (["lokb"], "B"), + (["lokc"], "C"), + (["lokd"], "D"), + (["loke"], "E"), + (["lokf"], "F"), + (["lokg"], "G"), + (["lokh"], "H"), + (["loki"], "I"), + (["lokj"], "J"), + (["lokk"], "K"), + (["lokl"], "L"), + (["lokm"], "M"), + (["lokn"], "N"), + (["loko"], "O"), + (["lokp"], "P"), +] @@ -1,27 +1,20 @@ +// Author: Paul Oliver <contact@pauloliver.dev> // Project: Salis -// Author: Paul Oliver -// Email: contact@pauloliver.dev -/* - * Simple benchmark test helps measure simulation speed by stepping the - * simulator N times and printing results. - */ - -#if ACTION != ACT_BENCH -#error Using bench UI with unsupported action -#endif +// Simple benchmark test helps measure simulation speed. +// Steps the simulation N times and prints part of the simulator's state. int main() { printf("Salis Benchmark Test\n\n"); - salis_init("", SEED); - salis_step(BENCH_STEPS); + salis_init(); + salis_step({{ args.steps }}); - printf("seed => %#lx\n", SEED); + printf("seed => %#lx\n", {{ args.seed }}); printf("g_steps => %#lx\n", g_steps); printf("g_syncs => %#lx\n", g_syncs); - for (int i = 0; i < CORE_COUNT; ++i) { + for (int i = 0; i < {{ args.cores }}; ++i) { putchar('\n'); printf("core %d mall => %#lx\n", i, g_cores[i].mall); printf("core %d mut0 => %#lx\n", i, g_cores[i].muta[0]); @@ -34,7 +27,7 @@ int main() { printf("core %d plst => %#lx\n", i, g_cores[i].plst); printf("core %d pcur => %#lx\n", i, g_cores[i].pcur); printf("core %d psli => %#lx\n", i, g_cores[i].psli); - printf("core %d ncyc => %#lx\n", i, g_cores[i].ncyc); + printf("core %d cycl => %#lx\n", i, g_cores[i].cycl); printf("core %d ivpt => %#lx\n", i, g_cores[i].ivpt); putchar('\n'); diff --git a/core.j2.c b/core.j2.c new file mode 100644 index 0000000..ec158dc --- /dev/null +++ b/core.j2.c @@ -0,0 +1,838 @@ +// Author: Paul Oliver <contact@pauloliver.dev> +// Project: salis-v3 + +// Core template of the salis simulator. +// Different architectures and UIs can be attached in order to +// create a streamlined source file. + +{% for include in includes|sort %} +#include <{{ include }}> +{% endfor %} + +// Each architecture defines its own process type +struct Proc { + {% for type, val in arch_vars.proc_fields %} + {{ type }} {{ val }}; + {% endfor %} +}; + +// Simulation core +// Each core runs on a separate thread +// Core synchronization and IPC occurs at set intervals +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; + + // Architectures may provide custom fields + {% for type, val in arch_vars.core_fields %} + {{ type }} {{ val }}; + {% endfor %} + + struct Proc *pvec; + uint8_t mvec[{{ mvec_size }}]; + uint8_t tgap[{{ thread_gap }}]; +}; + +// Globals +struct Core g_cores[{{ args.cores }}]; +uint64_t g_steps; +uint64_t g_syncs; +const struct Proc g_dead_proc; + +{% if args.command in ["load", "new"] %} +char g_asav_pbuf[{{ auto_save_name_len }}]; +{% endif %} + +{% if data_push_path is defined %} +sqlite3 *g_sim_data; +{% endif %} + +// Forward declarations +// Each architecture must define these +{% if args.command in ["bench", "new"] and anc_bytes is defined %} +void arch_anc_init(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 not args.optimized %} +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 data_push_path is defined %} +void arch_push_data_header(); +void arch_push_data_line(); +{% endif %} + +// ---------------------------------------------------------------------------- +// Memory vector functions +// ---------------------------------------------------------------------------- +{% if arch_vars.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 arch_vars.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 arch_vars.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 arch_vars.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 arch_vars.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 arch_vars.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 arch_vars.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 args.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_proc_owner(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); + + if (((addr - mb0a) % {{ mvec_size }}) < mb0s) { + return true; + } + + uint64_t mb1a = arch_proc_mb1_addr(core, pix); + uint64_t mb1s = arch_proc_mb1_size(core, pix); + + if (((addr - mb1a) % {{ mvec_size }}) < mb1s) { + return true; + } + + return false; +} + +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 args.command in ["bench", "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 args.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 args.command in ["load", "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); +} +{% endif %} + +{% if args.command in ["bench", "new"] %} +{% if anc_bytes is defined %} +void core_assemble_ancestor(struct Core *core) { + assert(core); + + {% if arch_vars.mvec_loop %} + uint64_t addr = {{ uint64_half }}; + {% else %} + uint64_t addr = 0; + {% endif %} + + uint8_t anc_bytes[] = { + {{ anc_bytes|join(",") }} + }; + + for (uint64_t i = 0; i < sizeof(anc_bytes); ++i, ++addr) { + for (uint64_t j = 0; j < {{ args.clones }}; ++j) { + uint64_t addr_clone = addr + (({{ mvec_size }} / {{ args.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 = {{ args.clones }}; + core->pcap = {{ args.clones }}; + core->plst = {{ args.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 anc_bytes is defined %} + core_assemble_ancestor(core); + arch_anc_init(core); + {% endif %} +} +{% endif %} + +{% if args.command in ["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); +} +{% 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 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 args.command in ["load", "new"] %} +void salis_save(const char *path) { + {% if args.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 < {{ args.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 args.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 not args.optimized %} + int rem = snprintf( + {% else %} + snprintf( + {% endif %} + g_asav_pbuf, + {{ auto_save_name_len }}, + "%s-%#018lx", + "{{ sim_path }}", + g_steps + ); + + assert(rem >= 0); + assert(rem < {{ auto_save_name_len }}); + + salis_save(g_asav_pbuf); +} +{% endif %} + +{% if args.command in ["bench", "new"] %} +void salis_init() { + uint64_t seed = {{ args.seed }}; + + for (int i = 0; i < {{ args.cores }}; ++i) { + core_init(&g_cores[i], &seed); + } + + {% if args.command in ["new"] %} + salis_auto_save(); + {% endif %} + + {% if data_push_path is defined %} + sqlite3_open("{{ data_push_path }}", &g_sim_data); + assert(g_sim_data); + + arch_push_data_header(); + arch_push_data_line(); + {% endif %} +} +{% endif %} + +{% if args.command in ["load"] %} +void salis_load() { + {% if args.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 not args.optimized %} + assert(inflate(&strm, Z_FINISH)); + {% else %} + 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 < {{ args.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 args.compress %} + free(in); + free(out); + {% endif %} + + {% if data_push_path is defined %} + sqlite3_open("{{ data_push_path }}", &g_sim_data); + assert(g_sim_data); + {% 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 < {{ args.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 < {{ args.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 < {{ args.cores }}; ++i) { + g_cores[i - 1].iviv = g_cores[i].iviv; + g_cores[i - 1].ivav = g_cores[i].ivav; + } + + g_cores[{{ args.cores }} - 1].iviv = iviv0; + g_cores[{{ args.cores }} - 1].ivav = ivav0; + + for (int i = 0; i < {{ args.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 args.command in ["load", "new"] %} + if (g_steps % {{ auto_save_interval }} == 0) { + salis_auto_save(); + } + {% endif %} + + {% if data_push_path is defined %} + if (g_steps % {{ data_push_interval }} == 0) { + arch_push_data_line(); + } + {% endif %} + + salis_loop(ns - dt, {{ sync_interval }}); +} + +{% if not args.optimized %} +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 < {{ args.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 not args.optimized %} + salis_validate(); + {% endif %} +} + +void salis_free() { + {% if data_push_path is defined %} + assert(g_sim_data); + sqlite3_close(g_sim_data); + {% endif %} + + for (int i = 0; i < {{ args.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/" ~ args.arch ~ "/arch.j2.c" %} + +// ---------------------------------------------------------------------------- +// UI +// ---------------------------------------------------------------------------- +{% if args.command in ["load", "new"] %} + {% include "ui/" ~ args.ui ~ "/ui.j2.c" %} +{% else %} + {% include "bench.j2.c" %} +{% endif %} @@ -1,406 +0,0 @@ -#!/bin/bash - -# Project: Salis -# Author: Paul Oliver -# Email: contact@pauloliver.dev - -# Salis simulator launcher script. Builds salis binary (according to passed -# arguments) and launches it. Binary is purged on exit. This JIT compilation -# scheme allows to quickly switch between available architectures and UIs. - -set -euo pipefail - -headline="Salis: Simple A-Life Simulator." -help_msg="Shows help and exits" - -usage() { -cat << EOF -${headline} -Usage: ${0} [-h|--help] COMMAND [args...] - -Options: - -h, --help ${help_msg} - -Commands: - bench Runs benchmark - load Loads saved simulation - new Creates a new simulation - -Use '-h' to list arguments for each command. -Example: ${0} bench -h -EOF -} - -case ${1:-} in -bench|load|new) - ;; --h|--help) - usage - exit 0 - ;; -"") - echo "${0}: please specify command -- 'bench|load|new'" - exit 1 - ;; -*) - echo "${0}: invalid command -- '${1}'" - exit 1 - ;; -esac - -cmd=${1} - -shift - -falter() { - find src/${1}/ -type f | sed 's|.*/||; s|\.c||' | paste -sd: - -} - -arches=`falter arch` -uis=`falter ui` - -anc_def_desc() { - echo "Default ancestor file name without extension, " - echo "to be compiled on all cores " - echo "('ANC' points to file 'ancs/<ARCH>/<ANC>.asm')" -} - -anc_spec_def() { - echo "Core specific ancestor file names separated by commas, " - echo "using same convention as with default ancestor (<ANC0>,<ANC1>,...). " - echo "When provided will override default ancestor on specified cores." -} - -options=( - "A|anc-def|ANC|`anc_def_desc`|||bench:new" - "a|arch|ARCH|VM architecture|${arches}|dummy|bench:new" - "b|steps|N|Number of steps to run in benchmark||0x1000000|bench" - "C|clones|N|Number of ancestor clones on each core||1|bench:new" - "c|cores|N|Number of simulator cores||2|bench:new" - "F|muta-flip||Cosmic rays flip bits instead of randomizing whole bytes||false|bench:new" - "f|force||Overwrites existing simulation of given name||false|new" - "H|half||Compiles ancestor at the middle of the memory buffer||false|bench:new" - "h|help||${help_msg}|||bench:load:new" - "M|muta-pow|POW|Mutator range exponent (range == 2^POW)||32|bench:new" - "m|mvec-pow|POW|Memory vector size exponent (size == 2^POW)||20|bench:new" - "n|name|NAME|Name of new or loaded simulation||def.sim|load:new" - "o|optimized||Builds Salis binary with optimizations||false|bench:load:new" - "p|pre-cmd|CMD|Shell command to wrap executable (e.g. gdb, valgrind, etc.)|||bench:load:new" - "S|anc-spec|ANC0,ANC1,...|`anc_spec_def`|||bench:new" - "s|seed|SEED|Seed value for new simulation||0|bench:new" - "t|thread-gap|N|Memory gap between cores in bytes (could help reduce cache misses?)||0x100|bench:load:new" - "u|ui|UI|User interface|${uis}|curses|load:new" - "y|sync-pow|POW|Core sync interval exponent (interval == 2^POW)||20|bench:new" - "x|compress||Compress save files (requires 'zlib')||true|new" - "z|auto-save-pow|POW|Auto-save interval exponent (interval == 2^POW)||36|new" -) - -field() { - echo ${1} | cut -d'|' -f${2} -} - -flist() { - sopt=`field "${1}" 1` - lopt=`field "${1}" 2` - meta=`field "${1}" 3` - - echo -n "[-${sopt}|--${lopt}`[[ -n ${meta} ]] && echo " ${meta}"`] " -} - -fhelp() { - sopt=`field "${1}" 1` - lopt=`field "${1}" 2` - meta=`field "${1}" 3` - - printf "%s\r" " -${sopt}, --${lopt}`[[ -n ${meta} ]] && echo " ${meta}"`" - - help=`field "${1}" 4` - choi=`field "${1}" 5` - defv=`field "${1}" 6` - copt=`[[ -n ${choi} ]] && echo " (choices: ${choi/:/, })"` - dopt=`[[ -n ${defv} ]] && echo " (default: ${defv})"` - - echo -e "\t\t\t\t${help}${copt}${dopt}" | fmt -w120 -} - -fshort() { - sopt=`field "${1}" 1` - meta=`field "${1}" 3` - - echo -n "${sopt}`[[ -n ${meta} ]] && echo :`" -} - -flong() { - lopt=`field "${1}" 2` - meta=`field "${1}" 3` - - echo -n "${lopt}`[[ -n ${meta} ]] && echo :`," -} - -fdefaults() { - lopt=`field "${1}" 2` - - [[ ${lopt} == help ]] && return - - defv=`field "${1}" 6` - nopt=opt_${lopt//-/_} - - eval ${nopt}=${defv} -} - -fshow() { - lopt=`field "${1}" 2` - - [[ ${lopt} == help ]] && return - - nopt=opt_${lopt//-/_} - - echo "${nopt}=${!nopt}" -} - -fiter() { - for ((i = 0; i < ${#options[@]}; i++)) ; do - if [[ `field "${options[${i}]}" 7` =~ ${cmd} ]] ; then - ${1} "${options[${i}]}" || true - fi - done -} - -usage() { -cat << EOF -${headline} -Usage: ${0} ${cmd} `fiter flist | fmt -t -w120` - -Options: -`fiter fhelp` -EOF -} - -fiter fdefaults - -sopts=`fiter fshort` -lopts=`fiter flong` -popts=`getopt -n "${0} ${cmd}" -o ${sopts} -l ${lopts::-1} -- "${@}"` - -eval set -- ${popts} - -parse_next() { - for ((i = 0; i < ${#options[@]}; i++)) ; do - vopt="${options[${i}]}" - - sopt=`field "${vopt}" 1` - lopt=`field "${vopt}" 2` - - [[ ${1} != -${sopt} ]] && [[ ${1} != --${lopt} ]] && continue - - meta=`field "${vopt}" 3` - nopt=opt_${lopt//-/_} - - if [[ -z ${meta} ]] ; then - defv=`field "${vopt}" 6` - eval ${nopt}=`[[ ${defv} == true ]] && echo false || echo true` - shift_next=1 - else - eval ${nopt}=${2} - shift_next=2 - fi - done -} - -while true ; do - case ${1} in - -h|--help) - usage - exit 0 - ;; - --) - shift - break - ;; - *) - parse_next ${@} - shift ${shift_next} - ;; - esac -done - -if [[ -n ${1:-} ]] ; then - while [[ -n ${1:-} ]] ; do - echo "${0} ${cmd}: unrecognized option -- '${1}'" - shift - done - - exit 1 -fi - -blue() { - echo -e "\033[1;34m${1}\033[0m" -} - -red() { - echo -e "\033[1;31m${1}\033[0m" -} - -blue "${headline}" -blue "Called '${cmd}' command with the following options:" -fiter fshow - -case ${cmd} in -load|new) - sim_dir=${HOME}/.salis/${opt_name} - sim_path=${sim_dir}/${opt_name} - sim_opts=${sim_dir}/opts - ;; -esac - -case ${cmd} in -load) - if [[ ! -d ${sim_dir} ]] ; then - red "Error: no saved simulation was found named '${opt_name}'." - exit 1 - fi - - blue "Sourcing configurations from '${sim_opts}':" - cat ${sim_opts} - source ${sim_opts} - ;; -esac - -blue "Generating a temporary Salis directory:" -salis_tmp=/tmp/salis-tmp -salis_exe=${salis_tmp}/salis-bin -mkdir -pv ${salis_tmp} - -act_bench=1 -act_load=2 -act_new=3 - -act_var="act_${cmd}" - -gcc_flags="-Wall -Wextra -Werror -std=gnu11 -pedantic" - -fquote() { - echo "\\\"${1}\\\"" -} - -fpow() { - printf '%#xul' $((1 << ${1})) -} - -bcmd="gcc src/salis.c -o ${salis_exe} ${gcc_flags} -Isrc -pthread" -bcmd="${bcmd} `[[ ${opt_optimized} == true ]] && echo "-O3 -DNDEBUG" || echo "-ggdb"`" -bcmd="${bcmd} -DACTION=${!act_var}" -bcmd="${bcmd} -DARCHITECTURE=`fquote ${opt_arch}`" -bcmd="${bcmd} -DARCH_SOURCE=`fquote arch/${opt_arch}.c`" -bcmd="${bcmd} -DCORE_COUNT=${opt_cores}" -bcmd="${bcmd} -DMUTA_RANGE=`fpow ${opt_muta_pow}`" -bcmd="${bcmd} -DMVEC_SIZE=`fpow ${opt_mvec_pow}`" -bcmd="${bcmd} -DNCURSES_WIDECHAR=1" -bcmd="${bcmd} -DSEED=${opt_seed}ul" -bcmd="${bcmd} -DSYNC_INTERVAL=`fpow ${opt_sync_pow}`" -bcmd="${bcmd} -DTGAP_SIZE=${opt_thread_gap}ul" - -case ${cmd} in -bench) - bcmd="${bcmd} -DBENCH_STEPS=${opt_steps}ul" - bcmd="${bcmd} -DUI=`fquote bench.c`" - ;; -esac - -case ${cmd} in -bench|new) - anc_list= - - for cix in `seq 1 ${opt_cores}` ; do - anc_spec=`echo ${opt_anc_spec}, | cut -s -d, -f${cix}` - anc_spec=${anc_spec:-${opt_anc_def}} - - if [[ -n ${anc_spec} ]] ; then - anc_src=ancs/${opt_arch}/${anc_spec}.asm - anc_path=${salis_tmp}/${anc_spec}.asm - sed -E '/(^$|^;)/d; s/ +/ /g' ${anc_src} > ${anc_path} - else - anc_path=_ - fi - - anc_list=${anc_list}${anc_path}, - done - - bcmd="${bcmd} -DANC_LIST=`fquote "${anc_list::-1}"`" - bcmd="${bcmd} -DANC_HALF=`[[ ${opt_half} == true ]] && echo 1 || echo 0`" - bcmd="${bcmd} -DANC_CLONES=${opt_clones}" - ;; -esac - -case ${cmd} in -load|new) - bcmd="${bcmd} -DAUTO_SAVE_INTERVAL=`fpow ${opt_auto_save_pow}`" - bcmd="${bcmd} -DAUTO_SAVE_NAME_LEN=$((${#sim_path} + 20))" - bcmd="${bcmd} -DMUTA_FLIP_BIT=`[[ ${opt_muta_flip} == true ]] && echo 1 || echo 0`" - bcmd="${bcmd} -DSIM_NAME=`fquote ${opt_name}`" - bcmd="${bcmd} -DSIM_PATH=`fquote ${sim_path}`" - - ui_file=ui/${opt_ui}.c - ui_flags=`sed -n -e 's/^.*GCC_EXTRA_FLAGS //p' src/${ui_file}` - - shopt -s extglob - bcmd="${bcmd} -DUI=`fquote ${ui_file}` ${ui_flags}" - bcmd="${bcmd%%*( )}" - shopt -u extglob - - if [[ ${opt_compress} == true ]] ; then - bcmd="${bcmd} -DCOMPRESS -lz" - fi - ;; -esac - -blue "Using build command:" -echo "${bcmd}" -eval "${bcmd}" - -case ${cmd} in -new) - if [[ -d ${sim_dir} ]] && [[ ${opt_force} == true ]] ; then - red "Force flag used. Wiping old simulation at '${sim_dir}':" - rm -rv ${sim_dir} - fi - - if [[ -d ${sim_dir} ]] ; then - red "Error: simulation directory found at '${sim_dir}'." - red "Please, remove it or call 'load' instead." - exit 1 - fi - - blue "Creating new simulation directory at '${sim_dir}':" - mkdir -pv ${sim_dir} - ;; -esac - -rcmd="`[[ -z ${opt_pre_cmd} ]] || echo "${opt_pre_cmd} "`${salis_exe}" - -blue "Using run command:" -echo "${rcmd}" - -blue "Running Salis..." -eval "${rcmd}" - -case ${cmd} in -new) - blue "Saving new simulation configuration file at:" - echo "${sim_opts}" - - for ((i = 0; i < ${#options[@]}; i++)) ; do - oopt=`field "${options[${i}]}" 7` - - [[ ! ${oopt} =~ new ]] || [[ ${oopt} =~ load ]] && continue - - lopt=`field "${options[${i}]}" 2` - nopt=opt_${lopt//-/_} - - echo "${nopt}=${!nopt}" >> ${sim_opts} - done - ;; -esac - -blue "Removing temporary Salis directory and resources:" -rm -rv ${salis_tmp} diff --git a/salis.py b/salis.py new file mode 100755 index 0000000..b7f7a18 --- /dev/null +++ b/salis.py @@ -0,0 +1,356 @@ +#!/usr/bin/env -S PYTHONDONTWRITEBYTECODE=1 python3 + +# Author: Paul Oliver <contact@pauloliver.dev> +# Project: salis-v3 + +# Salis simulator launcher script +# Emits a single C source file, builds it into a binary and launches it. +# JIT compilation allows quick switching between all available executable configurations. + +import os +import shutil +import subprocess +import sys + +from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, RawTextHelpFormatter +from jinja2 import Environment, FileSystemLoader, StrictUndefined +from tempfile import TemporaryDirectory + +# ------------------------------------------------------------------------------ +# Parse CLI arguments +# ------------------------------------------------------------------------------ +headline = "Salis: Simple A-Life Simulator" +script = sys.argv[0] +epilog = f"Use '-h' to list arguments for each command.\nExample: '{script} bench -h'" + +main_parser = ArgumentParser( + description = headline, + epilog = epilog, + formatter_class = RawTextHelpFormatter, + prog = script, +) + +parsers = main_parser.add_subparsers(dest="command", required=True) +fclass = ArgumentDefaultsHelpFormatter + +bench = parsers.add_parser("bench", formatter_class=fclass, help="run benchmark") +load = parsers.add_parser("load", formatter_class=fclass, help="load saved simulation") +new = parsers.add_parser("new", formatter_class=fclass, help="create new simulation") + +archs = os.listdir("./arch") +uis = os.listdir("./ui") + +intt = lambda i: int(i, 0) + +option_keys = ["short", "long", "metavar", "description", "default", "type", "parsers"] + +# fmt: off +option_conf = [ + ["A", "anc", "ANC", "ancestor file name without extension, to be compiled on " + "all cores (ANC points to 'ancs/<ARCH>/<ANC>.asm')", None, str, [bench, new]], + ["a", "arch", archs, "VM architecture", "dummy", str, [bench, new]], + ["b", "steps", "N", "number of steps to run in benchmark", 0x1000000, intt, [bench]], + ["C", "clones", "N", "number of ancestor clones on each core", 1, intt, [bench, new]], + ["c", "cores", "N", "number of simulator cores", 2, intt, [bench, new]], + ["d", "data-push-pow", "POW", "data aggregation interval exponent (interval == 2^POW >= " + "thread sync interval); a value of 0 disables data " + "aggregation (requires 'sqlite')", 28, intt, [new]], + ["f", "force", None, "overwrite existing simulation of given name", False, bool, [new]], + ["F", "muta-flip", None, "cosmic rays flip bits instead of randomizing whole bytes", False, bool, [bench, new]], + ["M", "muta-pow", "POW", "mutator range exponent (range == 2^POW)", 32, intt, [bench, new]], + ["m", "mvec-pow", "POW", "memory vector size exponent (size == 2^POW)", 20, intt, [bench, new]], + ["n", "name", "NAME", "name of new or loaded simulation", "def.sim", str, [load, new]], + ["o", "optimized", None, "builds salis binary with optimizations", False, bool, [bench, load, new]], + ["p", "pre-cmd", "CMD", "shell command to wrap call to executable (e.g. gdb, " + "valgrind, etc.)", None, str, [bench, load, new]], + ["s", "seed", "SEED", "seed value for new simulation", 0, intt, [bench, new]], + ["S", "print-source", None, "print generated C source to stdout and exit", False, bool, [bench, load, new]], + ["T", "delete-temp-dir", None, "delete temporary directory on exit", True, bool, [bench, load, new]], + ["t", "thread-gap", "N", "memory gap between cores in bytes (may help reduce cache " + "misses?)", 0x100, intt, [bench, load, new]], + ["u", "ui", uis, "user interface", "curses", str, [load, new]], + ["x", "compress", None, "compress save files (requires 'zlib')", True, bool, [new]], + ["y", "sync-pow", "POW", "core sync interval exponent (interval == 2^POW)", 20, intt, [bench, new]], + ["z", "auto-save-pow", "POW", "auto-save interval exponent (interval == 2^POW)", 36, intt, [new]], +] +# fmt: on + +# Map arguments to subparsers that use them +options = list(map(lambda option: dict(zip(option_keys, option)), option_conf)) +parser_map = ((parser, option) for option in options for parser in option["parsers"]) + +for parser, option in parser_map: + arg_kwargs = {} + + def push_same(key): + arg_kwargs[key] = option[key] + + def push_diff(tgt_key, src_key): + arg_kwargs[tgt_key] = option[src_key] + + def push_val(key, val): + arg_kwargs[key] = val + + push_diff("help", "description") + + # No metavar means this argument is a flag + if option["metavar"] is None: + push_val("action", "store_false" if option["default"] else "store_true") + else: + push_same("default") + push_same("type") + + if type(option["metavar"]) is list: + push_diff("choices", "metavar") + + if type(option["metavar"]) is str: + push_same("metavar") + + parser.add_argument( + f"-{option["short"]}", + f"--{option["long"]}", + **arg_kwargs, + ) + +args = main_parser.parse_args() + + +def log(msg, val=""): + print(f"\033[1;34m{msg}\033[0m", val, flush=True) + + +def warn(msg, val=""): + print(f"\033[1;31m{msg}\033[0m", val, flush=True) + + +def error(msg, val=""): + warn(f"ERROR: {msg}", val) + sys.exit(1) + + +# ------------------------------------------------------------------------------ +# Load configuration +# ------------------------------------------------------------------------------ +log(headline) +log(f"Called '{script}' with the following options:") + +for key, val in vars(args).items(): + print(f"{key} = {repr(val)}") + +if args.command in ["load", "new"]: + sim_dir = f"{os.environ["HOME"]}/.salis/{args.name}" + sim_opts = f"{sim_dir}/opts.py" + sim_path = f"{sim_dir}/{args.name}" + +if args.command in ["load"]: + if not os.path.isdir(sim_dir): + error("No simulation found named:", args.name) + + log(f"Sourcing configuration from '{sim_opts}':") + sys.path.append(sim_dir) + import opts as opts_module + + # Copy all fields in configuration file into the 'args' object + opts = (opt for opt in dir(opts_module) if not opt.startswith("__")) + + for opt in opts: + opt_attr = getattr(opts_module, opt) + print(f"{opt} = {repr(opt_attr)}") + setattr(args, opt, opt_attr) + +if args.command in ["new"]: + if args.data_push_pow != 0 and args.data_push_pow < args.sync_pow: + error("Data push power must be equal or greater than thread sync power") + + if os.path.isdir(sim_dir) and args.force: + warn("Force flag used - wiping old simulation at:", sim_dir) + shutil.rmtree(sim_dir) + + if os.path.isdir(sim_dir): + error("Simulation directory found at:", sim_dir) + + log("Creating new simulation directory at:", sim_dir) + os.mkdir(sim_dir) + + log("Creating configuration file at:", sim_opts) + + opts = ( + option["long"].replace("-", "_") + for option in options + if new in option["parsers"] and load not in option["parsers"] + ) + + with open(sim_opts, "w") as file: + for opt in opts: + file.write(f"{opt} = {repr(eval(f"args.{opt}"))}\n") + +# ------------------------------------------------------------------------------ +# Load architecture and UI variables +# ------------------------------------------------------------------------------ +arch_path = f"arch/{args.arch}" +log("Loading architecture specific variables from:", f"{arch_path}/arch_vars.py") +sys.path.append(arch_path) +import arch_vars + +if args.command in ["load", "new"]: + ui_path = f"ui/{args.ui}" + log("Loading UI specific variables from:", f"{ui_path}/ui_vars.py") + sys.path.append(ui_path) + import ui_vars + +# ------------------------------------------------------------------------------ +# Fill in template variables +# ------------------------------------------------------------------------------ +ul_val = lambda val: f"{hex(val)}ul" +ul_pow = lambda val: f"{hex(2 ** val)}ul" + +includes = [ + "assert.h", + "stdbool.h", + "stddef.h", + "stdint.h", + "stdlib.h", + "string.h", + "threads.h", +] + +inst_cap = "0x80" +inst_mask = "0x7f" +ipc_flag = "0x80" +mall_flag = "0x80" +muta_range = ul_pow(args.muta_pow) +muta_seed = ul_val(args.seed) +mvec_size = ul_pow(args.mvec_pow) +sync_interval = ul_pow(args.sync_pow) +thread_gap = ul_val(args.thread_gap) +uint64_half = ul_val(0x8000000000000000) + +args.seed = ul_val(args.seed) + +if args.command in ["bench"]: + includes.append("stdio.h") + args.steps = ul_val(args.steps) + +if args.command in ["load", "new"]: + auto_save_interval = ul_pow(args.auto_save_pow) + auto_save_name_len = f"{len(sim_path) + 20}" + + if args.data_push_pow != 0: + data_push_path = f"{sim_dir}/{args.name}.sqlite3" + data_push_interval = ul_pow(args.data_push_pow) + includes.append("sqlite3.h") + log("Data will be aggregated at:", data_push_path) + else: + warn("Data aggregation disabled") + + if args.compress: + includes.append("zlib.h") + log("Save file compression enabled") + else: + warn("Save file compression disabled") + + includes.extend(ui_vars.includes) + +# ------------------------------------------------------------------------------ +# Assemble ancestor organism into byte array +# ------------------------------------------------------------------------------ +if args.command in ["bench", "new"] and args.anc is not None: + anc_path = f"ancs/{args.arch}/{args.anc}.asm" + + if not os.path.isfile(anc_path): + error("Could not find ancestor file:", anc_path) + + with open(anc_path, "r") as file: + lines = file.read().splitlines() + + lines = filter(lambda line: not line.startswith(";"), lines) + lines = filter(lambda line: not line.isspace(), lines) + lines = filter(lambda line: line, lines) + lines = map(lambda line: line.split(), lines) + + # A very simple assembler that compares lines in input ASM file against + # all entries in the instruction set table provided by each architecture. + # The resulting bytes equate to each instruction's index on the table. + anc_bytes = [] + + for line in lines: + found = False + + for byte, tup in enumerate(arch_vars.inst_set): + if line == tup[0]: + anc_bytes.append(byte) + found = True + continue + + if not found: + error("Unrecognized instruction in ancestor file:", line) + + log(f"Compiled ancestor file '{anc_path}' into byte array:", anc_bytes) + +# ------------------------------------------------------------------------------ +# Emit C source +# ------------------------------------------------------------------------------ +tempdir = TemporaryDirectory(prefix="salis_", delete=args.delete_temp_dir) +log("Created a temporary salis directory at:", tempdir.name) + +salis_src = f"{tempdir.name}/salis.c" +log("Emitting C source at:", salis_src) + +jinja_env = Environment( + loader = FileSystemLoader("."), + lstrip_blocks = True, + trim_blocks = True, + undefined = StrictUndefined, +) + +source_str = jinja_env.get_template("core.j2.c").render(**locals()) + +if args.print_source: + log("Printing C source and exiting...") + print(source_str) + exit(0) + +with open(salis_src, "w") as file: + file.write(source_str) + +# ------------------------------------------------------------------------------ +# Build executable +# ------------------------------------------------------------------------------ +salis_bin = f"{tempdir.name}/salis_bin" +log("Building salis binary at:", salis_bin) + +build_cmd = ["gcc", salis_src, "-o", salis_bin, "-Wall", "-Wextra", "-Werror", "-Wno-overlength-strings", "-pedantic", "-std=c11"] +build_cmd.extend(["-O3", "-DNDEBUG"] if args.optimized else ["-ggdb"]) + +if args.command in ["load", "new"]: + build_cmd.extend(ui_vars.flags) + + # Enable POSIX extensions (open_memstream) + build_cmd.extend(["-lz", "-D_POSIX_C_SOURCE=200809L"] if args.compress else []) + + # Enable GNU extensions (asprintf) + # This allows managing large SQL strings more easily + build_cmd.extend(["-lsqlite3", "-D_GNU_SOURCE"] if args.data_push_pow != 0 else []) + +log("Using build command:", " ".join(build_cmd)) +subprocess.run(build_cmd, check=True) + +# ------------------------------------------------------------------------------ +# Run salis binary +# ------------------------------------------------------------------------------ +log("Running salis binary...") + +run_cmd = [args.pre_cmd] if args.pre_cmd else [] +run_cmd.append(salis_bin) + +log("Using run command:", " ".join(run_cmd)) +salis_sp = subprocess.Popen(run_cmd, stdout=sys.stdout) + +# Ctrl-C terminates the simulator gracefully. +# When using signals (e.g. SIGTERM), they must be sent to the entire process group +# to make sure both the simulator and the interpreter get shut down. +try: + salis_sp.wait() +except KeyboardInterrupt: + salis_sp.terminate() + salis_sp.wait() diff --git a/src/arch/dummy.c b/src/arch/dummy.c deleted file mode 100644 index 7a47900..0000000 --- a/src/arch/dummy.c +++ /dev/null @@ -1,152 +0,0 @@ -// Project: Salis -// Author: Paul Oliver -// Email: contact@pauloliver.dev - -/* - * Defines a minimal viable architecture for the Salis VM. Useful for - * debugging and benchmarking. Also, this file can be used as a template when - * implementing a real architecture. - */ - -bool proc_is_live(const Core *core, u64 pix); - -#define PROC_FIELDS \ - PROC_FIELD(u64, ip) \ - PROC_FIELD(u64, sp) \ - PROC_FIELD(u64, mb0a) \ - PROC_FIELD(u64, mb0s) \ - PROC_FIELD(u64, mb1a) \ - PROC_FIELD(u64, mb1s) - -struct Proc { -#define PROC_FIELD(type, name) type name; - PROC_FIELDS -#undef PROC_FIELD -}; - -#define MNEMONIC_BUFF_SIZE (0x10) - -const wchar_t *g_arch_byte_symbols = ( - L"⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟" - L"⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿" - L"⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟" - L"⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿" -); - -u64 arch_proc_mb0_addr(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return 0; -} - -u64 arch_proc_mb0_size(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return 0; -} - -u64 arch_proc_mb1_addr(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return 0; -} - -u64 arch_proc_mb1_size(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return 0; -} - -u64 arch_proc_ip_addr(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return 0; -} - -u64 arch_proc_sp_addr(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return 0; -} - -u64 arch_proc_slice(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return 1; -} - -void arch_on_proc_kill(Core *core) { - assert(core); - assert(core->pnum > 1); - - (void)core; -} - -#if ACTION == ACT_BENCH || ACTION == ACT_NEW -void arch_anc_init(Core *core, u64 size) { - assert(core); - - (void)core; - (void)size; -} -#endif - -void arch_proc_step(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return; -} - -#ifndef NDEBUG -void arch_validate_proc(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - assert(true); -} -#endif - -wchar_t arch_symbol(u8 inst) { - return g_arch_byte_symbols[inst]; -} - -void arch_mnemonic(u8 inst, char *buff) { - assert(buff); - - snprintf(buff, MNEMONIC_BUFF_SIZE, "dummy %#x", inst); -} diff --git a/src/arch/salis-v1.c b/src/arch/salis-v1.c deleted file mode 100644 index 1085883..0000000 --- a/src/arch/salis-v1.c +++ /dev/null @@ -1,875 +0,0 @@ -// Project: Salis -// Author: Paul Oliver -// Email: contact@pauloliver.dev - -/* - * This is based on the original VM architecture in salis-v1: - * https://git.pauloliver.dev/salis-v1/about/ - */ - -bool mvec_is_alloc(const Core *core, u64 pix); -void mvec_alloc(Core *core, u64 addr); -void mvec_free(Core *core, u64 addr); -u8 mvec_get_inst(const Core *core, u64 addr); -void mvec_set_inst(Core *core, u64 addr, u8 inst); -bool mvec_is_proc_owner(const Core *core, u64 addr, u64 pix); -void proc_new(Core *core, const Proc *proc); -bool proc_is_live(const Core *core, u64 pix); -const Proc *proc_get(const Core *core, u64 pix); -Proc *proc_fetch(Core *core, u64 pix); - -#define INST_LIST \ - INST(noop, L' ') \ - INST(nop0, L'0') \ - INST(nop1, L'1') \ - INST(nop2, L'2') \ - INST(nop3, L'3') \ - \ - INST(jmpb, L'(') \ - INST(jmpf, L')') \ - INST(adrb, L'[') \ - INST(adrf, L']') \ - INST(ifnz, L'?') \ - \ - INST(allb, L'{') \ - INST(allf, L'}') \ - INST(bswp, L'%') \ - INST(bclr, L'|') \ - INST(splt, L'$') \ - \ - INST(addn, L'+') \ - INST(subn, L'-') \ - INST(muln, L'*') \ - INST(divn, L'/') \ - INST(incn, L'^') \ - INST(decn, L'v') \ - INST(notn, L'!') \ - INST(shfl, L'<') \ - INST(shfr, L'>') \ - INST(zero, L'z') \ - INST(unit, L'u') \ - \ - INST(pshn, L'#') \ - INST(popn, L'~') \ - \ - INST(load, L'.') \ - INST(wrte, L':') \ - INST(dupl, L'"') \ - INST(swap, L'x') \ - \ - INST(keya, L'a') \ - INST(keyb, L'b') \ - INST(keyc, L'c') \ - INST(keyd, L'd') \ - INST(keye, L'e') \ - INST(keyf, L'f') \ - INST(keyg, L'g') \ - INST(keyh, L'h') \ - INST(keyi, L'i') \ - INST(keyj, L'j') \ - INST(keyk, L'k') \ - INST(keyl, L'l') \ - INST(keym, L'm') \ - INST(keyn, L'n') \ - INST(keyo, L'o') \ - INST(keyp, L'p') \ - \ - INST(loka, L'A') \ - INST(lokb, L'B') \ - INST(lokc, L'C') \ - INST(lokd, L'D') \ - INST(loke, L'E') \ - INST(lokf, L'F') \ - INST(lokg, L'G') \ - INST(lokh, L'H') \ - INST(loki, L'I') \ - INST(lokj, L'J') \ - INST(lokk, L'K') \ - INST(lokl, L'L') \ - INST(lokm, L'M') \ - INST(lokn, L'N') \ - INST(loko, L'O') \ - INST(lokp, L'P') - -#define MNEMONIC_BUFF_SIZE (0x5) - -enum sinst { -#define INST(name, symb) name, - INST_LIST -#undef INST - INST_COUNT -}; - -#define PROC_FIELDS \ - PROC_FIELD(u64, ip) \ - PROC_FIELD(u64, sp) \ - PROC_FIELD(u64, mb0a) \ - PROC_FIELD(u64, mb0s) \ - PROC_FIELD(u64, mb1a) \ - PROC_FIELD(u64, mb1s) \ - PROC_FIELD(u64, r0x) \ - PROC_FIELD(u64, r1x) \ - PROC_FIELD(u64, r2x) \ - PROC_FIELD(u64, r3x) \ - PROC_FIELD(u64, s0) \ - PROC_FIELD(u64, s1) \ - PROC_FIELD(u64, s2) \ - PROC_FIELD(u64, s3) \ - PROC_FIELD(u64, s4) \ - PROC_FIELD(u64, s5) \ - PROC_FIELD(u64, s6) \ - PROC_FIELD(u64, s7) - -struct Proc { -#define PROC_FIELD(type, name) type name; - PROC_FIELDS -#undef PROC_FIELD -}; - -u64 arch_proc_mb0_addr(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - return proc_get(core, pix)->mb0a; -} - -u64 arch_proc_mb0_size(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - return proc_get(core, pix)->mb0s; -} - -u64 arch_proc_mb1_addr(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - return proc_get(core, pix)->mb1a; -} - -u64 arch_proc_mb1_size(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - return proc_get(core, pix)->mb1s; -} - -u64 arch_proc_ip_addr(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - return proc_get(core, pix)->ip; -} - -u64 arch_proc_sp_addr(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - return proc_get(core, pix)->sp; -} - -u64 arch_proc_slice(const Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - (void)core; - (void)pix; - - return 1; -} - -void _free_memory_block(Core *core, u64 addr, u64 size) { - assert(core); - assert(size); - - for (u64 i = 0; i < size; ++i) { - mvec_free(core, addr + i); - } -} - -void arch_on_proc_kill(Core *core) { - assert(core); - assert(core->pnum > 1); - - Proc *pfst = proc_fetch(core, core->pfst); - - _free_memory_block(core, pfst->mb0a, pfst->mb0s); - - if (pfst->mb1s) { - _free_memory_block(core, pfst->mb1a, pfst->mb1s); - } - - memcpy(pfst, &g_dead_proc, sizeof(Proc)); -} - -#if ACTION == ACT_BENCH || ACTION == ACT_NEW -void arch_anc_init(Core *core, u64 size) { - assert(core); - -#if ANC_HALF == 1 - u64 addr = U64_HALF; -#else - u64 addr = 0; -#endif - - for (int i = 0; i < ANC_CLONES; ++i) { - u64 addr_clone = addr + ((MVEC_SIZE / ANC_CLONES) * i); - - Proc *panc = proc_fetch(core, i); - - panc->mb0a = addr_clone; - panc->mb0s = size; - panc->ip = addr_clone; - panc->sp = addr_clone; - } -} -#endif - -u8 _get_inst(const Core *core, u64 addr) { - assert(core); - - return mvec_get_inst(core, addr) % INST_COUNT; -} - -void _increment_ip(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - - proc->ip++; - proc->sp = proc->ip; -} - -bool _is_between(u8 inst, u8 lo, u8 hi) { - assert(inst < INST_COUNT); - assert(lo < INST_COUNT); - assert(hi < INST_COUNT); - assert(lo < hi); - - return (inst >= lo) && (inst <= hi); -} - -bool _is_key(u8 inst) { - assert(inst < INST_COUNT); - - return _is_between(inst, keya, keyp); -} - -bool _is_lock(u8 inst) { - assert(inst < INST_COUNT); - - return _is_between(inst, loka, lokp); -} - -bool _is_rmod(u8 inst) { - assert(inst < INST_COUNT); - - return _is_between(inst, nop0, nop3); -} - -bool _key_lock_match(u8 key, u8 lock) { - assert(key < INST_COUNT); - assert(lock < INST_COUNT); - assert(_is_key(key)); - - return (key - keya) == (lock - loka); -} - -bool _seek(Core *core, u64 pix, bool fwrd) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u8 next = _get_inst(core, proc->ip + 1); - - if (!_is_key(next)) { - _increment_ip(core, pix); - return false; - } - - u8 spin = _get_inst(core, proc->sp); - - if (_key_lock_match(next, spin)) { - return true; - } - - if (fwrd) { - proc->sp++; - } else { - proc->sp--; - } - - return false; -} - -void _jump(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - -#ifndef NDEBUG - u8 next = _get_inst(core, proc->ip + 1); - u8 spin = _get_inst(core, proc->sp); - assert(_is_key(next)); - assert(_is_lock(spin)); - assert(_key_lock_match(next, spin)); -#endif - - proc->ip = proc->sp; -} - -void _get_reg_addr_list(Core *core, u64 pix, u64 **rlist, int rcount, bool offset) { - assert(core); - assert(proc_is_live(core, pix)); - - assert(rlist); - assert(rcount); - assert(rcount < 4); - - Proc *proc = proc_fetch(core, pix); - u64 madr = proc->ip + (offset ? 2 : 1); - - for (int i = 0; i < rcount; ++i) { - rlist[i] = &proc->r0x; - } - - for (int i = 0; i < rcount; ++i) { - u64 mnxt = madr + i; - u8 mins = _get_inst(core, mnxt); - - if (!_is_rmod(mins)) { - break; - } - - switch (mins) { - case nop0: - rlist[i] = &proc->r0x; - break; - case nop1: - rlist[i] = &proc->r1x; - break; - case nop2: - rlist[i] = &proc->r2x; - break; - case nop3: - rlist[i] = &proc->r3x; - break; - } - } -} - -void _addr(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u64 *reg; - -#ifndef NDEBUG - u8 next = _get_inst(core, proc->ip + 1); - u8 spin = _get_inst(core, proc->sp); - assert(_is_key(next)); - assert(_is_lock(spin)); - assert(_key_lock_match(next, spin)); -#endif - - _get_reg_addr_list(core, pix, ®, 1, true); - *reg = proc->sp; - - _increment_ip(core, pix); -} - -void _ifnz(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u64 *reg; - - _get_reg_addr_list(core, pix, ®, 1, false); - - u64 jmod = _is_rmod(_get_inst(core, proc->ip + 1)) ? 1 : 0; - u64 rmod = *reg ? 1 : 2; - - proc->ip += jmod + rmod; - proc->sp = proc->ip; -} - -void _free_child_memory_of(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - - assert(proc->mb1s); - - _free_memory_block(core, proc->mb1a, proc->mb1s); - - proc->mb1a = 0; - proc->mb1s = 0; -} - -void _alloc(Core *core, u64 pix, bool fwrd) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u64 *regs[2]; - - _get_reg_addr_list(core, pix, regs, 2, false); - - u64 bsize = *regs[0]; - - // do nothing if block-size is zero - if (!bsize) { - _increment_ip(core, pix); - return; - } - - // do nothing if sp is not adjacent to allocated mem. block - if (proc->mb1s) { - u64 exp_addr = proc->mb1a; - - if (fwrd) { - exp_addr += proc->mb1s; - } else { - exp_addr--; - } - - if (proc->sp != exp_addr) { - _increment_ip(core, pix); - return; - } - } - - // allocation was successful, store block address on register - if (proc->mb1s == bsize) { - _increment_ip(core, pix); - *regs[1] = proc->mb1a; - return; - } - - // sp collided with another allocated block, clear and try again - if (mvec_is_alloc(core, proc->sp)) { - if (proc->mb1s) { - _free_child_memory_of(core, pix); - } - - if (fwrd) { - proc->sp++; - } else { - proc->sp--; - } - - return; - } - - // otherwise enlarge block - mvec_alloc(core, proc->sp); - - // adjust child block address and size - if (!proc->mb1s || !fwrd) { - proc->mb1a = proc->sp; - } - - proc->mb1s++; - - // move sp to new location - if (fwrd) { - proc->sp++; - } else { - proc->sp--; - } -} - -void _bswap(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - - if (proc->mb1s) { - u64 tmpa = proc->mb0a; - u64 tmps = proc->mb0s; - - proc->mb0a = proc->mb1a; - proc->mb0s = proc->mb1s; - proc->mb1a = tmpa; - proc->mb1s = tmps; - } - - _increment_ip(core, pix); -} - -void _bclear(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - - if (proc->mb1s) { - _free_child_memory_of(core, pix); - } - - _increment_ip(core, pix); -} - -void _split(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - - if (proc->mb1s) { - Proc child = {0}; - - child.ip = proc->mb1a; - child.sp = proc->mb1a; - child.mb0a = proc->mb1a; - child.mb0s = proc->mb1s; - - proc->mb1a = 0; - proc->mb1s = 0; - - proc_new(core, &child); - } else { - assert(!proc->mb1a); - } - - _increment_ip(core, pix); -} - -void _3rop(Core *core, u64 pix, u8 inst) { - assert(core); - assert(proc_is_live(core, pix)); - - u64 *regs[3]; - - _get_reg_addr_list(core, pix, regs, 3, false); - - switch (inst) { - case addn: - *regs[0] = *regs[1] + *regs[2]; - break; - case subn: - *regs[0] = *regs[1] - *regs[2]; - break; - case muln: - *regs[0] = *regs[1] * *regs[2]; - break; - case divn: - // do nothing on div. by zero - if (*regs[2]) { - *regs[0] = *regs[1] / *regs[2]; - } - - break; - default: - assert(false); - } - - _increment_ip(core, pix); -} - -void _1rop(Core *core, u64 pix, u8 inst) { - assert(core); - assert(proc_is_live(core, pix)); - - u64 *reg; - - _get_reg_addr_list(core, pix, ®, 1, false); - - switch (inst) { - case incn: - (*reg)++; - break; - case decn: - (*reg)--; - break; - case notn: - *reg = !(*reg); - break; - case shfl: - *reg <<= 1; - break; - case shfr: - *reg >>= 1; - break; - case zero: - *reg = 0; - break; - case unit: - *reg = 1; - break; - default: - assert(false); - } - - _increment_ip(core, pix); -} - -void _push(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u64 *reg; - - _get_reg_addr_list(core, pix, ®, 1, false); - - proc->s7 = proc->s6; - proc->s6 = proc->s5; - proc->s5 = proc->s4; - proc->s4 = proc->s3; - proc->s3 = proc->s2; - proc->s2 = proc->s1; - proc->s1 = proc->s0; - proc->s0 = *reg; - - _increment_ip(core, pix); -} - -void _pop(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u64 *reg; - - _get_reg_addr_list(core, pix, ®, 1, false); - - *reg = proc->s0; - proc->s0 = proc->s1; - proc->s1 = proc->s2; - proc->s2 = proc->s3; - proc->s3 = proc->s4; - proc->s4 = proc->s5; - proc->s5 = proc->s6; - proc->s6 = proc->s7; - proc->s7 = 0; - - _increment_ip(core, pix); -} - -int _sp_dir(u64 src, u64 dst) { - if (src == dst) { - return 0; - } else if (src - dst <= dst - src) { - return -1; - } else { - return 1; - } -} - -void _load(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u64 *regs[2]; - - _get_reg_addr_list(core, pix, regs, 2, false); - - int sp_dir = _sp_dir(proc->sp, *regs[0]); - - if (sp_dir == 1) { - proc->sp++; - } else if (sp_dir == -1) { - proc->sp--; - } else { - *regs[1] = mvec_get_inst(core, *regs[0]); - _increment_ip(core, pix); - } -} - -bool _is_writeable_by(const Core *core, u64 addr, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - return !mvec_is_alloc(core, addr) || mvec_is_proc_owner(core, addr, pix); -} - -void _write(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u64 *regs[2]; - - _get_reg_addr_list(core, pix, regs, 2, false); - - int sp_dir = _sp_dir(proc->sp, *regs[0]); - - if (sp_dir == 1) { - proc->sp++; - } else if (sp_dir == -1) { - proc->sp--; - } else { - if (_is_writeable_by(core, *regs[0], pix)) { - mvec_set_inst(core, *regs[0], *regs[1] % INST_CAPS); - } - - _increment_ip(core, pix); - } -} - -void _2rop(Core *core, u64 pix, u8 inst) { - assert(core); - assert(proc_is_live(core, pix)); - - u64 *regs[2]; - - _get_reg_addr_list(core, pix, regs, 2, false); - - switch (inst) { - case dupl: - *regs[1] = *regs[0]; - break; - case swap: - { - u64 tmp = *regs[0]; - *regs[0] = *regs[1]; - *regs[1] = tmp; - } - - break; - default: - assert(false); - } - - _increment_ip(core, pix); -} - -void arch_proc_step(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - Proc *proc = proc_fetch(core, pix); - u8 inst = _get_inst(core, proc->ip); - - switch (inst) { - case jmpb: - if (_seek(core, pix, false)) { - _jump(core, pix); - } - - break; - case jmpf: - if (_seek(core, pix, true)) { - _jump(core, pix); - } - - break; - case adrb: - if (_seek(core, pix, false)) { - _addr(core, pix); - } - - break; - case adrf: - if (_seek(core, pix, true)) { - _addr(core, pix); - } - - break; - case ifnz: - _ifnz(core, pix); - break; - case allb: - _alloc(core, pix, false); - break; - case allf: - _alloc(core, pix, true); - break; - case bswp: - _bswap(core, pix); - break; - case bclr: - _bclear(core, pix); - break; - case splt: - _split(core, pix); - break; - case addn: - case subn: - case muln: - case divn: - _3rop(core, pix, inst); - break; - case incn: - case decn: - case notn: - case shfl: - case shfr: - case zero: - case unit: - _1rop(core, pix, inst); - break; - case pshn: - _push(core, pix); - break; - case popn: - _pop(core, pix); - break; - case load: - _load(core, pix); - break; - case wrte: - _write(core, pix); - break; - case dupl: - case swap: - _2rop(core, pix, inst); - break; - default: - _increment_ip(core, pix); - break; - } - - return; -} - -#ifndef NDEBUG -void arch_validate_proc(const Core *core, u64 pix) { - assert(core); - - const Proc *proc = proc_get(core, pix); - - assert(proc->mb0s); - - if (proc->mb1a) { - assert(proc->mb1s); - } - - for (u64 i = 0; i < proc->mb0s; ++i) { - u64 addr = proc->mb0a + i; - assert(mvec_is_alloc(core, addr)); - assert(mvec_is_proc_owner(core, addr, pix)); - } - - for (u64 i = 0; i < proc->mb1s; ++i) { - u64 addr = proc->mb1a + i; - assert(mvec_is_alloc(core, addr)); - assert(mvec_is_proc_owner(core, addr, pix)); - } -} -#endif - -wchar_t arch_symbol(u8 inst) { - switch (inst % INST_COUNT) { -#define INST(name, symb) case name: return symb; - INST_LIST -#undef INST - } - - assert(false); - return L'\0'; -} - -void arch_mnemonic(u8 inst, char *buff) { - assert(buff); - - switch (inst % INST_COUNT) { -#define INST(name, symb) case name: snprintf(buff, MNEMONIC_BUFF_SIZE, #name); return; - INST_LIST -#undef INST - } - - assert(false); -} diff --git a/src/graphics.c b/src/graphics.c deleted file mode 100644 index 780b879..0000000 --- a/src/graphics.c +++ /dev/null @@ -1,238 +0,0 @@ -// Project: Salis -// Author: Paul Oliver -// Email: contact@pauloliver.dev - -/* - * This module renders the contents of the VM memory buffer into a 7 channel - * image. It supports zooming in and out, condensing the state of several - * bytes of memory into single pixels, when zoomed out. When zoomed in, each - * pixel represents a single byte in memory. - */ - -u64 g_gfx_vsiz; // zoom level - -u64 *g_gfx_inst; // instruction channel -u64 *g_gfx_mall; // allocated state channel -u64 *g_gfx_mbst; // memory block start channel -u64 *g_gfx_mb0s; // selected organism's memory block #1 channel -u64 *g_gfx_mb1s; // selected organism's memory block #2 channel -u64 *g_gfx_ipas; // selected organism's IP channel -u64 *g_gfx_spas; // selected organism's SP channel - -void gfx_init(u64 vsiz) { - assert(vsiz); - - g_gfx_vsiz = vsiz; - - g_gfx_inst = calloc(g_gfx_vsiz, sizeof(u64)); - g_gfx_mall = calloc(g_gfx_vsiz, sizeof(u64)); - g_gfx_mbst = calloc(g_gfx_vsiz, sizeof(u64)); - g_gfx_mb0s = calloc(g_gfx_vsiz, sizeof(u64)); - g_gfx_mb1s = calloc(g_gfx_vsiz, sizeof(u64)); - g_gfx_ipas = calloc(g_gfx_vsiz, sizeof(u64)); - g_gfx_spas = calloc(g_gfx_vsiz, sizeof(u64)); - - assert(g_gfx_inst); - assert(g_gfx_mall); - assert(g_gfx_mbst); - assert(g_gfx_mb0s); - assert(g_gfx_mb1s); - assert(g_gfx_ipas); - assert(g_gfx_spas); -} - -void gfx_free() { - if (g_gfx_vsiz == 0) { - return; - } - - assert(g_gfx_inst); - assert(g_gfx_mall); - assert(g_gfx_mbst); - assert(g_gfx_mb0s); - assert(g_gfx_mb1s); - assert(g_gfx_ipas); - assert(g_gfx_spas); - - g_gfx_vsiz = 0; - - free(g_gfx_inst); - free(g_gfx_mall); - free(g_gfx_mbst); - free(g_gfx_mb0s); - free(g_gfx_mb1s); - free(g_gfx_ipas); - free(g_gfx_spas); - - g_gfx_inst = NULL; - g_gfx_mall = NULL; - g_gfx_mbst = NULL; - g_gfx_mb0s = NULL; - g_gfx_mb1s = NULL; - g_gfx_ipas = NULL; - g_gfx_spas = NULL; -} - -void gfx_resize(u64 vsiz) { - assert(vsiz); - - gfx_free(); - gfx_init(vsiz); -} - -void gfx_render_inst(const Core *core, u64 pos, u64 zoom) { - assert(core); - - for (u64 i = 0; i < g_gfx_vsiz; ++i) { - g_gfx_inst[i] = 0; - g_gfx_mall[i] = 0; - - for (u64 j = 0; j < zoom; ++j) { - u64 addr = pos + (i * zoom) + j; - - g_gfx_inst[i] += mvec_get_byte(core, addr); - g_gfx_mall[i] += mvec_is_alloc(core, addr) ? 1 : 0; - } - } -} - -void gfx_clear_array(u64 *arry) { - assert(arry); - memset(arry, 0, g_gfx_vsiz * sizeof(u64)); -} - -#ifdef MVEC_LOOP -void gfx_accumulate_pixel(u64 pos, u64 zoom, u64 pixa, u64 *arry) { - assert(arry); - - u64 beg_mod = pos % MVEC_SIZE; - u64 end_mod = beg_mod + (g_gfx_vsiz * zoom); - u64 pix_mod = pixa % MVEC_SIZE; - -#ifndef NDEBUG - u64 inc_cnt = 0; -#endif - - while (pix_mod < end_mod) { - if (pix_mod >= beg_mod && pix_mod < end_mod) { - u64 pixi = (pix_mod - beg_mod) / zoom; - assert(pixi < g_gfx_vsiz); - arry[pixi]++; - -#ifndef NDEBUG - inc_cnt++; -#endif - } - - pix_mod += MVEC_SIZE; - } - - -#ifndef NDEBUG - if (zoom != 1) { - assert(inc_cnt <= 2); - } -#endif -} -#else -void gfx_accumulate_pixel(u64 pos, u64 zoom, u64 pixa, u64 *arry) { - assert(arry); - - u64 end = pos + (g_gfx_vsiz * zoom); - - if (pixa < pos || pixa >= end) { - return; - } - - u64 pixi = (pixa - pos) / zoom; - assert(pixi < g_gfx_vsiz); - arry[pixi]++; -} -#endif - -void gfx_render_mbst(const Core *core, u64 pos, u64 zoom) { - assert(core); - - gfx_clear_array(g_gfx_mbst); - - for (u64 pix = core->pfst; pix <= core->plst; ++pix) { - u64 mb0a = arch_proc_mb0_addr(core, pix); - u64 mb1a = arch_proc_mb1_addr(core, pix); - - gfx_accumulate_pixel(pos, zoom, mb0a, g_gfx_mbst); - gfx_accumulate_pixel(pos, zoom, mb1a, g_gfx_mbst); - } -} - -void gfx_render_mb0s(const Core *core, u64 pos, u64 zoom, u64 psel) { - assert(core); - - gfx_clear_array(g_gfx_mb0s); - - if (psel < core->pfst || psel > core->plst) { - return; - } - - u64 mb0a = arch_proc_mb0_addr(core, psel); - u64 mb0s = arch_proc_mb0_size(core, psel); - - for (u64 i = 0; i < mb0s; ++i) { - gfx_accumulate_pixel(pos, zoom, mb0a + i, g_gfx_mb0s); - } -} - -void gfx_render_mb1s(const Core *core, u64 pos, u64 zoom, u64 psel) { - assert(core); - - gfx_clear_array(g_gfx_mb1s); - - if (psel < core->pfst || psel > core->plst) { - return; - } - - u64 mb1a = arch_proc_mb1_addr(core, psel); - u64 mb1s = arch_proc_mb1_size(core, psel); - - for (u64 i = 0; i < mb1s; ++i) { - gfx_accumulate_pixel(pos, zoom, mb1a + i, g_gfx_mb1s); - } -} - -void gfx_render_ipas(const Core *core, u64 pos, u64 zoom, u64 psel) { - assert(core); - - gfx_clear_array(g_gfx_ipas); - - if (psel < core->pfst || psel > core->plst) { - return; - } - - u64 ipa = arch_proc_ip_addr(core, psel); - - gfx_accumulate_pixel(pos, zoom, ipa, g_gfx_ipas); -} - -void gfx_render_spas(const Core *core, u64 pos, u64 zoom, u64 psel) { - assert(core); - - gfx_clear_array(g_gfx_spas); - - if (psel < core->pfst || psel > core->plst) { - return; - } - - u64 spa = arch_proc_sp_addr(core, psel); - - gfx_accumulate_pixel(pos, zoom, spa, g_gfx_spas); -} - -void gfx_render(const Core *core, u64 pos, u64 zoom, u64 psel) { - assert(core); - - gfx_render_inst(core, pos, zoom); - gfx_render_mbst(core, pos, zoom); - gfx_render_mb0s(core, pos, zoom, psel); - gfx_render_mb1s(core, pos, zoom, psel); - gfx_render_ipas(core, pos, zoom, psel); - gfx_render_spas(core, pos, zoom, psel); -} diff --git a/src/salis.c b/src/salis.c deleted file mode 100644 index 348385f..0000000 --- a/src/salis.c +++ /dev/null @@ -1,811 +0,0 @@ -// Project: Salis -// Author: Paul Oliver -// Email: contact@pauloliver.dev - -/* - * Core of the Salis simulator. Can be built against different architectures - * and UI modules. - */ - -#include <assert.h> -#include <ctype.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <threads.h> - -#ifdef COMPRESS -#include <zlib.h> -#endif - -#define ACT_BENCH (1) -#define ACT_LOAD (2) -#define ACT_NEW (3) - -#define ASM_LINE_LEN (0x100) - -#define MALL_FLAG (0x80) -#define IPCM_FLAG (0x80) -#define INST_CAPS (0x80) -#define INST_MASK (0x7f) - -#define U64_HALF (0x8000000000000000) - -typedef struct Core Core; -typedef struct Ipcm Ipcm; -typedef struct Proc Proc; -typedef thrd_t Thread; -typedef uint64_t u64; -typedef uint8_t u8; - -struct Core { - u64 mall; - u64 muta[4]; - u64 pnum; - u64 pcap; - u64 pfst; - u64 plst; - u64 pcur; - u64 psli; - u64 ncyc; - - Thread thread; - u64 tix; - - u64 ivpt; - u8 *iviv; - u64 *ivav; - - Proc *pvec; - u8 mvec[MVEC_SIZE]; - u8 tgap[TGAP_SIZE]; -}; - -Core g_cores[CORE_COUNT]; -u64 g_steps; -u64 g_syncs; -#if ACTION == ACT_LOAD || ACTION == ACT_NEW -char g_asav_pbuf[AUTO_SAVE_NAME_LEN]; -#endif -const Proc g_dead_proc; - -#include ARCH_SOURCE - -#if ACTION == ACT_BENCH || ACTION == ACT_NEW -char g_mnemo_table[0x100][MNEMONIC_BUFF_SIZE]; -#endif - -#ifdef MVEC_LOOP -u64 mvec_loop(u64 addr) { - return addr % MVEC_SIZE; -} -#endif - -bool mvec_is_alloc(const Core *core, u64 addr) { - assert(core); -#ifdef 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(Core *core, u64 addr) { - assert(core); - assert(!mvec_is_alloc(core, addr)); -#ifdef 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(Core *core, u64 addr) { - assert(core); - assert(mvec_is_alloc(core, addr)); -#ifdef MVEC_LOOP - core->mvec[mvec_loop(addr)] ^= MALL_FLAG; -#else - assert(addr < MVEC_SIZE); - core->mvec[addr] ^= MALL_FLAG; -#endif - core->mall--; -} - -u8 mvec_get_byte(const Core *core, u64 addr) { - assert(core); -#ifdef MVEC_LOOP - return core->mvec[mvec_loop(addr)]; -#else - if (addr < MVEC_SIZE) { - return core->mvec[addr]; - } else { - return 0; - } -#endif -} - -u8 mvec_get_inst(const Core *core, u64 addr) { - assert(core); -#ifdef 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(Core *core, u64 addr, u8 inst) { - assert(core); - assert(inst < INST_CAPS); -#ifdef 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 MUTA_FLIP_BIT == 1 -void mvec_flip_bit(Core *core, u64 addr, int bit) { - assert(core); - assert(bit < 8); - core->mvec[addr] ^= (1 << bit) & INST_MASK; -} -#endif - -bool mvec_is_proc_owner(const Core *core, u64 addr, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - u64 mb0a = arch_proc_mb0_addr(core, pix); - u64 mb0s = arch_proc_mb0_size(core, pix); - - if (((addr - mb0a) % MVEC_SIZE) < mb0s) { - return true; - } - - u64 mb1a = arch_proc_mb1_addr(core, pix); - u64 mb1s = arch_proc_mb1_size(core, pix); - - if (((addr - mb1a) % MVEC_SIZE) < mb1s) { - return true; - } - - return false; -} - -u64 mvec_get_owner(const Core *core, u64 addr) { - assert(core); - assert(mvec_is_alloc(core, addr)); - - for (u64 pix = core->pfst; pix <= core->plst; ++pix) { - if (mvec_is_proc_owner(core, addr, pix)) { - return pix; - } - } - - assert(false); - return -1; -} - -#if ACTION == ACT_BENCH || ACTION == ACT_NEW -u64 muta_smix(u64 *seed) { - assert(seed); - - u64 next = (*seed += 0x9e3779b97f4a7c15); - next = (next ^ (next >> 30)) * 0xbf58476d1ce4e5b9; - next = (next ^ (next >> 27)) * 0x94d049bb133111eb; - - return next ^ (next >> 31); -} -#endif - -u64 muta_ro64(u64 x, int k) { - return (x << k) | (x >> (64 - k)); -} - -u64 muta_next(Core *core) { - assert(core); - - u64 r = muta_ro64(core->muta[1] * 5, 7) * 9; - u64 t = core->muta[1] << 17; - - core->muta[2] ^= core->muta[0]; - core->muta[3] ^= core->muta[1]; - core->muta[1] ^= core->muta[2]; - core->muta[0] ^= core->muta[3]; - - core->muta[2] ^= t; - core->muta[3] = muta_ro64(core->muta[3], 45); - - return r; -} - -void muta_cosmic_ray(Core *core) { - assert(core); - - u64 a = muta_next(core) % MUTA_RANGE; - u64 b = muta_next(core); - - if (a < MVEC_SIZE) { -#if MUTA_FLIP_BIT == 1 - mvec_flip_bit(core, a, (int)(b % 8)); -#else - mvec_set_inst(core, a, b & INST_MASK); -#endif - } -} - -void proc_new(Core *core, const Proc *proc) { - assert(core); - assert(proc); - - if (core->pnum == core->pcap) { - u64 new_pcap = core->pcap * 2; - Proc *new_pvec = calloc(new_pcap, sizeof(Proc)); - - for (u64 pix = core->pfst; pix <= core->plst; ++pix) { - u64 iold = pix % core->pcap; - u64 inew = pix % new_pcap; - memcpy(&new_pvec[inew], &core->pvec[iold], sizeof(Proc)); - } - - free(core->pvec); - core->pcap = new_pcap; - core->pvec = new_pvec; - } - - core->pnum++; - core->plst++; - memcpy(&core->pvec[core->plst % core->pcap], proc, sizeof(Proc)); -} - -void proc_kill(Core *core) { - assert(core); - assert(core->pnum > 1); - - arch_on_proc_kill(core); - - core->pcur++; - core->pfst++; - core->pnum--; -} - -bool proc_is_live(const Core *core, u64 pix) { - assert(core); - - return pix >= core->pfst && pix <= core->plst; -} - -const Proc *proc_get(const Core *core, u64 pix) { - assert(core); - - if (proc_is_live(core, pix)) { - return &core->pvec[pix % core->pcap]; - } else { - return &g_dead_proc; - } -} - -Proc *proc_fetch(Core *core, u64 pix) { - assert(core); - assert(proc_is_live(core, pix)); - - return &core->pvec[pix % core->pcap]; -} - -#if ACTION == ACT_LOAD || ACTION == ACT_NEW -void core_save(FILE *f, const Core *core) { - assert(f); - assert(core); - - fwrite(&core->mall, sizeof(u64), 1, f); - fwrite( core->muta, sizeof(u64), 4, f); - fwrite(&core->pnum, sizeof(u64), 1, f); - fwrite(&core->pcap, sizeof(u64), 1, f); - fwrite(&core->pfst, sizeof(u64), 1, f); - fwrite(&core->plst, sizeof(u64), 1, f); - fwrite(&core->pcur, sizeof(u64), 1, f); - fwrite(&core->psli, sizeof(u64), 1, f); - fwrite(&core->ncyc, sizeof(u64), 1, f); - fwrite(&core->ivpt, sizeof(u64), 1, f); - - fwrite(core->iviv, sizeof(u8), SYNC_INTERVAL, f); - fwrite(core->ivav, sizeof(u64), SYNC_INTERVAL, f); - fwrite(core->pvec, sizeof(Proc), core->pcap, f); - fwrite(core->mvec, sizeof(u8), MVEC_SIZE, f); -} -#endif - -#if ACTION == ACT_BENCH || ACTION == ACT_NEW -u64 core_assemble_ancestor(int cix, const char *anc) { - assert(cix >= 0 && cix < CORE_COUNT); - assert(anc); - - if (anc[0] == '_') { - return 0; - } - - FILE *f = fopen(anc, "r"); - - assert(f); - -#if ANC_HALF == 1 - u64 addr = U64_HALF; -#else - u64 addr = 0; -#endif - - char line[ASM_LINE_LEN] = {0}; - Core *core = &g_cores[cix]; - - for (; fgets(line, ASM_LINE_LEN, f); ++addr) { -#ifndef NDEBUG - bool line_ok = false; -#endif - - line[strcspn(line, "\r\n")] = '\0'; - - for (int i = 0; i < 0x100; ++i) { - if (strcmp(line, g_mnemo_table[i]) == 0) { - for (u64 j = 0; j < ANC_CLONES; ++j) { - u64 addr_clone = addr + ((MVEC_SIZE / ANC_CLONES) * j); - - mvec_alloc(core, addr_clone); - mvec_set_inst(core, addr_clone, i); - } - -#ifndef NDEBUG - line_ok = true; -#endif - break; - } - } - - assert(line_ok); - } - - fclose(f); - - return addr; -} - -void core_init(int cix, u64 *seed, const char *anc) { - assert(cix >= 0 && cix < CORE_COUNT); - assert(seed); - assert(anc); - - Core *core = &g_cores[cix]; - - if (*seed) { - core->muta[0] = muta_smix(seed); - core->muta[1] = muta_smix(seed); - core->muta[2] = muta_smix(seed); - core->muta[3] = muta_smix(seed); - } - - core->pnum = ANC_CLONES; - core->pcap = ANC_CLONES; - core->plst = ANC_CLONES - 1; - core->iviv = calloc(SYNC_INTERVAL, sizeof(u8)); - core->ivav = calloc(SYNC_INTERVAL, sizeof(u64)); - core->pvec = calloc(core->pcap, sizeof(Proc)); - - assert(core->iviv); - assert(core->ivav); - assert(core->pvec); - - u64 anc_size = core_assemble_ancestor(cix, anc); - -#if ANC_HALF == 1 - anc_size -= U64_HALF; -#endif - - arch_anc_init(core, anc_size); -} -#endif - -#if ACTION == ACT_LOAD -void core_load(FILE *f, Core *core) { - assert(f); - assert(core); - - fread(&core->mall, sizeof(u64), 1, f); - fread( core->muta, sizeof(u64), 4, f); - fread(&core->pnum, sizeof(u64), 1, f); - fread(&core->pcap, sizeof(u64), 1, f); - fread(&core->pfst, sizeof(u64), 1, f); - fread(&core->plst, sizeof(u64), 1, f); - fread(&core->pcur, sizeof(u64), 1, f); - fread(&core->psli, sizeof(u64), 1, f); - fread(&core->ncyc, sizeof(u64), 1, f); - fread(&core->ivpt, sizeof(u64), 1, f); - - core->iviv = calloc(SYNC_INTERVAL, sizeof(u8)); - core->ivav = calloc(SYNC_INTERVAL, sizeof(u64)); - core->pvec = calloc(core->pcap, sizeof(Proc)); - - assert(core->iviv); - assert(core->ivav); - assert(core->pvec); - - fread(core->iviv, sizeof(u8), SYNC_INTERVAL, f); - fread(core->ivav, sizeof(u64), SYNC_INTERVAL, f); - fread(core->pvec, sizeof(Proc), core->pcap, f); - fread(core->mvec, sizeof(u8), MVEC_SIZE, f); -} -#endif - -void core_pull_ipcm(Core *core) { - assert(core); - assert(core->ivpt < SYNC_INTERVAL); - - u8 *iinst = &core->iviv[core->ivpt]; - u64 *iaddr = &core->ivav[core->ivpt]; - - if ((*iinst & IPCM_FLAG) != 0) { - mvec_set_inst(core, *iaddr, *iinst & INST_MASK); - - *iinst = 0; - *iaddr = 0; - } - - assert(*iinst == 0); - assert(*iaddr == 0); -} - -void core_push_ipcm(Core *core, u8 inst, u64 addr) { - assert(core); - assert(core->ivpt < SYNC_INTERVAL); - assert((inst & IPCM_FLAG) == 0); - - u8 *iinst = &core->iviv[core->ivpt]; - u64 *iaddr = &core->ivav[core->ivpt]; - - assert(*iinst == 0); - assert(*iaddr == 0); - - *iinst = inst | IPCM_FLAG; - *iaddr = addr; -} - -void core_step(Core *core) { - assert(core); - - if (core->psli != 0) { - core_pull_ipcm(core); - arch_proc_step(core, core->pcur); - - core->psli--; - core->ivpt++; - - return; - } - - if (core->pcur != core->plst) { - core->psli = arch_proc_slice(core, ++core->pcur); - core_step(core); - return; - } - - core->pcur = core->pfst; - core->psli = arch_proc_slice(core, core->pcur); - core->ncyc++; - - while (core->mall > MVEC_SIZE / 2 && core->pnum > 1) { - proc_kill(core); - } - - muta_cosmic_ray(core); - core_step(core); -} - -#if ACTION == ACT_LOAD || ACTION == ACT_NEW -void salis_save(const char *path) { -#ifdef 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 < CORE_COUNT; ++i) { - core_save(f, &g_cores[i]); - } - - fwrite(&g_steps, sizeof(u64), 1, f); - fwrite(&g_syncs, sizeof(u64), 1, f); - fclose(f); - -#ifdef 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 (g_steps % AUTO_SAVE_INTERVAL != 0) { - return; - } - -#ifndef NDEBUG - int rem = snprintf( -#else - snprintf( -#endif - g_asav_pbuf, - AUTO_SAVE_NAME_LEN, - "%s-%#018lx", - SIM_PATH, - g_steps - ); - - assert(rem >= 0); - assert(rem < AUTO_SAVE_NAME_LEN); - - salis_save(g_asav_pbuf); -} -#endif - -#if ACTION == ACT_BENCH || ACTION == ACT_NEW -void salis_init() { - for (int i = 0; i < 0x100; ++i) { - arch_mnemonic(i, g_mnemo_table[i]); - } - - u64 seed = SEED; - char anc_list[] = ANC_LIST; - - assert(anc_list); - - for (int i = 0; i < CORE_COUNT; ++i) { - core_init(i, &seed, strtok(i ? NULL : anc_list, ",")); - } - -#if ACTION == ACT_NEW - salis_auto_save(); -#endif -} -#endif - -#if ACTION == ACT_LOAD -void salis_load() { -#ifdef 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; - -#ifdef 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 < CORE_COUNT; ++i) { - core_load(f, &g_cores[i]); - } - - fread(&g_steps, sizeof(u64), 1, f); - fread(&g_syncs, sizeof(u64), 1, f); - fclose(f); - -#ifdef COMPRESS - free(in); - free(out); -#endif -} -#endif - -int salis_thread(Core *core) { - assert(core); - - for (u64 i = 0; i < core->tix; ++i) { - core_step(core); - } - - return 0; -} - -void salis_run_thread(u64 ns) { - for (int i = 0; i < CORE_COUNT; ++i) { - g_cores[i].tix = ns; - - thrd_create( - &g_cores[i].thread, - (thrd_start_t)salis_thread, - &g_cores[i] - ); - } - - for (int i = 0; i < CORE_COUNT; ++i) { - thrd_join(g_cores[i].thread, NULL); - } - - g_steps += ns; -} - -void salis_sync() { - u8 *iviv0 = g_cores[0].iviv; - u64 *ivav0 = g_cores[0].ivav; - - for (int i = 1; i < CORE_COUNT; ++i) { - g_cores[i - 1].iviv = g_cores[i].iviv; - g_cores[i - 1].ivav = g_cores[i].ivav; - } - - g_cores[CORE_COUNT - 1].iviv = iviv0; - g_cores[CORE_COUNT - 1].ivav = ivav0; - - for (int i = 0; i < CORE_COUNT; ++i) { - g_cores[i].ivpt = 0; - } - - g_syncs++; -} - -void salis_loop(u64 ns, u64 dt) { - assert(dt); - - if (ns < dt) { - salis_run_thread(ns); - return; - } - - salis_run_thread(dt); - salis_sync(); -#if ACTION == ACT_LOAD || ACTION == ACT_NEW - salis_auto_save(); -#endif - salis_loop(ns - dt, SYNC_INTERVAL); -} - -#ifndef NDEBUG -void salis_validate_core(const Core *core) { - assert(core->plst >= core->pfst); - assert(core->pnum == core->plst + 1 - core->pfst); - assert(core->pnum <= core->pcap); - assert(core->pcur >= core->pfst && core->pcur <= core->plst); - assert(core->ncyc <= g_steps); - - u64 mall = 0; - - for (u64 i = 0; i < MVEC_SIZE; ++i) { - mall += mvec_is_alloc(core, i) ? 1 : 0; - } - - assert(core->mall == mall); - - for (u64 i = core->pfst; i <= core->plst; ++i) { - arch_validate_proc(core, i); - } - - for (u64 i = 0; i < SYNC_INTERVAL; ++i) { - u8 iinst = core->iviv[i]; - - if ((iinst & IPCM_FLAG) == 0) { - u64 iaddr = core->ivav[i]; - - assert(iinst == 0); - assert(iaddr == 0); - } - } - - assert(core->ivpt == g_steps % SYNC_INTERVAL); -} - -void salis_validate() { - assert(g_steps / SYNC_INTERVAL == g_syncs); - - for (int i = 0; i < CORE_COUNT; ++i) { - salis_validate_core(&g_cores[i]); - } -} -#endif - -void salis_step(u64 ns) { - assert(ns); - salis_loop(ns, SYNC_INTERVAL - (g_steps % SYNC_INTERVAL)); - -#ifndef NDEBUG - salis_validate(); -#endif -} - -void salis_free() { - for (int i = 0; i < CORE_COUNT; ++i) { - assert(g_cores[i].pvec); - assert(g_cores[i].iviv); - assert(g_cores[i].ivav); - - free(g_cores[i].pvec); - free(g_cores[i].iviv); - free(g_cores[i].ivav); - - g_cores[i].pvec = NULL; - g_cores[i].iviv = NULL; - g_cores[i].ivav = NULL; - } -} - -#include UI diff --git a/src/ui/curses.c b/ui/curses/ui.j2.c index d1f821d..4370774 100644 --- a/src/ui/curses.c +++ b/ui/curses/ui.j2.c @@ -1,30 +1,36 @@ +// Author: Paul Oliver <contact@pauloliver.dev> // Project: Salis -// Author: Paul Oliver -// Email: contact@pauloliver.dev -/* - * Implements a TUI for the Salis simulator using the ncurses library. - */ +// Implements a curses TUI for the Salis simulator. +// World view renders the contents of the VM memory buffer into a 7 channel image. +// It supports zooming in and out, condensing the state of several bytes of memory +// into single pixels (terminal cells). When zoomed in, each pixel represents a +// single byte in memory. -// GCC_EXTRA_FLAGS -lncursesw +{% set pane_width = 27 %} +{% set proc_field_width = 21 %} +{% set proc_page_lines = 12 %} -#include <curses.h> -#include <locale.h> -#include <time.h> +{% macro ctrl(x) %}('{{ x }}' & 0x1f){% endmacro %} -#define CTRL(x) ((x) & 0x1f) -#define PANE_WIDTH (27) -#define PROC_FIELD_WIDTH (21) -#define PROC_PAGE_LINES (12) +{% if not args.optimized %} + {% set min_fps = 5 %} + {% set max_fps = 10 %} +{% else %} + {% set min_fps = 30 %} + {% set max_fps = 60 %} +{% endif %} +// pages enum { PAGE_CORE, PAGE_PROCESS, PAGE_WORLD, PAGE_IPC, - PAGE_COUNT + PAGE_COUNT, }; +// color pairs enum { PAIR_NOUSE, PAIR_NORMAL, @@ -40,28 +46,39 @@ enum { PAIR_SELECTED_SP, }; -bool g_exit; -bool g_running; -unsigned g_core; -unsigned g_page; -bool g_proc_genes; -u64 g_proc_scroll; -u64 g_proc_field_scroll; -u64 g_proc_gene_scroll; -u64 g_proc_selected; -u64 g_wrld_pos; -u64 g_wrld_zoom; -bool g_wcursor_mode; -int g_wcursor_x; -int g_wcursor_y; -u64 g_wcursor_pointed; -u64 g_vlin; -u64 g_vsiz; -u64 g_vlin_rng; -u64 g_vsiz_rng; -u64 g_ivpt_scroll; -char *g_line_buff; -u64 g_step_block; +// GFX globals +uint64_t g_gfx_vsiz; // zoom level +uint64_t *g_gfx_inst; // instruction channel +uint64_t *g_gfx_mall; // allocated state channel +uint64_t *g_gfx_mbst; // memory block start channel +uint64_t *g_gfx_mb0s; // selected organism's memory block #1 channel +uint64_t *g_gfx_mb1s; // selected organism's memory block #2 channel +uint64_t *g_gfx_ipas; // selected organism's IP channel +uint64_t *g_gfx_spas; // selected organism's SP channel + +// TUI globals +bool g_exit; +bool g_running; +unsigned g_core; +unsigned g_page; +bool g_proc_genes; +uint64_t g_proc_scroll; +uint64_t g_proc_field_scroll; +uint64_t g_proc_gene_scroll; +uint64_t g_proc_selected; +uint64_t g_wrld_pos; +uint64_t g_wrld_zoom; +bool g_wcursor_mode; +int g_wcursor_x; +int g_wcursor_y; +uint64_t g_wcursor_pointed; +uint64_t g_vlin; +uint64_t g_vsiz; +uint64_t g_vlin_rng; +uint64_t g_vsiz_rng; +uint64_t g_ivpt_scroll; +char *g_line_buff; +uint64_t g_step_block; const wchar_t *g_zoomed_symbols = ( L"⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟" @@ -70,8 +87,231 @@ const wchar_t *g_zoomed_symbols = ( L"⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿" ); -#include "graphics.c" +// ---------------------------------------------------------------------------- +// GFX functions +// ---------------------------------------------------------------------------- +void gfx_init(uint64_t vsiz) { + assert(vsiz); + + g_gfx_vsiz = vsiz; + + g_gfx_inst = calloc(g_gfx_vsiz, sizeof(uint64_t)); + g_gfx_mall = calloc(g_gfx_vsiz, sizeof(uint64_t)); + g_gfx_mbst = calloc(g_gfx_vsiz, sizeof(uint64_t)); + g_gfx_mb0s = calloc(g_gfx_vsiz, sizeof(uint64_t)); + g_gfx_mb1s = calloc(g_gfx_vsiz, sizeof(uint64_t)); + g_gfx_ipas = calloc(g_gfx_vsiz, sizeof(uint64_t)); + g_gfx_spas = calloc(g_gfx_vsiz, sizeof(uint64_t)); + + assert(g_gfx_inst); + assert(g_gfx_mall); + assert(g_gfx_mbst); + assert(g_gfx_mb0s); + assert(g_gfx_mb1s); + assert(g_gfx_ipas); + assert(g_gfx_spas); +} + +void gfx_free() { + if (g_gfx_vsiz == 0) { + return; + } + + assert(g_gfx_inst); + assert(g_gfx_mall); + assert(g_gfx_mbst); + assert(g_gfx_mb0s); + assert(g_gfx_mb1s); + assert(g_gfx_ipas); + assert(g_gfx_spas); + + g_gfx_vsiz = 0; + + free(g_gfx_inst); + free(g_gfx_mall); + free(g_gfx_mbst); + free(g_gfx_mb0s); + free(g_gfx_mb1s); + free(g_gfx_ipas); + free(g_gfx_spas); + + g_gfx_inst = NULL; + g_gfx_mall = NULL; + g_gfx_mbst = NULL; + g_gfx_mb0s = NULL; + g_gfx_mb1s = NULL; + g_gfx_ipas = NULL; + g_gfx_spas = NULL; +} + +void gfx_resize(uint64_t vsiz) { + assert(vsiz); + + gfx_free(); + gfx_init(vsiz); +} + +void gfx_render_inst(const struct Core *core, uint64_t pos, uint64_t zoom) { + assert(core); + + for (uint64_t i = 0; i < g_gfx_vsiz; ++i) { + g_gfx_inst[i] = 0; + g_gfx_mall[i] = 0; + + for (uint64_t j = 0; j < zoom; ++j) { + uint64_t addr = pos + (i * zoom) + j; + + g_gfx_inst[i] += mvec_get_byte(core, addr); + g_gfx_mall[i] += mvec_is_alloc(core, addr) ? 1 : 0; + } + } +} + +void gfx_clear_array(uint64_t *arry) { + assert(arry); + memset(arry, 0, g_gfx_vsiz * sizeof(uint64_t)); +} + +{% if arch_vars.mvec_loop %} +void gfx_accumulate_pixel(uint64_t pos, uint64_t zoom, uint64_t pixa, uint64_t *arry) { + assert(arry); + + uint64_t beg_mod = pos % {{ mvec_size }}; + uint64_t end_mod = beg_mod + (g_gfx_vsiz * zoom); + uint64_t pix_mod = pixa % {{ mvec_size }}; + + {% if not args.optimized %} + uint64_t inc_cnt = 0; + {% endif %} + + while (pix_mod < end_mod) { + if (pix_mod >= beg_mod && pix_mod < end_mod) { + uint64_t pixi = (pix_mod - beg_mod) / zoom; + assert(pixi < g_gfx_vsiz); + arry[pixi]++; + + {% if not args.optimized %} + inc_cnt++; + {% endif %} + } + + pix_mod += {{ mvec_size }}; + } + + {% if not args.optimized %} + if (zoom != 1) { + assert(inc_cnt <= 2); + } + {% endif %} +} +{% endif %} + +{% if not arch_vars.mvec_loop %} +void gfx_accumulate_pixel(uint64_t pos, uint64_t zoom, uint64_t pixa, uint64_t *arry) { + assert(arry); + + uint64_t end = pos + (g_gfx_vsiz * zoom); + + if (pixa < pos || pixa >= end) { + return; + } + + uint64_t pixi = (pixa - pos) / zoom; + assert(pixi < g_gfx_vsiz); + arry[pixi]++; +} +{% endif %} + +void gfx_render_mbst(const struct Core *core, uint64_t pos, uint64_t zoom) { + assert(core); + + gfx_clear_array(g_gfx_mbst); + + for (uint64_t pix = core->pfst; pix <= core->plst; ++pix) { + uint64_t mb0a = arch_proc_mb0_addr(core, pix); + uint64_t mb1a = arch_proc_mb1_addr(core, pix); + + gfx_accumulate_pixel(pos, zoom, mb0a, g_gfx_mbst); + gfx_accumulate_pixel(pos, zoom, mb1a, g_gfx_mbst); + } +} +void gfx_render_mb0s(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) { + assert(core); + + gfx_clear_array(g_gfx_mb0s); + + if (psel < core->pfst || psel > core->plst) { + return; + } + + uint64_t mb0a = arch_proc_mb0_addr(core, psel); + uint64_t mb0s = arch_proc_mb0_size(core, psel); + + for (uint64_t i = 0; i < mb0s; ++i) { + gfx_accumulate_pixel(pos, zoom, mb0a + i, g_gfx_mb0s); + } +} + +void gfx_render_mb1s(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) { + assert(core); + + gfx_clear_array(g_gfx_mb1s); + + if (psel < core->pfst || psel > core->plst) { + return; + } + + uint64_t mb1a = arch_proc_mb1_addr(core, psel); + uint64_t mb1s = arch_proc_mb1_size(core, psel); + + for (uint64_t i = 0; i < mb1s; ++i) { + gfx_accumulate_pixel(pos, zoom, mb1a + i, g_gfx_mb1s); + } +} + +void gfx_render_ipas(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) { + assert(core); + + gfx_clear_array(g_gfx_ipas); + + if (psel < core->pfst || psel > core->plst) { + return; + } + + uint64_t ipa = arch_proc_ip_addr(core, psel); + + gfx_accumulate_pixel(pos, zoom, ipa, g_gfx_ipas); +} + +void gfx_render_spas(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) { + assert(core); + + gfx_clear_array(g_gfx_spas); + + if (psel < core->pfst || psel > core->plst) { + return; + } + + uint64_t spa = arch_proc_sp_addr(core, psel); + + gfx_accumulate_pixel(pos, zoom, spa, g_gfx_spas); +} + +void gfx_render(const struct Core *core, uint64_t pos, uint64_t zoom, uint64_t psel) { + assert(core); + + gfx_render_inst(core, pos, zoom); + gfx_render_mbst(core, pos, zoom); + gfx_render_mb0s(core, pos, zoom, psel); + gfx_render_mb1s(core, pos, zoom, psel); + gfx_render_ipas(core, pos, zoom, psel); + gfx_render_spas(core, pos, zoom, psel); +} + +// ---------------------------------------------------------------------------- +// TUI functions +// ---------------------------------------------------------------------------- void ui_line_buff_free() { if (g_line_buff) { free(g_line_buff); @@ -142,13 +382,14 @@ void ui_str_field(int l, const char *label, const char *value) { ui_line(false, l, PAIR_NORMAL, A_NORMAL, "%s : %18s", label, value); } -void ui_ulx_field(int l, const char *label, u64 value) { +void ui_ulx_field(int l, const char *label, uint64_t value) { assert(label); ui_line(false, l, PAIR_NORMAL, A_NORMAL, "%-4s : %#18lx", label, value); } void ui_print_core(int l) { ui_line(false, ++l, PAIR_HEADER, A_BOLD, "CORE [%d]", g_core); + ui_ulx_field(++l, "cycl", g_cores[g_core].cycl); ui_ulx_field(++l, "mall", g_cores[g_core].mall); ui_ulx_field(++l, "mut0", g_cores[g_core].muta[0]); ui_ulx_field(++l, "mut1", g_cores[g_core].muta[1]); @@ -160,38 +401,42 @@ void ui_print_core(int l) { ui_ulx_field(++l, "plst", g_cores[g_core].plst); ui_ulx_field(++l, "pcur", g_cores[g_core].pcur); ui_ulx_field(++l, "psli", g_cores[g_core].psli); - ui_ulx_field(++l, "ncyc", g_cores[g_core].ncyc); ui_ulx_field(++l, "ivpt", g_cores[g_core].ivpt); + + // Architecture specific fields + {% for type, val in arch_vars.core_fields %} + ui_ulx_field(++l, "{{ val }}", g_cores[g_core].{{ val }}); + {% endfor %} } -int ui_proc_pair(u64 pix) { +int ui_proc_pair(uint64_t pix) { if (pix == g_proc_selected) { return PAIR_SELECTED_PROC; - } else if (proc_is_live(&g_cores[g_core], pix)) { + } else if (mvec_proc_is_live(&g_cores[g_core], pix)) { return PAIR_LIVE_PROC; } else { return PAIR_NORMAL; } } -const char *ui_proc_state(u64 pix) { - return proc_is_live(&g_cores[g_core], pix) ? "live" : "dead"; +const char *ui_proc_state(uint64_t pix) { + return mvec_proc_is_live(&g_cores[g_core], pix) ? "live" : "dead"; } void ui_print_process_genome_header(int l) { ui_line(false, l++, PAIR_NORMAL, A_NORMAL, "%s : %18s : %s", "stat", "pix", "genome"); } -void ui_print_process_gene(int l, int gcol, u64 gidx, u64 mba, u64 pix, int pair) { - assert(gcol >= PANE_WIDTH + 2); +void ui_print_process_gene(int l, int gcol, uint64_t gidx, uint64_t mba, uint64_t pix, int pair) { + assert(gcol >= {{ pane_width }} + 2); assert(gcol < COLS); - assert(proc_is_live(&g_cores[g_core], pix)); + assert(mvec_proc_is_live(&g_cores[g_core], pix)); assert(pair == PAIR_SELECTED_MB1 || pair == PAIR_SELECTED_MB2); - const Core *core = &g_cores[g_core]; + const struct Core *core = &g_cores[g_core]; - u64 addr = mba + gidx; - u8 byte = mvec_get_byte(core, addr); + uint64_t addr = mba + gidx; + uint8_t byte = mvec_get_byte(core, addr); wchar_t gsym[2] = { arch_symbol(byte), L'\0' }; cchar_t cchar = { 0 }; @@ -210,29 +455,29 @@ void ui_print_process_gene(int l, int gcol, u64 gidx, u64 mba, u64 pix, int pair mvadd_wch(l, gcol, &cchar); } -void ui_print_process_genes(int l, u64 pix) { +void ui_print_process_genes(int l, uint64_t pix) { ui_line(true, l, ui_proc_pair(pix), A_NORMAL, "%s : %#18lx :", ui_proc_state(pix), pix); - if (!proc_is_live(&g_cores[g_core], pix)) { + if (!mvec_proc_is_live(&g_cores[g_core], pix)) { return; } - const Core *core = &g_cores[g_core]; + const struct Core *core = &g_cores[g_core]; - int scol = PANE_WIDTH + 2; - int gcol = scol - g_proc_gene_scroll; - u64 mb0a = arch_proc_mb0_addr(core, pix); - u64 mb0s = arch_proc_mb0_size(core, pix); - u64 mb1a = arch_proc_mb1_addr(core, pix); - u64 mb1s = arch_proc_mb1_size(core, pix); + int scol = {{ pane_width }} + 2; + int gcol = scol - g_proc_gene_scroll; + uint64_t mb0a = arch_proc_mb0_addr(core, pix); + uint64_t mb0s = arch_proc_mb0_size(core, pix); + uint64_t mb1a = arch_proc_mb1_addr(core, pix); + uint64_t mb1s = arch_proc_mb1_size(core, pix); - for (u64 gidx = 0; gidx < mb0s && gcol < COLS; ++gidx, ++gcol) { + for (uint64_t gidx = 0; gidx < mb0s && gcol < COLS; ++gidx, ++gcol) { if (gcol >= scol) { ui_print_process_gene(l, gcol, gidx, mb0a, pix, PAIR_SELECTED_MB1); } } - for (u64 gidx = 0; gidx < mb1s && gcol < COLS; ++gidx, ++gcol) { + for (uint64_t gidx = 0; gidx < mb1s && gcol < COLS; ++gidx, ++gcol) { if (gcol >= scol) { ui_print_process_gene(l, gcol, gidx, mb1a, pix, PAIR_SELECTED_MB2); } @@ -250,7 +495,7 @@ void ui_print_process_field_header_element(int l, int fidx, const char *name) { } int foff = fidx - g_proc_field_scroll; - int fcol = foff * PROC_FIELD_WIDTH + PANE_WIDTH - 1; + int fcol = foff * {{ proc_field_width }} + {{ pane_width }} - 1; ui_field(l, fcol, PAIR_NORMAL, A_NORMAL, " : %18s", name); } @@ -260,12 +505,12 @@ void ui_print_process_field_header(int l) { int fidx = 0; -#define PROC_FIELD(type, name) ui_print_process_field_header_element(l, fidx++, #name); - PROC_FIELDS -#undef PROC_FIELD + {% for _, val in arch_vars.proc_fields %} + ui_print_process_field_header_element(l, fidx++, "{{ val }}"); + {% endfor %} } -void ui_print_process_field_element(int l, int fidx, int fclr, u64 field) { +void ui_print_process_field_element(int l, int fidx, int fclr, uint64_t field) { assert(fidx >= 0); if (fidx < (int)g_proc_field_scroll) { @@ -273,22 +518,22 @@ void ui_print_process_field_element(int l, int fidx, int fclr, u64 field) { } int foff = fidx - g_proc_field_scroll; - int fcol = foff * PROC_FIELD_WIDTH + PANE_WIDTH - 1; + int fcol = foff * {{ proc_field_width }} + {{ pane_width }} - 1; ui_field(l, fcol, fclr, A_NORMAL, " : %#18lx", field); } - -void ui_print_process_fields(int l, u64 pix) { +void ui_print_process_fields(int l, uint64_t pix) { ui_line(true, l, ui_proc_pair(pix), A_NORMAL, "%s : %#18lx", ui_proc_state(pix), pix); - const Proc *proc = proc_get(&g_cores[g_core], pix); - int fidx = 0; - int fclr = ui_proc_pair(pix); + const struct Proc *proc = proc_get(&g_cores[g_core], pix); + + int fidx = 0; + int fclr = ui_proc_pair(pix); -#define PROC_FIELD(type, name) ui_print_process_field_element(l, fidx++, fclr, proc->name); - PROC_FIELDS -#undef PROC_FIELD + {% for _, val in arch_vars.proc_fields %} + ui_print_process_field_element(l, fidx++, fclr, proc->{{ val }}); + {% endfor %} } void ui_print_process(int l) { @@ -304,7 +549,7 @@ void ui_print_process(int l) { g_proc_gene_scroll ); - u64 pix = g_proc_scroll; + uint64_t pix = g_proc_scroll; if (g_proc_genes) { ui_print_process_genome_header(l++); @@ -329,8 +574,8 @@ void ui_world_resize() { g_vlin_rng = 0; g_vsiz_rng = 0; - if (COLS > PANE_WIDTH) { - g_vlin = COLS - PANE_WIDTH; + if (COLS > {{ pane_width }}) { + g_vlin = COLS - {{ pane_width }}; g_vsiz = LINES * g_vlin; g_vlin_rng = g_vlin * g_wrld_zoom; g_vsiz_rng = g_vsiz * g_wrld_zoom; @@ -339,22 +584,30 @@ void ui_world_resize() { } } -void ui_print_cell(u64 i, u64 r, u64 x, u64 y, u64 a) { - wchar_t inst_nstr[2] = { L'\0', L'\0' }; - cchar_t cchar = { 0 }; - u64 inst_avrg = g_gfx_inst[i] / g_wrld_zoom; +{% if arch_vars.mvec_loop %} +void ui_print_cell(uint64_t i, uint64_t r, uint64_t x, uint64_t y) { +{% else %} +void ui_print_cell(uint64_t i, uint64_t r, uint64_t x, uint64_t y, uint64_t a) { +{% endif %} + wchar_t inst_nstr[2] = { L'\0', L'\0' }; + cchar_t cchar = { 0 }; + uint64_t inst_avrg = g_gfx_inst[i] / g_wrld_zoom; if (g_wrld_zoom == 1) { - inst_nstr[0] = arch_symbol((u8)inst_avrg); + inst_nstr[0] = arch_symbol((uint8_t)inst_avrg); } else { - inst_nstr[0] = g_zoomed_symbols[(u8)inst_avrg]; + inst_nstr[0] = g_zoomed_symbols[(uint8_t)inst_avrg]; } int pair_cell; - if (a >= MVEC_SIZE) { + {% if arch_vars.mvec_loop %} + if (g_wcursor_mode && r == (uint64_t)g_wcursor_x && y == (uint64_t)g_wcursor_y) { + {% else %} + if (a >= {{ mvec_size }} || (g_wcursor_mode && r == (uint64_t)g_wcursor_x && y == (uint64_t)g_wcursor_y)) { + {% endif %} pair_cell = PAIR_NORMAL; - } else if (g_wcursor_mode && r == (u64)g_wcursor_x && y == (u64)g_wcursor_y) { + } else if (g_wcursor_mode && r == (uint64_t)g_wcursor_x && y == (uint64_t)g_wcursor_y) { pair_cell = PAIR_NORMAL; } else if (g_gfx_ipas[i] != 0) { pair_cell = PAIR_SELECTED_IP; @@ -379,40 +632,26 @@ void ui_print_cell(u64 i, u64 r, u64 x, u64 y, u64 a) { void ui_print_wcursor_bar() { ui_clear_line(LINES - 1); - const Core *core = &g_cores[g_core]; + const struct Core *core = &g_cores[g_core]; - char cmnem[MNEMONIC_BUFF_SIZE]; - char cownr[PROC_FIELD_WIDTH]; + char cownr[{{ proc_field_width }}]; - u64 cpos = g_vlin * g_wcursor_y + g_wcursor_x; - u64 caddr = cpos * g_wrld_zoom + g_wrld_pos; - u8 cbyte = mvec_get_byte(core, caddr); + uint64_t cpos = g_vlin * g_wcursor_y + g_wcursor_x; + uint64_t caddr = cpos * g_wrld_zoom + g_wrld_pos; + uint8_t cbyte = mvec_get_byte(core, caddr); if (mvec_is_alloc(core, caddr)) { g_wcursor_pointed = mvec_get_owner(core, caddr); - snprintf(cownr, PROC_FIELD_WIDTH, "%#lx", g_wcursor_pointed); + snprintf(cownr, {{ proc_field_width }}, "%#lx", g_wcursor_pointed); } else { - g_wcursor_pointed = (u64)(-1); - snprintf(cownr, PROC_FIELD_WIDTH, "-"); + g_wcursor_pointed = (uint64_t)(-1); + snprintf(cownr, {{ proc_field_width }}, "-"); } - arch_mnemonic(cbyte, cmnem); - mvprintw( LINES - 1, 1, - ( - "cursor" - " | x:%#x" - " | y:%#x" - " | addr:%#lx" - " | isum:%#lx" - " | iavr:%#lx" - " | mall:%#lx" - " | mbst:%#lx" - " | mnem:<%s>" - " | ownr:%s" - ), + "cursor | x:%#x | y:%#x | addr:%#lx | isum:%#lx | iavr:%#lx | mall:%#lx | mbst:%#lx | mnem:<%s> | ownr:%s", g_wcursor_x, g_wcursor_y, caddr, @@ -420,7 +659,7 @@ void ui_print_wcursor_bar() { g_gfx_inst[cpos] / g_wrld_zoom, g_gfx_mall[cpos], g_gfx_mbst[cpos], - cmnem, + arch_mnemonic(cbyte), cownr ); } @@ -440,11 +679,11 @@ void ui_print_world(int l) { ui_line(false, l++, PAIR_HEADER, A_BOLD, "SELECTED"); - const Proc *psel = proc_get(&g_cores[g_core], g_proc_selected); + const struct Proc *psel = proc_get(&g_cores[g_core], g_proc_selected); -#define PROC_FIELD(type, name) ui_ulx_field(l++, #name, psel->name); - PROC_FIELDS -#undef PROC_FIELD + {% for _, val in arch_vars.proc_fields %} + ui_ulx_field(l++, "{{ val }}", psel->{{ val }}); + {% endfor %} if (!g_vlin) { return; @@ -460,13 +699,18 @@ void ui_print_world(int l) { g_wcursor_y = (g_wcursor_y < ymax) ? g_wcursor_y : ymax; } - for (u64 i = 0; i < g_vsiz; ++i) { - u64 r = i % g_vlin; - u64 x = r + PANE_WIDTH; - u64 y = i / g_vlin; - u64 a = g_wrld_pos + (i * g_wrld_zoom); + for (uint64_t i = 0; i < g_vsiz; ++i) { + uint64_t r = i % g_vlin; + uint64_t x = r + {{ pane_width }}; + uint64_t y = i / g_vlin; + + {% if arch_vars.mvec_loop %} + ui_print_cell(i, r, x, y); + {% else %} + uint64_t a = g_wrld_pos + (i * g_wrld_zoom); ui_print_cell(i, r, x, y, a); + {% endif %} } if (g_wcursor_mode) { @@ -474,19 +718,19 @@ void ui_print_world(int l) { } } -void ui_print_ipc_field(int l, u64 i, int color) { - u8 iinst = g_cores[g_core].iviv[i]; - u64 iaddr = g_cores[g_core].ivav[i]; +void ui_print_ipc_field(int l, uint64_t i, int color) { + uint8_t iinst = g_cores[g_core].iviv[i]; + uint64_t iaddr = g_cores[g_core].ivav[i]; - ui_field(l, PANE_WIDTH, color, A_NORMAL, "%#18x : %#18x : %#18x", i, iinst, iaddr); + ui_field(l, {{ pane_width }}, color, A_NORMAL, "%#18x : %#18x : %#18x", i, iinst, iaddr); } void ui_print_ipc_data() { - ui_field(0, PANE_WIDTH, PAIR_NORMAL, A_NORMAL, "%18s : %18s : %18s", "ipci", "inst", "addr"); + ui_field(0, {{ pane_width }}, PAIR_NORMAL, A_NORMAL, "%18s : %18s : %18s", "ipci", "inst", "addr"); int l = 1 - g_ivpt_scroll; - for (u64 i = 0; i < SYNC_INTERVAL; ++i) { + for (uint64_t i = 0; i < {{ sync_interval }}; ++i) { if (i == g_cores[g_core].ivpt) { if (l >= 1) { ui_print_ipc_field(l++, i, PAIR_SELECTED_PROC); @@ -495,9 +739,9 @@ void ui_print_ipc_data() { continue; } - u8 iinst = g_cores[g_core].iviv[i]; + uint8_t iinst = g_cores[g_core].iviv[i]; - if ((iinst & IPCM_FLAG) != 0) { + if ((iinst & {{ ipc_flag }}) != 0) { if (l >= 1) { ui_print_ipc_field(l++, i, PAIR_LIVE_PROC); } @@ -508,7 +752,7 @@ void ui_print_ipc_data() { for (; l < LINES; ++l) { if (l >= 1) { - move(l, PANE_WIDTH); + move(l, {{ pane_width }}); clrtoeol(); } } @@ -517,7 +761,7 @@ void ui_print_ipc_data() { void ui_print_ipc(int l) { l++; - const Core *core = &g_cores[g_core]; + const struct Core *core = &g_cores[g_core]; ui_line(true, l++, PAIR_HEADER, A_BOLD, "IPC [%#lx]", g_ivpt_scroll); ui_ulx_field(l++, "ivpt", core->ivpt); @@ -530,14 +774,14 @@ void ui_print_ipc(int l) { void ui_print() { int l = 1; - ui_line(false, l++, PAIR_HEADER, A_BOLD, "SALIS [%d:%d]", g_core, CORE_COUNT); - ui_str_field(l++, "name", SIM_NAME); - ui_ulx_field(l++, "seed", SEED); - ui_str_field(l++, "fbit", MUTA_FLIP_BIT ? "yes" : "no"); - ui_ulx_field(l++, "asav", AUTO_SAVE_INTERVAL); - ui_str_field(l++, "arch", ARCHITECTURE); - ui_ulx_field(l++, "size", MVEC_SIZE); - ui_ulx_field(l++, "syni", SYNC_INTERVAL); + ui_line(false, l++, PAIR_HEADER, A_BOLD, "SALIS [%d:%d]", g_core, {{ args.cores }}); + ui_str_field(l++, "name", "{{ args.name }}"); + ui_ulx_field(l++, "seed", {{ args.seed }}); + ui_str_field(l++, "fbit", "{{ "yes" if args.muta_flip else "no" }}"); + ui_ulx_field(l++, "asav", {{ auto_save_interval }}); + ui_str_field(l++, "arch", "{{ args.arch }}"); + ui_ulx_field(l++, "size", {{ mvec_size }}); + ui_ulx_field(l++, "syni", {{ sync_interval }}); ui_ulx_field(l++, "step", g_steps); ui_ulx_field(l++, "sync", g_syncs); ui_ulx_field(l++, "step", g_step_block); @@ -565,10 +809,10 @@ void ev_vscroll(int ev) { case PAGE_PROCESS: switch (ev) { case 'W': - g_proc_scroll += (LINES > PROC_PAGE_LINES) ? LINES - PROC_PAGE_LINES : 0; + g_proc_scroll += (LINES > {{ proc_page_lines }}) ? LINES - {{ proc_page_lines }} : 0; break; case 'S': - g_proc_scroll -= (LINES > PROC_PAGE_LINES) ? LINES - PROC_PAGE_LINES : 0; + g_proc_scroll -= (LINES > {{ proc_page_lines }}) ? LINES - {{ proc_page_lines }} : 0; break; case 'w': g_proc_scroll += 1; @@ -596,15 +840,15 @@ void ev_vscroll(int ev) { g_wrld_pos += g_vlin_rng; break; case 's': -#ifdef MVEC_LOOP + {% if arch_vars.mvec_loop %} g_wrld_pos -= g_vlin_rng; -#else + {% else %} if (g_wrld_pos < g_vlin_rng) { g_wrld_pos = 0; } else { g_wrld_pos -= g_vlin_rng; } -#endif + {% endif %} break; case 'q': g_wrld_pos = 0; @@ -621,7 +865,7 @@ void ev_vscroll(int ev) { g_ivpt_scroll += LINES; break; case 'S': - g_ivpt_scroll -= g_ivpt_scroll < (u64)LINES ? g_ivpt_scroll : (u64)LINES; + g_ivpt_scroll -= g_ivpt_scroll < (uint64_t)LINES ? g_ivpt_scroll : (uint64_t)LINES; break; case 'w': g_ivpt_scroll += 1; @@ -643,7 +887,7 @@ void ev_vscroll(int ev) { void ev_hscroll(int ev) { switch (g_page) { case PAGE_PROCESS: { - u64 *hs_var = g_proc_genes ? &g_proc_gene_scroll : &g_proc_field_scroll; + uint64_t *hs_var = g_proc_genes ? &g_proc_gene_scroll : &g_proc_field_scroll; switch (ev) { case 'A': @@ -665,15 +909,15 @@ void ev_hscroll(int ev) { case PAGE_WORLD: switch (ev) { case 'a': -#ifdef MVEC_LOOP + {% if arch_vars.mvec_loop %} g_wrld_pos -= g_wrld_zoom; -#else + {% else %} if (g_wrld_pos < g_wrld_zoom) { g_wrld_pos = 0; } else { g_wrld_pos -= g_wrld_zoom; } -#endif + {% endif %} break; case 'd': g_wrld_pos += g_wrld_zoom; @@ -693,7 +937,7 @@ void ev_zoom(int ev) { case PAGE_WORLD: switch (ev) { case 'x': - g_wrld_zoom *= (g_vlin != 0 && g_vsiz_rng < MVEC_SIZE) ? 2 : 1; + g_wrld_zoom *= (g_vlin != 0 && g_vsiz_rng < {{ mvec_size }}) ? 2 : 1; ui_world_resize(); break; case 'z': @@ -722,7 +966,7 @@ void ev_move_wcursor(int ev) { g_wcursor_x -= (g_wcursor_x != 0) ? 1 : 0; break; case KEY_RIGHT: - g_wcursor_x += ((u64)g_wcursor_x < g_vlin - 1) ? 1 : 0; + g_wcursor_x += ((uint64_t)g_wcursor_x < g_vlin - 1) ? 1 : 0; break; default: break; @@ -777,7 +1021,7 @@ void ev_handle() { ev_move_wcursor(ev); return; case '\n': - if (g_wcursor_pointed != (u64)(-1)) { + if (g_wcursor_pointed != (uint64_t)(-1)) { g_proc_selected = g_wcursor_pointed; } @@ -788,16 +1032,16 @@ void ev_handle() { } switch (ev) { - case CTRL('c'): + case {{ ctrl('c') }}: g_exit = true; break; case KEY_SLEFT: clear(); - g_core = (g_core - 1) % CORE_COUNT; + g_core = (g_core - 1) % {{ args.cores }}; break; case KEY_SRIGHT: clear(); - g_core = (g_core + 1) % CORE_COUNT; + g_core = (g_core + 1) % {{ args.cores }}; break; case KEY_LEFT: clear(); @@ -813,7 +1057,7 @@ void ev_handle() { ui_world_resize(); if (g_vlin) { - while (g_vsiz_rng >= MVEC_SIZE * 2 && g_wrld_zoom != 1) { + while (g_vsiz_rng >= {{ mvec_size }} * 2 && g_wrld_zoom != 1) { g_wrld_zoom /= 2; ui_world_resize(); } @@ -880,7 +1124,7 @@ void ev_handle() { case '9': case '0': if (!g_running) { - u64 cycles = 1 << (((ev - '0') ? (ev - '0') : 10) - 1); + uint64_t cycles = 1 << (((ev - '0') ? (ev - '0') : 10) - 1); salis_step(cycles); } @@ -914,11 +1158,11 @@ void init() { init_pair(PAIR_SELECTED_IP, COLOR_BLACK, COLOR_RED ); init_pair(PAIR_SELECTED_SP, COLOR_BLACK, COLOR_MAGENTA); -#if ACTION == ACT_NEW + {% if args.command == "new" %} salis_init(); -#elif ACTION == ACT_LOAD + {% elif args.command == "load" %} salis_load(); -#endif + {% endif %} g_wrld_zoom = 1; g_step_block = 1; @@ -934,11 +1178,11 @@ void exec() { salis_step(g_step_block - (g_steps % g_step_block)); clock_t end = clock(); - if ((end - beg) < (CLOCKS_PER_SEC / 30)) { + if ((end - beg) < (CLOCKS_PER_SEC / {{ min_fps }})) { g_step_block <<= 1; } - if ((end - beg) >= (CLOCKS_PER_SEC / 60) && g_step_block != 1) { + if ((end - beg) >= (CLOCKS_PER_SEC / {{ max_fps }}) && g_step_block != 1) { g_step_block >>= 1; } } @@ -951,7 +1195,7 @@ void exec() { void quit() { gfx_free(); ui_line_buff_free(); - salis_save(SIM_PATH); + salis_save("{{ sim_path }}"); salis_free(); endwin(); } diff --git a/ui/curses/ui_vars.py b/ui/curses/ui_vars.py new file mode 100644 index 0000000..4dfdc10 --- /dev/null +++ b/ui/curses/ui_vars.py @@ -0,0 +1,2 @@ +flags = ["-lncurses", "-DNCURSES_WIDECHAR=1"] +includes = ["curses.h", "locale.h", "time.h"] diff --git a/src/ui/daemon.c b/ui/daemon/ui.j2.c index f74713d..7d92f9d 100644 --- a/src/ui/daemon.c +++ b/ui/daemon/ui.j2.c @@ -1,23 +1,18 @@ +// Author: Paul Oliver <contact@pauloliver.dev> // Project: Salis -// Author: Paul Oliver -// Email: contact@pauloliver.dev -/* - * Implements a minimal UI for the Salis simulator with minimal output and - * interruptible via OS signals. Ideal for running Salis in the background. - */ - -#include <signal.h> -#include <unistd.h> +// Lightweight UI for the Salis simulator with minimal output. +// Can be interrupted through OS signals. +// Ideal for running Salis in the background. volatile bool g_running; -u64 g_step_block; +uint64_t g_step_block; void sig_handler(int signo) { switch (signo) { case SIGINT: case SIGTERM: - printf("signal received, stopping simulator...\n"); + printf("Signal received, will stop simulator soon...\n"); g_running = false; break; } @@ -36,15 +31,15 @@ void step_block() { g_step_block >>= 1; } - printf("simulator running on step '%#lx'\n", g_steps); + printf("Simulator running on step '%#lx'\n", g_steps); } int main() { -#if ACTION == ACT_NEW + {% if args.command == "new" %} salis_init(); -#elif ACTION == ACT_LOAD + {% elif args.command == "load" %} salis_load(); -#endif + {% endif %} g_running = true; g_step_block = 1; @@ -56,8 +51,10 @@ int main() { step_block(); } - salis_save(SIM_PATH); + printf("Saving simulation...\n"); + salis_save("{{ sim_path }}"); salis_free(); + printf("Exiting salis...\n"); return 0; } diff --git a/ui/daemon/ui_vars.py b/ui/daemon/ui_vars.py new file mode 100644 index 0000000..9d0fc33 --- /dev/null +++ b/ui/daemon/ui_vars.py @@ -0,0 +1,2 @@ +flags = [] +includes = ["signal.h", "stdio.h", "unistd.h"] |
