diff options
| -rw-r--r-- | README.md | 60 | ||||
| -rwxr-xr-x | salis | 79 | ||||
| -rw-r--r-- | sim.png | bin | 0 -> 311643 bytes | |||
| -rw-r--r-- | src/arch/dummy.c | 7 | ||||
| -rw-r--r-- | src/graphics.c | 21 | ||||
| -rw-r--r-- | src/salis.c | 147 | ||||
| -rw-r--r-- | src/ui/curses.c | 27 | ||||
| -rw-r--r-- | todo.adoc | 5 |
8 files changed, 289 insertions, 57 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba9b2d7 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# SALIS: A-life Simulator + + +*SALIS simulation running on the V1 architecture with the ncurses user interface* + +## Overview +*SALIS* is a platform for conducting artificial life experiments. It enables +the development of Tierra-like virtual machines, with certain limitations. For +newcomers, I recommend exploring Tierra first. The following resources provide +valuable context and insight into both the motivations and implementation of +both Tierra and this project: + +- [Video about Tierra](https://www.youtube.com/watch?v=Wl5rRGVD0QI) +- [Read about Tierra](https://tomray.me/pubs/doc/index.html#What) + +## SALIS V1 Reimplementation +A fully functional clone of the V1 architecture for the SALIS virtual machine +has been implemented using the tools available in this repository. For more +information on the V1 architecture, including its similarities and differences +with the original Tierra simulator, check out the following resources: + +- [SALIS V1 repository](https://git.pauloliver.dev/salis-v1/about/) +- [SALIS V1 introductory playlist](https://www.youtube.com/watch?v=jCFmOCvy6po&list=PLrEmYrpTcDJY2NdGL6B7wIRbKGp_6NkxY) + +## Usage +*SALIS* simulations are initialized using the provided `salis` shell 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 +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. + +Similarly, different user interfaces are implemented as C files within the +`src/ui/` directory. For example, the `curses.c` 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 +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 +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`: +```console +user@host$ ./salis new -A55a -asalis-v1 -c8 -C4 -m22 -nworld-1 -s123456789 -o +``` + +Upon exit, the simulation data 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 +``` @@ -11,7 +11,7 @@ set -euo pipefail headline="Salis: Simple A-Life Simulator." -help_msg="show help and exit" +help_msg="Shows help and exits" usage() { cat << EOF @@ -22,9 +22,9 @@ Options: -h, --help ${help_msg} Commands: - bench run benchmark test - load load saved simulation - new create a new simulation + bench Runs benchmark + load Loads saved simulation + new Creates a new simulation Use '-h' to list arguments for each command. Example: ${0} bench -h @@ -35,7 +35,7 @@ case ${1:-} in bench|load|new) ;; -h|--help) - usage | less -CQS~ + usage exit 0 ;; "") @@ -60,38 +60,39 @@ arches=`falter arch` uis=`falter ui` anc_def_desc() { - echo "default ancestor file name without extension, " + 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" + echo "Core specific ancestor file names separated by commas, " + echo "using same convention as with default ancestor (<ANC0>,<ANC1>,...). " + echo "When provided will override default ancestor on specified cores." } options=( "A|anc-def|ANC|`anc_def_desc`|||bench:new" "a|arch|ARCH|VM architecture|${arches}|dummy|bench:new" - "b|steps|N|number of steps to run in benchmark||0x1000000|bench" - "C|clones|N|number of ancestor clones on each core||1|bench:new" - "c|cores|N|number of simulator cores||2|bench:new" - "F|muta-flip||cosmic rays flip bits instead of randomizing whole bytes||false|bench:new" - "f|force||overwrite existing simulation of given name||false|new" - "H|half||compile ancestor at the middle of the memory buffer||false|bench:new" + "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||build Salis binary with optimizations||false|bench:load:new" - "p|pre-cmd|CMD|shell command to wrap executable (e.g. gdb)|||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)||0x100|bench:load:new" - "u|ui|UI|user interface|${uis}|curses|load:new" - "y|sync-pow|POW|core sync interval exponent (interval == 2^POW)||20|bench:new" - "z|auto-save-pow|POW|auto save interval exponent (interval == 2^POW)||36|new" + "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() { @@ -111,7 +112,7 @@ fhelp() { lopt=`field "${1}" 2` meta=`field "${1}" 3` - printf %-32s " -${sopt}, --${lopt}`[[ -n ${meta} ]] && echo " ${meta}"`" + printf "%s\r" " -${sopt}, --${lopt}`[[ -n ${meta} ]] && echo " ${meta}"`" help=`field "${1}" 4` choi=`field "${1}" 5` @@ -119,7 +120,7 @@ fhelp() { copt=`[[ -n ${choi} ]] && echo " (choices: ${choi/:/, })"` dopt=`[[ -n ${defv} ]] && echo " (default: ${defv})"` - echo ${help}${copt}${dopt} + echo -e "\t\t\t\t${help}${copt}${dopt}" | fmt -w120 } fshort() { @@ -168,7 +169,7 @@ fiter() { usage() { cat << EOF ${headline} -Usage: ${0} ${cmd} `fiter flist` +Usage: ${0} ${cmd} `fiter flist | fmt -t -w120` Options: `fiter fhelp` @@ -196,7 +197,8 @@ parse_next() { nopt=opt_${lopt//-/_} if [[ -z ${meta} ]] ; then - eval ${nopt}=true + defv=`field "${vopt}" 6` + eval ${nopt}=`[[ ${defv} == true ]] && echo false || echo true` shift_next=1 else eval ${nopt}=${2} @@ -208,7 +210,7 @@ parse_next() { while true ; do case ${1} in -h|--help) - usage | less -CQS~ + usage exit 0 ;; --) @@ -275,7 +277,7 @@ act_new=3 act_var="act_${cmd}" -gcc_flags="-Wall -Wextra -Werror -std=c11 -pedantic" +gcc_flags="-Wall -Wextra -Werror -std=gnu11 -pedantic" fquote() { echo "\\\"${1}\\\"" @@ -285,7 +287,7 @@ fpow() { printf '%#xul' $((1 << ${1})) } -bcmd="gcc src/salis.c -o ${salis_exe} ${gcc_flags} -Isrc -lncursesw -pthread" +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}`" @@ -337,7 +339,18 @@ load|new) bcmd="${bcmd} -DMUTA_FLIP_BIT=`[[ ${opt_muta_flip} == true ]] && echo 1 || echo 0`" bcmd="${bcmd} -DSIM_NAME=`fquote ${opt_name}`" bcmd="${bcmd} -DSIM_PATH=`fquote ${sim_path}`" - bcmd="${bcmd} -DUI=`fquote ui/${opt_ui}.c`" + + 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 diff --git a/src/arch/dummy.c b/src/arch/dummy.c index b440be3..7a47900 100644 --- a/src/arch/dummy.c +++ b/src/arch/dummy.c @@ -11,7 +11,12 @@ bool proc_is_live(const Core *core, u64 pix); #define PROC_FIELDS \ - PROC_FIELD(u64, dmmy) + 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; diff --git a/src/graphics.c b/src/graphics.c index 8114f30..780b879 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -89,10 +89,9 @@ void gfx_render_inst(const Core *core, u64 pos, u64 zoom) { for (u64 j = 0; j < zoom; ++j) { u64 addr = pos + (i * zoom) + j; - u8 byte = mvec_get_byte(core, addr); - g_gfx_inst[i] += byte; - g_gfx_mall[i] += (byte & MALL_FLAG) ? 1 : 0; + g_gfx_inst[i] += mvec_get_byte(core, addr); + g_gfx_mall[i] += mvec_is_alloc(core, addr) ? 1 : 0; } } } @@ -102,6 +101,7 @@ void gfx_clear_array(u64 *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); @@ -134,6 +134,21 @@ void gfx_accumulate_pixel(u64 pos, u64 zoom, u64 pixa, u64 *arry) { } #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); diff --git a/src/salis.c b/src/salis.c index 814dd0d..348385f 100644 --- a/src/salis.c +++ b/src/salis.c @@ -16,6 +16,10 @@ #include <string.h> #include <threads.h> +#ifdef COMPRESS +#include <zlib.h> +#endif + #define ACT_BENCH (1) #define ACT_LOAD (2) #define ACT_NEW (3) @@ -73,51 +77,93 @@ const Proc g_dead_proc; 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[mvec_loop(addr)] ^= (1 << bit) & INST_MASK; + core->mvec[addr] ^= (1 << bit) & INST_MASK; } #endif @@ -378,8 +424,6 @@ void core_load(FILE *f, Core *core) { assert(f); assert(core); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-result" fread(&core->mall, sizeof(u64), 1, f); fread( core->muta, sizeof(u64), 4, f); fread(&core->pnum, sizeof(u64), 1, f); @@ -390,7 +434,6 @@ void core_load(FILE *f, Core *core) { fread(&core->psli, sizeof(u64), 1, f); fread(&core->ncyc, sizeof(u64), 1, f); fread(&core->ivpt, sizeof(u64), 1, f); -#pragma GCC diagnostic pop core->iviv = calloc(SYNC_INTERVAL, sizeof(u8)); core->ivav = calloc(SYNC_INTERVAL, sizeof(u64)); @@ -400,13 +443,10 @@ void core_load(FILE *f, Core *core) { assert(core->ivav); assert(core->pvec); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-result" fread(core->iviv, sizeof(u8), SYNC_INTERVAL, f); fread(core->ivav, sizeof(u64), SYNC_INTERVAL, f); fread(core->pvec, sizeof(Proc), core->pcap, f); fread(core->mvec, sizeof(u8), MVEC_SIZE, f); -#pragma GCC diagnostic pop } #endif @@ -476,7 +516,13 @@ void core_step(Core *core) { #if ACTION == ACT_LOAD || ACTION == ACT_NEW void salis_save(const char *path) { - FILE *f = fopen(path, "wb"); +#ifdef COMPRESS + size_t size = 0; + char *in = NULL; + FILE *f = open_memstream(&in, &size); +#else + FILE *f = fopen(path, "wb"); +#endif assert(f); @@ -487,6 +533,39 @@ void salis_save(const char *path) { 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() { @@ -536,7 +615,50 @@ void salis_init() { #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); @@ -544,13 +666,14 @@ void salis_load() { core_load(f, &g_cores[i]); } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-result" fread(&g_steps, sizeof(u64), 1, f); fread(&g_syncs, sizeof(u64), 1, f); -#pragma GCC diagnostic pop - fclose(f); + +#ifdef COMPRESS + free(in); + free(out); +#endif } #endif diff --git a/src/ui/curses.c b/src/ui/curses.c index 8f91d9f..d1f821d 100644 --- a/src/ui/curses.c +++ b/src/ui/curses.c @@ -6,6 +6,8 @@ * Implements a TUI for the Salis simulator using the ncurses library. */ +// GCC_EXTRA_FLAGS -lncursesw + #include <curses.h> #include <locale.h> #include <time.h> @@ -337,7 +339,7 @@ void ui_world_resize() { } } -void ui_print_cell(u64 i, u64 r, u64 x, u64 y) { +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; @@ -350,7 +352,9 @@ void ui_print_cell(u64 i, u64 r, u64 x, u64 y) { int pair_cell; - if (g_wcursor_mode && r == (u64)g_wcursor_x && y == (u64)g_wcursor_y) { + if (a >= MVEC_SIZE) { + pair_cell = PAIR_NORMAL; + } else if (g_wcursor_mode && r == (u64)g_wcursor_x && y == (u64)g_wcursor_y) { pair_cell = PAIR_NORMAL; } else if (g_gfx_ipas[i] != 0) { pair_cell = PAIR_SELECTED_IP; @@ -460,8 +464,9 @@ void ui_print_world(int l) { u64 r = i % g_vlin; u64 x = r + PANE_WIDTH; u64 y = i / g_vlin; + u64 a = g_wrld_pos + (i * g_wrld_zoom); - ui_print_cell(i, r, x, y); + ui_print_cell(i, r, x, y, a); } if (g_wcursor_mode) { @@ -591,7 +596,15 @@ void ev_vscroll(int ev) { g_wrld_pos += g_vlin_rng; break; case 's': +#ifdef MVEC_LOOP g_wrld_pos -= g_vlin_rng; +#else + if (g_wrld_pos < g_vlin_rng) { + g_wrld_pos = 0; + } else { + g_wrld_pos -= g_vlin_rng; + } +#endif break; case 'q': g_wrld_pos = 0; @@ -652,7 +665,15 @@ void ev_hscroll(int ev) { case PAGE_WORLD: switch (ev) { case 'a': +#ifdef MVEC_LOOP g_wrld_pos -= g_wrld_zoom; +#else + if (g_wrld_pos < g_wrld_zoom) { + g_wrld_pos = 0; + } else { + g_wrld_pos -= g_wrld_zoom; + } +#endif break; case 'd': g_wrld_pos += g_wrld_zoom; diff --git a/todo.adoc b/todo.adoc deleted file mode 100644 index 0011c6a..0000000 --- a/todo.adoc +++ /dev/null @@ -1,5 +0,0 @@ -= TODO -Paul Oliver <contact@pauloliver.dev> - -== Architectures -. salis-v3 |
