aboutsummaryrefslogtreecommitdiff
path: root/src/memory.c
blob: 7f51df49b2fb9e8360601eaf3462844e4b225804 (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
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "getter.h"
#include "instset.h"
#include "memory.h"

static boolean g_is_init;
static uint32 g_order;
static uint32 g_size;
static uint32 g_allocated;
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_allocated = 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_allocated, 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_allocated, 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, allocated)
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 > g_capacity;
}

boolean sal_mem_is_address_valid(uint32 address)
{
	/*
	* Check if given address is valid.
	*/
	assert(g_is_init);
	return address < g_size;
}

boolean sal_mem_is_allocated(uint32 address)
{
	/*
	* Check if given address is allocated.
	*/
	assert(g_is_init);
	assert(sal_mem_is_address_valid(address));
	return !!(g_memory[address] & ALLOCATED_FLAG);
}

void _sal_mem_set_allocated(uint32 address)
{
	/*
	* Set allocated flag on a given address.
	*/
	assert(g_is_init);
	assert(sal_mem_is_address_valid(address));

	if (!sal_mem_is_allocated(address)) {
		g_memory[address] ^= ALLOCATED_FLAG;
		g_allocated++;
	}
}

void _sal_mem_unset_allocated(uint32 address)
{
	/*
	* Unset allocated flag on a given address.
	*/
	assert(g_is_init);
	assert(sal_mem_is_address_valid(address));

	if (sal_mem_is_allocated(address)) {
		g_memory[address] ^= ALLOCATED_FLAG;
		g_allocated--;
	}
}

uint8 sal_mem_get_inst(uint32 address)
{
	/*
	* Get instruction currently set on a specified address, with the allocated
	* bit flag 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];
}

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 allocated = 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_allocated(bidx)) {
			allocated++;
		}
	}

	assert(allocated == g_allocated);
	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());
}