aboutsummaryrefslogtreecommitdiff
path: root/src/memory.c
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2024-02-29 02:29:13 +0100
committerPaul Oliver <contact@pauloliver.dev>2024-02-29 02:29:13 +0100
commitca118555214a176728b9aab87849391344306d6d (patch)
tree833cffdd4066a7114b1d79d6eeaa2e0152408fc8 /src/memory.c
Initial commit.
Diffstat (limited to 'src/memory.c')
-rw-r--r--src/memory.c325
1 files changed, 325 insertions, 0 deletions
diff --git a/src/memory.c b/src/memory.c
new file mode 100644
index 0000000..c586eb0
--- /dev/null
+++ b/src/memory.c
@@ -0,0 +1,325 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "types.h"
+#include "getter.h"
+#include "instset.h"
+#include "memory.h"
+
+#define MAX_ZOOM 0x10000
+
+static boolean g_is_init;
+static uint32 g_order;
+static uint32 g_size;
+static uint32 g_ip_count;
+static uint32 g_block_start_count;
+static uint32 g_allocated_count;
+static uint32 g_capacity;
+static uint32 g_inst_counter[INST_COUNT];
+static uint8_p g_memory;
+
+void _sal_mem_init(uint32 order)
+{
+ /* Set memory module to its initial state. We calculate memory size based
+ on its order (size = 1 << order) and allocate an array of such size. We
+ also initialize the array completely to zero.
+ */
+ assert(!g_is_init);
+ assert(order < 32);
+ g_is_init = TRUE;
+ g_order = order;
+ g_size = 1 << g_order;
+ g_capacity = g_size / 2;
+ g_inst_counter[0] = g_size;
+ g_memory = calloc(g_size, 1);
+ assert(g_memory);
+}
+
+void _sal_mem_quit(void)
+{
+ /* Reset memory module entirely back to zero. That way, we can load several
+ simulations without restarting the application entirely.
+ */
+ assert(g_is_init);
+ free(g_memory);
+ g_is_init = FALSE;
+ g_order = 0;
+ g_size = 0;
+ g_ip_count = 0;
+ g_block_start_count = 0;
+ g_allocated_count = 0;
+ g_capacity = 0;
+ memset(g_inst_counter, 0, sizeof(uint32) * INST_COUNT);
+ g_memory = NULL;
+}
+
+void _sal_mem_load_from(FILE *file)
+{
+ /* Load memory state from a binary file.
+ */
+ assert(!g_is_init);
+ assert(file);
+ fread(&g_is_init, sizeof(boolean), 1, file);
+ fread(&g_order, sizeof(uint32), 1, file);
+ fread(&g_size, sizeof(uint32), 1, file);
+ fread(&g_ip_count, sizeof(uint32), 1, file);
+ fread(&g_block_start_count, sizeof(uint32), 1, file);
+ fread(&g_allocated_count, sizeof(uint32), 1, file);
+ fread(&g_capacity, sizeof(uint32), 1, file);
+ fread(g_inst_counter, sizeof(uint32), INST_COUNT, file);
+ g_memory = calloc(g_size, sizeof(uint8));
+ assert(g_memory);
+ fread(g_memory, sizeof(uint8), g_size, file);
+}
+
+void _sal_mem_save_into(FILE *file)
+{
+ /* Save memory state to a binary file.
+ */
+ assert(g_is_init);
+ assert(file);
+ fwrite(&g_is_init, sizeof(boolean), 1, file);
+ fwrite(&g_order, sizeof(uint32), 1, file);
+ fwrite(&g_size, sizeof(uint32), 1, file);
+ fwrite(&g_ip_count, sizeof(uint32), 1, file);
+ fwrite(&g_block_start_count, sizeof(uint32), 1, file);
+ fwrite(&g_allocated_count, sizeof(uint32), 1, file);
+ fwrite(&g_capacity, sizeof(uint32), 1, file);
+ fwrite(g_inst_counter, sizeof(uint32), INST_COUNT, file);
+ fwrite(g_memory, sizeof(uint8), g_size, file);
+}
+
+/* Getter methods for the memory module.
+*/
+UINT32_GETTER(mem, order)
+UINT32_GETTER(mem, size)
+UINT32_GETTER(mem, ip_count)
+UINT32_GETTER(mem, block_start_count)
+UINT32_GETTER(mem, allocated_count)
+UINT32_GETTER(mem, capacity)
+
+uint32 sal_mem_get_inst_count(uint8 inst)
+{
+ /* Return number of times a certain instruction appears in memory. The
+ instruction counter gets updated dynamically during each cycle.
+ */
+ assert(g_is_init);
+ assert(sal_is_inst(inst));
+ return g_inst_counter[inst];
+}
+
+boolean sal_mem_is_over_capacity(void)
+{
+ /* Check if memory is filled above 50%. If so, old organisms will be popped
+ out of the reaper queue!
+ */
+ assert(g_is_init);
+ return g_allocated_count > g_capacity;
+}
+
+boolean sal_mem_is_address_valid(uint32 address)
+{
+ /* Check if given address is valid.
+ */
+ assert(g_is_init);
+ return address < g_size;
+}
+
+/* We declare a standard macro to test whether a specific FLAG is set on a given
+byte. Remember, a Salis byte contains a 5 bit instruction (of 32 possible) plus
+3 flags: IP, BLOCK_START and ALLOCATED. These flags help organisms identify
+where there is free memory space to reproduce on, and tell the python printer
+module how to color each byte.
+*/
+#define FLAG_TEST(name, flag) \
+boolean sal_mem_is_##name(uint32 address) \
+{ \
+ assert(g_is_init); \
+ assert(sal_mem_is_address_valid(address)); \
+ return !!(g_memory[address] & flag); \
+}
+
+FLAG_TEST(ip, IP_FLAG)
+FLAG_TEST(block_start, BLOCK_START_FLAG)
+FLAG_TEST(allocated, ALLOCATED_FLAG)
+
+/* We define a standard macro for 'setting' one of the 3 FLAGS into a given
+memory address.
+*/
+#define FLAG_SETTER(name, flag) \
+void _sal_mem_set_##name(uint32 address) \
+{ \
+ assert(g_is_init); \
+ assert(sal_mem_is_address_valid(address)); \
+\
+ if (!sal_mem_is_##name(address)) { \
+ g_memory[address] ^= flag; \
+ g_##name##_count++; \
+ } \
+}
+
+FLAG_SETTER(ip, IP_FLAG)
+FLAG_SETTER(block_start, BLOCK_START_FLAG)
+FLAG_SETTER(allocated, ALLOCATED_FLAG)
+
+/* We define a standard macro for 'unsetting' one of the 3 FLAGS into a given
+memory address.
+*/
+#define FLAG_UNSETTER(name, flag) \
+void _sal_mem_unset_##name(uint32 address) \
+{ \
+ assert(g_is_init); \
+ assert(sal_mem_is_address_valid(address)); \
+\
+ if (sal_mem_is_##name(address)) { \
+ g_memory[address] ^= flag; \
+ g_##name##_count--; \
+ } \
+}
+
+FLAG_UNSETTER(ip, IP_FLAG)
+FLAG_UNSETTER(block_start, BLOCK_START_FLAG)
+FLAG_UNSETTER(allocated, ALLOCATED_FLAG)
+
+uint8 sal_mem_get_flags(uint32 address)
+{
+ /* Get FLAG bits currently set on a specified address (byte). These may be
+ queried by using a bitwise 'and' operator against the returned byte.
+ */
+ assert(g_is_init);
+ assert(sal_mem_is_address_valid(address));
+ return g_memory[address] & ~INSTRUCTION_MASK;
+}
+
+uint8 sal_mem_get_inst(uint32 address)
+{
+ /* Get instruction currently set on a specified address (byte), with the
+ FLAG bits turned off.
+ */
+ assert(g_is_init);
+ assert(sal_mem_is_address_valid(address));
+ return g_memory[address] & INSTRUCTION_MASK;
+}
+
+void sal_mem_set_inst(uint32 address, uint8 inst)
+{
+ /* Set instruction at given address. This is useful when performing manual
+ memory manipulations (like compiling organism genomes).
+ */
+ assert(g_is_init);
+ assert(sal_mem_is_address_valid(address));
+ assert(sal_is_inst(inst));
+ g_inst_counter[sal_mem_get_inst(address)]--;
+ g_memory[address] &= ~INSTRUCTION_MASK;
+ g_memory[address] |= inst;
+ g_inst_counter[inst]++;
+}
+
+uint8 sal_mem_get_byte(uint32 address)
+{
+ /* Get unadulterated byte at given address. This could be used, for
+ example, to render nice images of the memory state.
+ */
+ assert(g_is_init);
+ assert(sal_mem_is_address_valid(address));
+ return g_memory[address];
+}
+
+void sal_mem_render_image(
+ uint32 origin, uint32 cell_size, uint32 buff_size, uint8_p buffer
+) {
+ /* Render a 1D image of a given section of memory, at a given resolution
+ (zoom) and store it in a pre-allocated 'buffer'.
+
+ On the Salis python handler we draw memory as a 1D 'image' on the WORLD
+ page. If we were to render this image directly on python, it would be
+ excruciatingly slow, as we have to iterate over large areas of memory!
+ Therefore, this memory module comes with a built-in, super fast renderer.
+ */
+ uint32 i;
+ assert(g_is_init);
+ assert(sal_mem_is_address_valid(origin));
+ assert(cell_size);
+ assert(cell_size <= MAX_ZOOM);
+ assert(buff_size);
+ assert(buffer);
+
+ /* We make use of openmp for multi-threaded looping. This allows even
+ faster render times, wherever openmp is supported.
+ */
+ #pragma omp parallel for
+ for (i = 0; i < buff_size; i++) {
+ uint32 j;
+ uint32 flag_sum = 0;
+ uint32 inst_sum = 0;
+ uint32 cell_addr = origin + (i * cell_size);
+
+ for (j = 0; j < cell_size; j++) {
+ uint32 address = j + cell_addr;
+
+ if (sal_mem_is_address_valid(address)) {
+ flag_sum |= sal_mem_get_flags(address);
+ inst_sum += sal_mem_get_inst(address);
+ }
+ }
+
+ buffer[i] = (uint8)(inst_sum / cell_size);
+ buffer[i] |= (uint8)(flag_sum);
+ }
+}
+
+static boolean inst_count_is_correct(void)
+{
+ /* Check that the instruction counter is in a valid state
+ (i.e. SUM inst_counter[0..(INST_COUNT - 1)] == memory_size).
+ */
+ uint32 i;
+ uint32 sum = 0;
+ assert(g_is_init);
+
+ for (i = 0; i < INST_COUNT; i++) {
+ assert(g_inst_counter[i] <= sal_mem_get_size());
+ sum += g_inst_counter[i];
+ }
+
+ return sum == g_size;
+}
+
+static boolean module_is_valid(void)
+{
+ /* Check for validity of memory module. This function only gets called when
+ Salis is running in debug mode. It makes Salis **very** slow in comparison
+ to when running optimized, but it is also **very** useful for debugging!
+ */
+ uint32 bidx;
+ uint32 ip_count = 0;
+ uint32 block_start_count = 0;
+ uint32 allocated_count = 0;
+ assert(g_is_init);
+ assert(g_capacity <= g_size / 2);
+ assert(inst_count_is_correct());
+
+ /* Iterate through all memory, counting the flags set on each address. We
+ then compare the sum to the flag counters to assert module validity.
+ */
+ for (bidx = 0; bidx < g_size; bidx++) {
+ if (sal_mem_is_ip(bidx)) ip_count++;
+ if (sal_mem_is_block_start(bidx)) block_start_count++;
+ if (sal_mem_is_allocated(bidx)) allocated_count++;
+ }
+
+ assert(ip_count == g_ip_count);
+ assert(block_start_count == g_block_start_count);
+ assert(allocated_count == g_allocated_count);
+ return TRUE;
+}
+
+void _sal_mem_cycle(void)
+{
+ /* Cycle memory module. Simply assert validity when running in debug mode.
+ When running optimized, this function does nothing.
+ */
+ assert(g_is_init);
+ assert(module_is_valid());
+}