aboutsummaryrefslogtreecommitdiff
path: root/src/memory.c
blob: 87fc7ee46e3eeb907b9192881060273dfb833923 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
#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_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_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_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_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, 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
2 flags: BLOCK_START and ALLOCATED. These flags help organisms identify where
there is free memory space to reproduce on.
*/
#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(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(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(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 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_block_start(bidx)) block_start_count++;
		if (sal_mem_is_allocated(bidx)) allocated_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());
}