1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-07 17:44:22 +03:00
inform7/inter/final-module/Chapter 5/C Memory Model.w

581 lines
22 KiB
OpenEdge ABL

[CMemoryModel::] C Memory Model.
How arrays of all kinds are stored in C.
@h Setting up the model.
The Inter semantics require that there be an area of byte-accessible memory:
(a) Byte-accessible memory must contain all of the arrays. These can but need
not have alignment gaps in between them. (For C, they do not.)
(b) "Addresses" in this memory identify individual byte positions in it. These
can but need not start at 0. (For C, they do.) They must not be too large to
fit into an Inter value.
(c) When an array name is compiled, its runtime value must be its address.
(d) When an Inter value is stored in byte-accessible memory, it occupies either
2 or 4 consecutive bytes, with the little end first. The result is called a
"word". (For C, always 4, which is always |sizeof(i7word_t)|.) Conversion between
a word stored in memory and an Inter value must be faithful in both directions.
(e) Words can be stored at any byte position, and not only at (say) multiples
of 2 or 4.
(f) Arrays in memory are free to contain a mixture of bytes and words: some do.
(g) Data may be written in byte form and read back in word form, or vice versa.
=
void CMemoryModel::initialise(code_generator *gtr) {
METHOD_ADD(gtr, WORD_TO_BYTE_MTID, CMemoryModel::word_to_byte);
METHOD_ADD(gtr, BEGIN_ARRAY_MTID, CMemoryModel::begin_array);
METHOD_ADD(gtr, ARRAY_ENTRY_MTID, CMemoryModel::array_entry);
METHOD_ADD(gtr, END_ARRAY_MTID, CMemoryModel::end_array);
}
typedef struct C_generation_memory_model_data {
int himem; /* 1 more than the largest legal byte address */
struct text_stream *array_name; /* of the array currently being compiled */
int entry_count; /* within the array currently being compiled */
} C_generation_memory_model_data;
void CMemoryModel::initialise_data(code_generation *gen) {
C_GEN_DATA(memdata.himem) = 0;
C_GEN_DATA(memdata.array_name) = Str::new();
C_GEN_DATA(memdata.entry_count) = 0;
}
@ For a given process |proc|, the current contents of byte-addressable memory will
be an array called |proc->state.memory|; here, we will compile a single static array
|i7_initial_memory| holding the initial contents of this memory, so that a new
process can be initialised from that.
The first 64 bytes of memory are reserved for the "header". We don't write those
here, and instead blank them out to all 0s.
=
void CMemoryModel::begin(code_generation *gen) {
segmentation_pos saved = CodeGen::select(gen, c_memory_array_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("i7byte_t i7_initial_memory[] = {\n");
for (int i=0; i<64; i++) WRITE("0, "); WRITE("/* header */\n");
C_GEN_DATA(memdata.himem) += 64;
CodeGen::deselect(gen, saved);
}
@ And we must close that array declaration, too:
=
void CMemoryModel::end(code_generation *gen) {
segmentation_pos saved = CodeGen::select(gen, c_memory_array_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("};\n");
CodeGen::deselect(gen, saved);
saved = CodeGen::select(gen, c_ids_and_maxima_I7CGS);
OUT = CodeGen::current(gen);
WRITE("#define i7_static_himem %d\n", C_GEN_DATA(memdata.himem));
CodeGen::deselect(gen, saved);
}
@ What goes into memory are arrays: memory is allocated only in the form of
such arrays, which are declared one at a time. See //Vanilla Constants//.
=
int CMemoryModel::begin_array(code_generator *gtr, code_generation *gen,
text_stream *array_name, inter_symbol *array_s, inter_tree_node *P, int format,
int zero_count, segmentation_pos *saved) {
Str::clear(C_GEN_DATA(memdata.array_name));
WRITE_TO(C_GEN_DATA(memdata.array_name), "%S", array_name);
C_GEN_DATA(memdata.entry_count) = 0;
if (ConstantInstruction::list_format(P) == CONST_LIST_FORMAT_GRAMMAR)
@<Short-circuit the usual Vanilla algorithm by compiling the whole array now@>
else
@<Declare this array in concert with the usual Vanilla algorithm@>;
}
@ Command-grammar arrays are handled differently: note the return value |FALSE|,
which tells Vanilla not to call us again about this array.
@<Short-circuit the usual Vanilla algorithm by compiling the whole array now@> =
if (saved) *saved = CodeGen::select(gen, c_verb_arrays_I7CGS);
VanillaIF::verb_grammar(gtr, gen, array_s, P);
return FALSE;
@<Declare this array in concert with the usual Vanilla algorithm@> =
if (saved) *saved = CodeGen::select(gen, c_arrays_I7CGS);
text_stream *format_name = I"unknown";
@<Work out the format name@>;
@<Define a constant for the byte address in memory where the array begins@>;
if ((format == TABLE_ARRAY_FORMAT) || (format == BUFFER_ARRAY_FORMAT))
@<Place the extent entry N at index 0@>;
for (int i=0; i<zero_count; i++) CMemoryModel::array_entry(gtr, gen, I"0", format);
return TRUE;
@<Work out the format name@> =
switch (format) {
case BYTE_ARRAY_FORMAT: format_name = I"byte"; break;
case WORD_ARRAY_FORMAT: format_name = I"word"; break;
case BUFFER_ARRAY_FORMAT: format_name = I"buffer"; break;
case TABLE_ARRAY_FORMAT: format_name = I"table"; break;
}
@ Crucially, the array names are |#define| constants declared up near the top
of the source code: they are not variables with pointer types, or something
like that. This means they can legally be used as values elsewhere in memory,
or as initial values of variables, and so on.
Object, class and function names can also legally appear as array entries,
because they too are defined constants, equal to their IDs: see //C Object Model//.
@<Define a constant for the byte address in memory where the array begins@> =
segmentation_pos saved = CodeGen::select(gen, c_predeclarations_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("#define ");
CNamespace::mangle(gtr, OUT, array_name);
WRITE(" %d /* = position in memory of %S array %S */\n",
C_GEN_DATA(memdata.himem), format_name, array_name);
if (array_s)
SymbolAnnotation::set_i(array_s, C_ARRAY_ADDRESS_IANN,
(inter_ti) C_GEN_DATA(memdata.himem));
CodeGen::deselect(gen, saved);
@ Of course, right now we don't know |N|, the extent of the array. So we will
refer to this with a constant like |i7_mgl_myarray__xt| (XT meaning "extent"),
which we will retrospectively predefine when the array ends.
@<Place the extent entry N at index 0@> =
TEMPORARY_TEXT(extname)
CNamespace::mangle(gtr, extname, array_name);
WRITE_TO(extname, "__xt");
CMemoryModel::array_entry(gtr, gen, extname, format);
DISCARD_TEXT(extname)
@ The call to //CMemoryModel::begin_array// is then followed by a series of calls to:
=
void CMemoryModel::array_entry(code_generator *gtr, code_generation *gen,
text_stream *entry, int format) {
segmentation_pos saved = CodeGen::select(gen, c_memory_array_I7CGS);
text_stream *OUT = CodeGen::current(gen);
if ((format == TABLE_ARRAY_FORMAT) || (format == WORD_ARRAY_FORMAT))
@<This is a word entry@>
else
@<This is a byte entry@>;
CodeGen::deselect(gen, saved);
C_GEN_DATA(memdata.entry_count)++;
}
@<This is a byte entry@> =
WRITE(" (i7byte_t) %S, /* %d */\n", entry, C_GEN_DATA(memdata.himem));
C_GEN_DATA(memdata.himem) += 1;
@ Note that |I7BYTE_0| and so on are macros and not functions (see below): they
use only arithmetic operations which can be constant-folded by the C compiler,
and therefore if |X| is a valid constant-context expression in C then so is
|I7BYTE_0(X)|.
@<This is a word entry@> =
WRITE(" I7BYTE_0(%S), I7BYTE_1(%S), I7BYTE_2(%S), I7BYTE_3(%S), /* %d */\n",
entry, entry, entry, entry, C_GEN_DATA(memdata.himem));
C_GEN_DATA(memdata.himem) += 4;
@ When all the entries have been placed, the following is called. It does nothing
except to predeclare the extent constant.
=
void CMemoryModel::end_array(code_generator *gtr, code_generation *gen, int format,
int zero_count, segmentation_pos *saved) {
segmentation_pos x_saved = CodeGen::select(gen, c_predeclarations_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("#define ");
CNamespace::mangle(gtr, OUT, C_GEN_DATA(memdata.array_name));
WRITE("__xt %d\n", C_GEN_DATA(memdata.entry_count)-1);
CodeGen::deselect(gen, x_saved);
if (saved) CodeGen::deselect(gen, *saved);
}
@ The primitives for byte and word lookup have the signatures:
= (text)
primitive !lookup val val -> val
primitive !lookupbyte val val -> val
=
=
int CMemoryModel::handle_store_by_ref(code_generation *gen, inter_tree_node *ref) {
if (ReferenceInstruction::node_is_ref_to(gen->from, ref, LOOKUP_BIP)) return TRUE;
if (ReferenceInstruction::node_is_ref_to(gen->from, ref, LOOKUPBYTE_BIP)) return TRUE;
return FALSE;
}
int CMemoryModel::invoke_primitive(code_generation *gen, inter_ti bip, inter_tree_node *P) {
text_stream *OUT = CodeGen::current(gen);
switch (bip) {
case LOOKUP_BIP:
WRITE("i7_read_word(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(")");
break;
case LOOKUPBYTE_BIP:
WRITE("i7_read_byte(proc, "); VNODE_1C; WRITE(" + "); VNODE_2C; WRITE(")");
break;
default:
return NOT_APPLICABLE;
}
return FALSE;
}
@ So, then, time to write some more of the C library. We are going to need to
define the macros |I7BYTE_0| to |I7BYTE_3|, and also the functions |i7_read_word|
and |i7_read_byte|, used just above. But we start with the function which resets
memory to its initial state when a process begins, and with the stack empty.
Note that we fill in ten bytes of the 64-byte header block of memory:
(*) The Release number as a big-endian 16-bit value at |0x34-0x35|;
(*) The Serial code as six ASCII characters (in practice digits) at |0x36-0x3B|.
We carefully defined those two constants, if they exist, before the inclusion point of
the C library in order that the conditional compilations in |i7_initialise_memory_and_stack|
will work correctly. See //CNamespace::declare_constant//.
The rest of the header area remains all zeros.
= (text to inform7_clib.h)
void *i7_calloc(i7process_t *proc, size_t how_many, size_t of_size);
void i7_initialise_memory_and_stack(i7process_t *proc);
=
= (text to inform7_clib.c)
void *i7_calloc(i7process_t *proc, size_t how_many, size_t of_size) {
void *p = calloc(how_many, of_size);
if (p == NULL) {
printf("Memory allocation failed\n");
i7_fatal_exit(proc);
}
return p;
}
i7byte_t i7_initial_memory[];
void i7_initialise_memory_and_stack(i7process_t *proc) {
if (proc->state.memory != NULL) free(proc->state.memory);
i7byte_t *mem = i7_calloc(proc, i7_static_himem, sizeof(i7byte_t));
for (int i=0; i<i7_static_himem; i++) mem[i] = i7_initial_memory[i];
#ifdef i7_mgl_Release
mem[0x34] = I7BYTE_2(i7_mgl_Release); mem[0x35] = I7BYTE_3(i7_mgl_Release);
#endif
#ifndef i7_mgl_Release
mem[0x34] = I7BYTE_2(1); mem[0x35] = I7BYTE_3(1);
#endif
#ifdef i7_mgl_Serial
char *p = i7_text_to_C_string(i7_mgl_Serial);
for (int i=0; i<6; i++) mem[0x36 + i] = p[i];
#endif
#ifndef i7_mgl_Serial
for (int i=0; i<6; i++) mem[0x36 + i] = '0';
#endif
proc->state.memory = mem;
proc->state.himem = i7_static_himem;
proc->state.stack_pointer = 0;
}
=
@ The array |proc->state.memory| is of |i7byte_t| values, so it's easy to read
and write bytes. Words are more challenging since we need to pack and unpack them.
The |i7_read_word| function reads a word which is in word entry |array_index| (counting
0, 1, 2, ...) in the array which begins at the byte address |array_address|.
We can also read "short words", that is, 16-bit values.
= (text to inform7_clib.h)
i7byte_t i7_read_byte(i7process_t *proc, i7word_t address);
i7word_t i7_read_sword(i7process_t *proc, i7word_t array_address, i7word_t array_index);
i7word_t i7_read_word(i7process_t *proc, i7word_t array_address, i7word_t array_index);
=
= (text to inform7_clib.c)
i7byte_t i7_read_byte(i7process_t *proc, i7word_t address) {
return proc->state.memory[address];
}
i7word_t i7_read_sword(i7process_t *proc, i7word_t array_address, i7word_t array_index) {
i7byte_t *data = proc->state.memory;
int byte_position = array_address + 2*array_index;
if ((byte_position < 0) || (byte_position >= i7_static_himem)) {
printf("Memory access out of range: %d\n", byte_position);
i7_fatal_exit(proc);
}
return (i7word_t) data[byte_position + 1] +
0x100U*((i7word_t) data[byte_position + 0]);
}
i7word_t i7_read_word(i7process_t *proc, i7word_t array_address, i7word_t array_index) {
i7byte_t *data = proc->state.memory;
int byte_position = array_address + 4*array_index;
if ((byte_position < 0) || (byte_position >= i7_static_himem)) {
printf("Memory access out of range: %d\n", byte_position);
i7_fatal_exit(proc);
}
return (i7word_t) data[byte_position + 3] +
0x100U*((i7word_t) data[byte_position + 2]) +
0x10000U*((i7word_t) data[byte_position + 1]) +
0x1000000U*((i7word_t) data[byte_position + 0]);
}
=
@ Writing values is again easy for bytes, but harder for words since they must
be broken up into bytes written in sequence.
Note that we make use of macros and not functions so that it is possible to
express the fragments of a packed word in constant context: this is essential
for our array initialisations.
Note that short words do not need to be written.
= (text to inform7_clib.h)
#define I7BYTE_0(V) ((V & 0xFF000000) >> 24)
#define I7BYTE_1(V) ((V & 0x00FF0000) >> 16)
#define I7BYTE_2(V) ((V & 0x0000FF00) >> 8)
#define I7BYTE_3(V) (V & 0x000000FF)
void i7_write_byte(i7process_t *proc, i7word_t address, i7byte_t new_val);
void i7_write_word(i7process_t *proc, i7word_t address, i7word_t array_index,
i7word_t new_val);
=
= (text to inform7_clib.c)
void i7_write_byte(i7process_t *proc, i7word_t address, i7byte_t new_val) {
proc->state.memory[address] = new_val;
}
void i7_write_word(i7process_t *proc, i7word_t address, i7word_t array_index,
i7word_t new_val) {
int byte_position = address + 4*array_index;
if ((byte_position < 0) || (byte_position >= i7_static_himem)) {
printf("Memory access out of range: %d\n", byte_position);
i7_fatal_exit(proc);
}
proc->state.memory[byte_position] = I7BYTE_0(new_val);
proc->state.memory[byte_position+1] = I7BYTE_1(new_val);
proc->state.memory[byte_position+2] = I7BYTE_2(new_val);
proc->state.memory[byte_position+3] = I7BYTE_3(new_val);
}
=
@ =
void CMemoryModel::word_to_byte(code_generator *gtr, code_generation *gen,
OUTPUT_STREAM, text_stream *val, int b) {
WRITE("I7BYTE_%d(%S)", b, val);
}
@ The seven primitive operations on storage need to be implemented for byte
and word lookups by the following pair of functions. Note that if |way| is
|i7_lvalue_SET| then |i7_change_byte| is equivalent to |i7_write_byte| and
|i7_change_word| to |i7_write_word|, except that they return the value as set.
= (text to inform7_clib.h)
i7byte_t i7_change_byte(i7process_t *proc, i7word_t address, i7byte_t new_val, int way);
i7word_t i7_change_word(i7process_t *proc, i7word_t array_address, i7word_t array_index,
i7word_t new_val, int way);
=
= (text to inform7_clib.c)
i7byte_t i7_change_byte(i7process_t *proc, i7word_t address, i7byte_t new_val, int way) {
i7byte_t old_val = i7_read_byte(proc, address);
i7byte_t return_val = new_val;
switch (way) {
case i7_lvalue_PREDEC: return_val = old_val-1; new_val = old_val-1; break;
case i7_lvalue_POSTDEC: return_val = old_val; new_val = old_val-1; break;
case i7_lvalue_PREINC: return_val = old_val+1; new_val = old_val+1; break;
case i7_lvalue_POSTINC: return_val = old_val; new_val = old_val+1; break;
case i7_lvalue_SETBIT: new_val = old_val | new_val; return_val = new_val; break;
case i7_lvalue_CLEARBIT: new_val = old_val &(~new_val); return_val = new_val; break;
}
i7_write_byte(proc, address, new_val);
return return_val;
}
i7word_t i7_change_word(i7process_t *proc, i7word_t array_address, i7word_t array_index,
i7word_t new_val, int way) {
i7byte_t *data = proc->state.memory;
i7word_t old_val = i7_read_word(proc, array_address, array_index);
i7word_t return_val = new_val;
switch (way) {
case i7_lvalue_PREDEC: return_val = old_val-1; new_val = old_val-1; break;
case i7_lvalue_POSTDEC: return_val = old_val; new_val = old_val-1; break;
case i7_lvalue_PREINC: return_val = old_val+1; new_val = old_val+1; break;
case i7_lvalue_POSTINC: return_val = old_val; new_val = old_val+1; break;
case i7_lvalue_SETBIT: new_val = old_val | new_val; return_val = new_val; break;
case i7_lvalue_CLEARBIT: new_val = old_val &(~new_val); return_val = new_val; break;
}
i7_write_word(proc, array_address, array_index, new_val);
return return_val;
}
=
@ The stack is very simple; it can be pushed or pulled, but there's otherwise
no access to it.
= (text to inform7_clib.h)
void i7_debug_stack(char *N);
i7word_t i7_pull(i7process_t *proc);
void i7_push(i7process_t *proc, i7word_t x);
=
= (text to inform7_clib.c)
void i7_debug_stack(char *N) {
#ifdef I7_LOG_STACK_STATE
printf("Called %s: stack %d ", N, proc->state.stack_pointer);
for (int i=0; i<proc->state.stack_pointer; i++)
printf("%d -> ", proc->state.stack[i]);
printf("\n");
#endif
}
i7word_t i7_pull(i7process_t *proc) {
if (proc->state.stack_pointer <= 0) {
printf("Stack underflow\n");
i7_fatal_exit(proc);
}
return proc->state.stack[--(proc->state.stack_pointer)];
}
void i7_push(i7process_t *proc, i7word_t x) {
if (proc->state.stack_pointer >= I7_ASM_STACK_CAPACITY) {
printf("Stack overflow\n");
i7_fatal_exit(proc);
}
proc->state.stack[proc->state.stack_pointer++] = x;
}
=
@ When processes are running, they take periodic "snapshots" of their states
so that these can if necessary be returned to. (For IF works, this is how the
UNDO command works; snapshots are taken once each turn.)
Taking a snapshot, or restoring the state from an existing snapshot, inevitably
means making a copy of state data. This has to be a deep copy, because the
|i7state_t| structure is really just a collection of pointers to arrays in
memory; copying only the pointers would not be good enough.
For the same reason, an |i7state_t| cannot simply be discarded without causing
a memory leak, so we provide a destructor function.
= (text to inform7_clib.h)
void i7_copy_state(i7process_t *proc, i7state_t *to, i7state_t *from);
void i7_destroy_state(i7process_t *proc, i7state_t *s);
=
= (text to inform7_clib.c)
void i7_copy_state(i7process_t *proc, i7state_t *to, i7state_t *from) {
to->himem = from->himem;
to->memory = i7_calloc(proc, i7_static_himem, sizeof(i7byte_t));
for (int i=0; i<i7_static_himem; i++) to->memory[i] = from->memory[i];
for (int i=0; i<I7_TMP_STORAGE_CAPACITY; i++) to->tmp[i] = from->tmp[i];
to->stack_pointer = from->stack_pointer;
for (int i=0; i<from->stack_pointer; i++) to->stack[i] = from->stack[i];
to->object_tree_parent = i7_calloc(proc, i7_max_objects, sizeof(i7word_t));
to->object_tree_child = i7_calloc(proc, i7_max_objects, sizeof(i7word_t));
to->object_tree_sibling = i7_calloc(proc, i7_max_objects, sizeof(i7word_t));
for (int i=0; i<i7_max_objects; i++) {
to->object_tree_parent[i] = from->object_tree_parent[i];
to->object_tree_child[i] = from->object_tree_child[i];
to->object_tree_sibling[i] = from->object_tree_sibling[i];
}
to->variables = i7_calloc(proc, i7_no_variables, sizeof(i7word_t));
for (int i=0; i<i7_no_variables; i++) to->variables[i] = from->variables[i];
to->current_output_stream_ID = from->current_output_stream_ID;
}
void i7_destroy_state(i7process_t *proc, i7state_t *s) {
free(s->memory);
s->himem = 0;
s->stack_pointer = 0;
free(s->object_tree_parent);
free(s->object_tree_child);
free(s->object_tree_sibling);
free(s->variables);
}
=
@ Destroying a snapshot is then a simple matter of destroying the state
stored inside it:
= (text to inform7_clib.h)
void i7_destroy_snapshot(i7process_t *proc, i7snapshot_t *unwanted);
void i7_destroy_latest_snapshot(i7process_t *proc);
=
= (text to inform7_clib.c)
void i7_destroy_snapshot(i7process_t *proc, i7snapshot_t *unwanted) {
i7_destroy_state(proc, &(unwanted->then));
unwanted->valid = 0;
}
void i7_destroy_latest_snapshot(i7process_t *proc) {
int will_be = proc->snapshot_pos - 1;
if (will_be < 0) will_be = I7_MAX_SNAPSHOTS - 1;
if (proc->snapshots[will_be].valid)
i7_destroy_snapshot(proc, &(proc->snapshots[will_be]));
proc->snapshot_pos = will_be;
}
=
@ To take a snapshot, we copy the process's current state in the next free
slot in the ring buffer of snapshots held by the process; the net effect is
that it stores the most recent |I7_MAX_SNAPSHOTS| snapshots, silently discarding
any older ones, but without leaking memory.
= (text to inform7_clib.h)
void i7_save_snapshot(i7process_t *proc);
=
= (text to inform7_clib.c)
void i7_save_snapshot(i7process_t *proc) {
if (proc->snapshots[proc->snapshot_pos].valid)
i7_destroy_snapshot(proc, &(proc->snapshots[proc->snapshot_pos]));
proc->snapshots[proc->snapshot_pos] = i7_new_snapshot();
proc->snapshots[proc->snapshot_pos].valid = 1;
i7_copy_state(proc, &(proc->snapshots[proc->snapshot_pos].then), &(proc->state));
int was = proc->snapshot_pos;
proc->snapshot_pos++;
if (proc->snapshot_pos == I7_MAX_SNAPSHOTS) proc->snapshot_pos = 0;
}
=
@ The function |i7_has_snapshot| tests whether the process has at least one
valid snapshot to revert to:
= (text to inform7_clib.h)
int i7_has_snapshot(i7process_t *proc);
void i7_restore_snapshot(i7process_t *proc);
void i7_restore_snapshot_from(i7process_t *proc, i7snapshot_t *ss);
=
= (text to inform7_clib.c)
int i7_has_snapshot(i7process_t *proc) {
int will_be = proc->snapshot_pos - 1;
if (will_be < 0) will_be = I7_MAX_SNAPSHOTS - 1;
return proc->snapshots[will_be].valid;
}
=
And |i7_restore_snapshot| restores the state of the process to that of the
most recent snapshot, winding backwards through the ring buffer, so that it's
then possible to restore again to go back another step, and so on:
= (text to inform7_clib.c)
void i7_restore_snapshot(i7process_t *proc) {
int will_be = proc->snapshot_pos - 1;
if (will_be < 0) will_be = I7_MAX_SNAPSHOTS - 1;
if (proc->snapshots[will_be].valid == 0) {
printf("Restore impossible\n");
i7_fatal_exit(proc);
}
i7_destroy_state(proc, &(proc->state));
i7_copy_state(proc, &(proc->state), &(proc->snapshots[will_be].then));
i7_destroy_snapshot(proc, &(proc->snapshots[will_be]));
int was = proc->snapshot_pos;
proc->snapshot_pos = will_be;
}
=