mirror of
https://github.com/ganelson/inform.git
synced 2024-07-16 22:14:23 +03:00
729 lines
42 KiB
HTML
729 lines
42 KiB
HTML
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||
|
<html>
|
||
|
<head>
|
||
|
<title>S/ft</title>
|
||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||
|
<meta http-equiv="Content-Language" content="en-gb">
|
||
|
<link href="inweb.css" rel="stylesheet" rev="stylesheet" type="text/css">
|
||
|
</head>
|
||
|
<body>
|
||
|
|
||
|
<!--Weave of 'S/ft2' generated by 7-->
|
||
|
<ul class="crumbs"><li><a href="../webs.html">★</a></li><li><a href="index.html">basic_inform Template Library</a></li><li><b>Flex Template</b></li></ul><p class="purpose">To allocate flexible-sized blocks of memory as needed to hold arbitrary-length strings of text, stored actions or other block values.</p>
|
||
|
|
||
|
<ul class="toc"><li><a href="#SP1">§1. Blocks</a></li><li><a href="#SP2">§2. Multiple Blocks</a></li><li><a href="#SP3">§3. The Heap</a></li><li><a href="#SP4">§4. Initialisation</a></li><li><a href="#SP5">§5. Net Free Space</a></li><li><a href="#SP6">§6. Make Space</a></li><li><a href="#SP7">§7. Block Allocation</a></li><li><a href="#SP8">§8. Errors</a></li><li><a href="#SP9">§9. Merging</a></li><li><a href="#SP10">§10. Recutting</a></li><li><a href="#SP11">§11. Deallocation</a></li><li><a href="#SP12">§12. Resizing</a></li><li><a href="#SP13">§13. Block Size</a></li><li><a href="#SP14">§14. Debugging Routines</a></li></ul><hr class="tocbar">
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP1"></a><b>§1. Blocks. </b>The purpose of the Flex routines is to manage flexible-sized "blocks" of
|
||
|
memory for any general-purpose use. The main customer for this service is
|
||
|
the system for creating texts, lists, stored actions, and so on, which
|
||
|
are collectively called "block values" because they store their data in
|
||
|
blocks allocated by Flex. But in this section, we're just managing
|
||
|
arrays of memory, and don't need to know what it's for.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">A "block" is a continuous range of 2^n bytes of memory, where n>= 3
|
||
|
for a 16-bit VM (i.e., for the Z-machine) and n>= 4 for a 32-bit VM
|
||
|
(i.e., on Glulx). Internally, a block is divided into a header followed
|
||
|
by a data section.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">The header size depends on the word size and the kind of block (see below). It
|
||
|
always begins with a byte specifying n, the binary logarithm of its size,
|
||
|
except that if the top bit is set then we know this isn't a flex-allocated
|
||
|
block, which turns out to be convenient. Thus the largest block representable
|
||
|
is 2^{127} bytes long, but somehow I think we can live with that. The second
|
||
|
byte contains a bitmap of (at present) four flags, whose meanings will be
|
||
|
explained below. The second word of the block, which might be at byte
|
||
|
offset 2 or 4 from the start of the block depending on the word-size of the
|
||
|
VM, is a number specifying the kind of value (if any) which the block contains
|
||
|
data of.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">The header also contains a weak kind ID and a reference count, but the Flex
|
||
|
routines do nothing with either of these except to initialise them; they're
|
||
|
provided for the benefit of the <code class="display"><span class="extract">BlockValue</span></code> system.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">The data section of a block begins at the byte offset <code class="display"><span class="extract">BLK_DATA_OFFSET</span></code>
|
||
|
from the address of the block: but see below for how multiple-blocks
|
||
|
behave differently.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">These definitions must not be altered without making matching changes
|
||
|
to the compiler.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">Constant BLK_HEADER_N = 0;</span>
|
||
|
<span class="plain">Constant BLK_HEADER_FLAGS = 1;</span>
|
||
|
<span class="plain">Constant BLK_FLAG_MULTIPLE = $$00000001;</span>
|
||
|
<span class="plain">Constant BLK_FLAG_16_BIT = $$00000010;</span>
|
||
|
<span class="plain">Constant BLK_FLAG_WORD = $$00000100;</span>
|
||
|
<span class="plain">Constant BLK_FLAG_RESIDENT = $$00001000;</span>
|
||
|
<span class="plain">Constant BLK_FLAG_TRUNCMULT = $$00010000;</span>
|
||
|
<span class="plain">Constant BLK_HEADER_KOV = 1;</span>
|
||
|
<span class="plain">Constant BLK_HEADER_RCOUNT = 2;</span>
|
||
|
|
||
|
<span class="plain">Constant BLK_DATA_OFFSET = 3*WORDSIZE;</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP2"></a><b>§2. Multiple Blocks. </b>Some of the data we want to store will be fixed in size, but some of it will
|
||
|
need to expand or contract. The latter can change unpredictably in size and
|
||
|
might at any point overflow their current storage, so they're stored in a
|
||
|
doubly linked list of blocks. The pointer to such a flexible-length array is
|
||
|
by definition the pointer to the block heading this linked list. For instance,
|
||
|
the data in a text
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">"But now I worship a celestiall Sunne"</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph">might be stored in a list of blocks like so:
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">NULL <-- BN: "But now I wor" <--> BN2: "ship a celestiall Sunne" --> NULL</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph">Note that the unique pointer to <code class="display"><span class="extract">BN2</span></code> is the one in the header of the
|
||
|
<code class="display"><span class="extract">BN</span></code> block. When we need to grow such a text, we add additional blocks;
|
||
|
if the text should shrink, blocks at the end can at our discretion be
|
||
|
deallocated. If the entire text should be deallocated, then all of the
|
||
|
blocks used for it are deallocated, starting at the back and working
|
||
|
towards the front.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">A multiple-block is one whose flags byte contains the <code class="display"><span class="extract">BLK_FLAG_MULTIPLE</span></code>.
|
||
|
This information is redundant since it could in principle be deduced from
|
||
|
the kind of value stored in the block, which is recorded in the
|
||
|
<code class="display"><span class="extract">-->BLK_HEADER_KOV</span></code> word, but that would be too slow. <code class="display"><span class="extract">BLK_FLAG_MULTIPLE</span></code>
|
||
|
can never change for a currently allocated block, just as it can never
|
||
|
change its KOV.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">A multiple-block header is longer than that of an ordinary block, because
|
||
|
it contains two extra words: <code class="display"><span class="extract">-->BLK_NEXT</span></code> is the next block in the
|
||
|
doubly-linked list of blocks representing the current value, or <code class="display"><span class="extract">NULL</span></code>
|
||
|
if this is the end; <code class="display"><span class="extract">-->BLK_PREV</span></code> is the previous block, or <code class="display"><span class="extract">NULL</span></code> if this
|
||
|
is the beginning. The need to fit these two extra words in means that the
|
||
|
data section is deferred, and so for a multiple-block data begins at the
|
||
|
byte offset <code class="display"><span class="extract">BLK_DATA_MULTI_OFFSET</span></code> rather than <code class="display"><span class="extract">BLK_DATA_OFFSET</span></code>.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">Constant BLK_DATA_MULTI_OFFSET = BLK_DATA_OFFSET + 2*WORDSIZE;</span>
|
||
|
<span class="plain">Constant BLK_NEXT 3;</span>
|
||
|
<span class="plain">Constant BLK_PREV 4;</span>
|
||
|
|
||
|
<span class="plain">! Constant BLKVALUE_TRACE = 1; ! Uncomment this for debugging purposes</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP3"></a><b>§3. The Heap. </b>Properly speaking, a "heap" is a specific kind of structure often used for
|
||
|
managing uneven-sized or unpredictably changing data. We use "heap" here in
|
||
|
the looser sense of being an amorphous-sized collection of blocks of memory,
|
||
|
some free, others allocated; our actual representation of free space on the
|
||
|
heap is not a heap structure in computer science terms. (Though this segment
|
||
|
could easily be rewritten to make it so, or to adopt any other scheme which
|
||
|
might be faster.) The heap begins as a contiguous region of memory, but it
|
||
|
need not remain so: on Glulx we use dynamic memory allocation to extend it.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">For I7 purposes we don't need a way to represent allocated memory, only
|
||
|
the free memory. A block is free if and only if it has <code class="display"><span class="extract">-->BLK_HEADER_KOV</span></code>
|
||
|
equal to 0, which is never a valid kind of value, and also has the multiple
|
||
|
flag set. We do that because we construct the whole collection of free
|
||
|
blocks, at any given time, as a single, multiple-block "value": a doubly
|
||
|
linked list joined by the <code class="display"><span class="extract">-->BLK_NEXT</span></code> and <code class="display"><span class="extract"><--BLK_PREV</span></code>.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">A single block, at the bottom of memory and never moving, never allocated
|
||
|
to anyone, is preserved in order to be the head of this linked list of
|
||
|
free blocks. This is a 16-byte (i.e., n=4) block, which we format when
|
||
|
the heap is initialised in <code class="display"><span class="extract">HeapInitialise()</span></code>. Thus the heap is full
|
||
|
if and only if the <code class="display"><span class="extract">-->BLK_NEXT</span></code> of the head-free-block is <code class="display"><span class="extract">NULL</span></code>.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">So far we have described a somewhat lax regime. After many allocations and
|
||
|
deallocations one could imagine the list of free blocks becoming a very
|
||
|
long list of individually small blocks, which would both make it difficult
|
||
|
to allocate large blocks, and also slow to look through the list. To
|
||
|
ameliorate matters, we maintain the following invariants:
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<ul class="items"><li>(a) In the free blocks list, <code class="display"><span class="extract">B-->BLK_NEXT</span></code> is always an address after <code class="display"><span class="extract">B</span></code>;
|
||
|
</li><li>(b) For any contiguous run of free space blocks in memory (excluding the
|
||
|
head-free-block), taking up a total of T bytes, the last block in the run
|
||
|
has size 2^n where n is the largest integer such that 2^n<= T.
|
||
|
</li></ul>
|
||
|
<p class="inwebparagraph">For instance, there can never be two consecutive free blocks of size 128:
|
||
|
they would form a "run" in the sense of rule (b) of size T = 256, and
|
||
|
when T is a power of two the run must contain a single block. In general,
|
||
|
it's easy to prove that the number of blocks in the run is exactly the
|
||
|
number of 1s when T is written out as a binary number, and that the
|
||
|
blocks are ordered in memory from small to large (the reverse of the
|
||
|
direction of reading, i.e., rightmost 1 digit first). Maintaining (b)
|
||
|
is a matter of being careful to fragment blocks only from the front when
|
||
|
smaller blocks are needed, and to rejoin from the back when blocks are
|
||
|
freed and added to the free space object.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">Array Flex_Heap -> (MEMORY_HEAP_SIZE + 16); ! Plus 16 to allow room for head-free-block</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP4"></a><b>§4. Initialisation. </b>To recap: the constant <code class="display"><span class="extract">MEMORY_HEAP_SIZE</span></code> has been predefined by the NI compiler,
|
||
|
and is always itself a power of 2, say 2^n. We therefore have 2^n + 2^4
|
||
|
bytes available to us, and we format these as a free space list of two
|
||
|
blocks: the 2^4-sized "head-free-block" described above followed by
|
||
|
a 2^n-sized block exactly containing the whole of the rest of the heap.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ HeapInitialise n bsize blk2;</span>
|
||
|
<span class="plain">blk2 = Flex_Heap + 16;</span>
|
||
|
<span class="plain">Flex_Heap->BLK_HEADER_N = 4;</span>
|
||
|
<span class="plain">Flex_Heap-->BLK_HEADER_KOV = 0;</span>
|
||
|
<span class="plain">Flex_Heap-->BLK_HEADER_RCOUNT = MAX_POSITIVE_NUMBER;</span>
|
||
|
<span class="plain">Flex_Heap->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE;</span>
|
||
|
<span class="plain">Flex_Heap-->BLK_NEXT = blk2;</span>
|
||
|
<span class="plain">Flex_Heap-->BLK_PREV = NULL;</span>
|
||
|
<span class="plain">for (bsize=1: bsize < MEMORY_HEAP_SIZE: bsize=bsize*2) n++;</span>
|
||
|
<span class="plain">blk2->BLK_HEADER_N = n;</span>
|
||
|
<span class="plain">blk2-->BLK_HEADER_KOV = 0;</span>
|
||
|
<span class="plain">blk2-->BLK_HEADER_RCOUNT = 0;</span>
|
||
|
<span class="plain">blk2->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE;</span>
|
||
|
<span class="plain">blk2-->BLK_NEXT = NULL;</span>
|
||
|
<span class="plain">blk2-->BLK_PREV = Flex_Heap;</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP5"></a><b>§5. Net Free Space. </b>"Net" in the sense of "after deductions for the headers": this is the
|
||
|
actual number of free bytes left on the heap which could be used for data.
|
||
|
Note that it is used to predict whether it is possible to fit something
|
||
|
further in: so there are two answers, depending on whether the something
|
||
|
is multiple-block data (with a larger header and therefore less room for
|
||
|
data) or single-block data (smaller header, more room).
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ HeapNetFreeSpace multiple txb asize;</span>
|
||
|
<span class="plain">for (txb=Flex_Heap-->BLK_NEXT: txb~=NULL: txb=txb-->BLK_NEXT) {</span>
|
||
|
<span class="plain">asize = asize + FlexSize(txb);</span>
|
||
|
<span class="plain">if (multiple) asize = asize - BLK_DATA_MULTI_OFFSET;</span>
|
||
|
<span class="plain">else asize = asize - BLK_DATA_OFFSET;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">return asize;</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP6"></a><b>§6. Make Space. </b>The following routine determines if there is enough free space to accommodate
|
||
|
another <code class="display"><span class="extract">size</span></code> bytes of data, given that it has to be multiple-block data if
|
||
|
the <code class="display"><span class="extract">multiple</span></code> flag is set. If the answer turns out to be "no", we see if
|
||
|
the host virtual machine is able to allocate more for us: if it is, then
|
||
|
we ask for 2^m further bytes, where 2^m is at least <code class="display"><span class="extract">size</span></code> plus the
|
||
|
worst-case header storage requirement (16 bytes), and in addition is large
|
||
|
enough to make it worth while allocating. We don't want to bother the VM
|
||
|
by asking for trivial amounts of memory.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">This looks to be more memory than is needed, since after all we've asked
|
||
|
for enough that the new data can fit entirely into the new block allocated,
|
||
|
and we might have been able to squeeze some of it into the existing free
|
||
|
space. But it ensures that heap invariant (b) above is preserved, and
|
||
|
besides, running out of memory tends to be something you don't do only once.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">(The code below is a refinement on the original, suggested by Jesse McGrew,
|
||
|
which handles non-multiple blocks better.)
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">Constant SMALLEST_BLK_WORTH_ALLOCATING = 12; ! i.e. 2^12 = 4096 bytes</span>
|
||
|
|
||
|
<span class="plain">[ HeapMakeSpace size multiple newblocksize newblock B n;</span>
|
||
|
<span class="plain">for (::) {</span>
|
||
|
<span class="plain">if (multiple) {</span>
|
||
|
<span class="plain">if (HeapNetFreeSpace(multiple) >= size) rtrue;</span>
|
||
|
<span class="plain">} else {</span>
|
||
|
<span class="plain">if (HeapLargestFreeBlock(0) >= size) rtrue;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">newblocksize = 1;</span>
|
||
|
<span class="plain">for (n=0: (n<SMALLEST_BLK_WORTH_ALLOCATING) || (newblocksize<size): n++)</span>
|
||
|
<span class="plain">newblocksize = newblocksize*2;</span>
|
||
|
<span class="plain">while (newblocksize < size+16) newblocksize = newblocksize*2;</span>
|
||
|
<span class="plain">newblock = VM_AllocateMemory(newblocksize);</span>
|
||
|
<span class="plain">if (newblock == 0) rfalse;</span>
|
||
|
<span class="plain">newblock->BLK_HEADER_N = n;</span>
|
||
|
<span class="plain">newblock-->BLK_HEADER_KOV = 0;</span>
|
||
|
<span class="plain">newblock-->BLK_HEADER_RCOUNT = 0;</span>
|
||
|
<span class="plain">newblock->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE;</span>
|
||
|
<span class="plain">newblock-->BLK_NEXT = NULL;</span>
|
||
|
<span class="plain">newblock-->BLK_PREV = NULL;</span>
|
||
|
<span class="plain">for (B = Flex_Heap-->BLK_NEXT:B ~= NULL:B = B-->BLK_NEXT)</span>
|
||
|
<span class="plain">if (B-->BLK_NEXT == NULL) {</span>
|
||
|
<span class="plain">B-->BLK_NEXT = newblock;</span>
|
||
|
<span class="plain">newblock-->BLK_PREV = B;</span>
|
||
|
<span class="plain">jump Linked;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">Flex_Heap-->BLK_NEXT = newblock;</span>
|
||
|
<span class="plain">newblock-->BLK_PREV = Flex_Heap;</span>
|
||
|
<span class="plain">.Linked; ;</span>
|
||
|
<span class="plain">#ifdef BLKVALUE_TRACE;</span>
|
||
|
<span class="plain">print "Increasing heap to free space map: "; FlexDebugDecomposition(Flex_Heap, 0);</span>
|
||
|
<span class="plain">#endif;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">rtrue;</span>
|
||
|
<span class="plain">];</span>
|
||
|
|
||
|
<span class="plain">[ HeapLargestFreeBlock multiple txb asize best;</span>
|
||
|
<span class="plain">best = 0;</span>
|
||
|
<span class="plain">for (txb=Flex_Heap-->BLK_NEXT: txb~=NULL: txb=txb-->BLK_NEXT) {</span>
|
||
|
<span class="plain">asize = FlexSize(txb);</span>
|
||
|
<span class="plain">if (multiple) asize = asize - BLK_DATA_MULTI_OFFSET;</span>
|
||
|
<span class="plain">else asize = asize - BLK_DATA_OFFSET;</span>
|
||
|
<span class="plain">if (asize > best) best = asize;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">return best;</span>
|
||
|
<span class="plain">];</span>
|
||
|
|
||
|
<span class="plain">[ HeapDebug full;</span>
|
||
|
<span class="plain">if (full) {</span>
|
||
|
<span class="plain">print "Managing a heap of initially ", MEMORY_HEAP_SIZE+16, " bytes.^";</span>
|
||
|
<span class="plain">print HeapNetFreeSpace(false), " bytes currently free.^";</span>
|
||
|
<span class="plain">print "Free space decomposition: "; FlexDebugDecomposition(Flex_Heap);</span>
|
||
|
<span class="plain">print "Free space map: "; FlexDebug(Flex_Heap);</span>
|
||
|
<span class="plain">} else {</span>
|
||
|
<span class="plain">print HeapNetFreeSpace(false), " of ", MEMORY_HEAP_SIZE+16, " bytes free.^";</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP7"></a><b>§7. Block Allocation. </b>Now for the Flex routines. Those with names ending in <code class="display"><span class="extract">Internal</span></code> are private
|
||
|
and should only be called by other Flex routines. Even the public ones must
|
||
|
be used with care, or memory leaks or crashes will occur.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">The routine <code class="display"><span class="extract">FlexAllocate(N, K, F)</span></code> allocates a block with room for <code class="display"><span class="extract">size</span></code>
|
||
|
net bytes of data, which will have kind of value <code class="display"><span class="extract">K</span></code> and with flags <code class="display"><span class="extract">F</span></code>. If
|
||
|
the flags include <code class="display"><span class="extract">BLK_FLAG_MULTIPLE</span></code>, this may be either a list of blocks
|
||
|
or a single block. It returns either the address of the block or else throws
|
||
|
run-time problem message and returns 0.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">If it does succeed and return a nonzero address, then the caller must be
|
||
|
able to guarantee that <code class="display"><span class="extract">FlexFree</span></code> will later be called, exactly once, on
|
||
|
this address. In other words, <code class="display"><span class="extract">FlexAllocate</span></code> and <code class="display"><span class="extract">FlexFree</span></code> behave somewhat
|
||
|
like C's <code class="display"><span class="extract">malloc</span></code> and <code class="display"><span class="extract">free</span></code> routines, with all the advantages and hazards
|
||
|
that implies.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">In allocation, we try to find a block which is as close as possible to the
|
||
|
right size, and we may have to subdivide blocks: see case II below. For
|
||
|
instance, if a block of size 2^n is available and we only need a block of
|
||
|
size 2^k where k<n then we break it up in memory as a sequence of
|
||
|
blocks of size 2^k, 2^k, 2^{k+1}, 2^{k+2}, ..., 2^{n-1}: note that the
|
||
|
sum of these sizes is the 2^n we started with. We then use the first
|
||
|
block of size 2^k. To continue the comparison with binary arithmetic,
|
||
|
this is like a subtraction with repeated carries:
|
||
|
10000000_2 - 00001000_2 = 01111000_2
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ FlexAllocate size kov flags</span>
|
||
|
<span class="plain">dsize n m free_block min_m max_m smallest_oversized_block secondhalf i hsize head tail;</span>
|
||
|
|
||
|
<span class="plain">if (HeapMakeSpace(size, flags & BLK_FLAG_MULTIPLE) == false) FlexError("ran out");</span>
|
||
|
|
||
|
<span class="plain">! Calculate the header size for a block of this KOV</span>
|
||
|
<span class="plain">if (flags & BLK_FLAG_MULTIPLE) hsize = BLK_DATA_MULTI_OFFSET;</span>
|
||
|
<span class="plain">else hsize = BLK_DATA_OFFSET;</span>
|
||
|
<span class="plain">! Calculate the data size</span>
|
||
|
<span class="plain">n=0; for (dsize=1: ((dsize < hsize+size) || (n<3+(WORDSIZE/2))): dsize=dsize*2) n++;</span>
|
||
|
|
||
|
<span class="plain">! Seek a free block closest to the correct size, but starting from the</span>
|
||
|
<span class="plain">! block after the fixed head-free-block, which we can't touch</span>
|
||
|
<span class="plain">min_m = 10000; max_m = 0;</span>
|
||
|
<span class="plain">for (free_block = Flex_Heap-->BLK_NEXT:</span>
|
||
|
<span class="plain">free_block ~= NULL:</span>
|
||
|
<span class="plain">free_block = free_block-->BLK_NEXT) {</span>
|
||
|
<span class="plain">m = free_block->BLK_HEADER_N;</span>
|
||
|
<span class="plain">! Current block the ideal size</span>
|
||
|
<span class="plain">if (m == n) jump CorrectSizeFound;</span>
|
||
|
<span class="plain">! Current block too large: find the smallest which is larger than needed</span>
|
||
|
<span class="plain">if (m > n) {</span>
|
||
|
<span class="plain">if (min_m > m) {</span>
|
||
|
<span class="plain">min_m = m;</span>
|
||
|
<span class="plain">smallest_oversized_block = free_block;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">! Current block too small: find the largest which is smaller than needed</span>
|
||
|
<span class="plain">if (m < n) {</span>
|
||
|
<span class="plain">if (max_m < m) {</span>
|
||
|
<span class="plain">max_m = m;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">}</span>
|
||
|
|
||
|
<span class="plain">if (min_m == 10000) {</span>
|
||
|
<span class="plain">! Case I: No block is large enough to hold the entire size</span>
|
||
|
<span class="plain">if (flags & BLK_FLAG_MULTIPLE == 0) FlexError("too fragmented");</span>
|
||
|
<span class="plain">! Set dsize to the size in bytes if the largest block available</span>
|
||
|
<span class="plain">for (dsize=1: max_m > 0: dsize=dsize*2) max_m--;</span>
|
||
|
<span class="plain">! Split as a head (dsize-hsize), which we can be sure fits into one block,</span>
|
||
|
<span class="plain">! plus a tail (size-(dsize-hsize), which might be a list of blocks</span>
|
||
|
<span class="plain">head = FlexAllocate(dsize-hsize, kov, flags);</span>
|
||
|
<span class="plain">if (head == 0) FlexError("for head block not available");</span>
|
||
|
<span class="plain">tail = FlexAllocate(size-(dsize-hsize), kov, flags);</span>
|
||
|
<span class="plain">if (tail == 0) FlexError("for tail block not available");</span>
|
||
|
<span class="plain">head-->BLK_NEXT = tail;</span>
|
||
|
<span class="plain">tail-->BLK_PREV = head;</span>
|
||
|
<span class="plain">return head;</span>
|
||
|
<span class="plain">}</span>
|
||
|
|
||
|
<span class="plain">! Case II: No block is the right size, but some exist which are too big</span>
|
||
|
<span class="plain">! Set dsize to the size in bytes of the smallest oversized block</span>
|
||
|
<span class="plain">for (dsize=1,m=1: m<=min_m: dsize=dsize*2) m++;</span>
|
||
|
<span class="plain">free_block = smallest_oversized_block;</span>
|
||
|
<span class="plain">while (min_m > n) {</span>
|
||
|
<span class="plain">! Repeatedly halve free_block at the front until the two smallest</span>
|
||
|
<span class="plain">! fragments left are the correct size: then take the frontmost</span>
|
||
|
<span class="plain">dsize = dsize/2;</span>
|
||
|
<span class="plain">! print "Halving size to ", dsize, "^";</span>
|
||
|
<span class="plain">secondhalf = free_block + dsize;</span>
|
||
|
<span class="plain">secondhalf-->BLK_NEXT = free_block-->BLK_NEXT;</span>
|
||
|
<span class="plain">if (secondhalf-->BLK_NEXT ~= NULL)</span>
|
||
|
<span class="plain">(secondhalf-->BLK_NEXT)-->BLK_PREV = secondhalf;</span>
|
||
|
<span class="plain">secondhalf-->BLK_PREV = free_block;</span>
|
||
|
<span class="plain">free_block-->BLK_NEXT = secondhalf;</span>
|
||
|
<span class="plain">free_block->BLK_HEADER_N = (free_block->BLK_HEADER_N) - 1;</span>
|
||
|
<span class="plain">secondhalf->BLK_HEADER_N = free_block->BLK_HEADER_N;</span>
|
||
|
<span class="plain">secondhalf-->BLK_HEADER_KOV = free_block-->BLK_HEADER_KOV;</span>
|
||
|
<span class="plain">secondhalf-->BLK_HEADER_RCOUNT = 0;</span>
|
||
|
<span class="plain">secondhalf->BLK_HEADER_FLAGS = free_block->BLK_HEADER_FLAGS;</span>
|
||
|
<span class="plain">min_m--;</span>
|
||
|
<span class="plain">}</span>
|
||
|
|
||
|
<span class="plain">! Once that is done, free_block points to a block which is exactly the</span>
|
||
|
<span class="plain">! right size, so we can fall into...</span>
|
||
|
|
||
|
<span class="plain">! Case III: There is a free block which has the correct size.</span>
|
||
|
<span class="plain">.CorrectSizeFound;</span>
|
||
|
<span class="plain">! Delete the free block from the double linked list of free blocks: note</span>
|
||
|
<span class="plain">! that it cannot be the head of this list, which is fixed</span>
|
||
|
<span class="plain">if (free_block-->BLK_NEXT == NULL) {</span>
|
||
|
<span class="plain">! We remove final block, so previous is now final</span>
|
||
|
<span class="plain">(free_block-->BLK_PREV)-->BLK_NEXT = NULL;</span>
|
||
|
<span class="plain">} else {</span>
|
||
|
<span class="plain">! We remove a middle block, so join previous to next</span>
|
||
|
<span class="plain">(free_block-->BLK_PREV)-->BLK_NEXT = free_block-->BLK_NEXT;</span>
|
||
|
<span class="plain">(free_block-->BLK_NEXT)-->BLK_PREV = free_block-->BLK_PREV;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">free_block-->BLK_HEADER_KOV = KindAtomic(kov);</span>
|
||
|
<span class="plain">free_block-->BLK_HEADER_RCOUNT = 1;</span>
|
||
|
<span class="plain">free_block->BLK_HEADER_FLAGS = flags;</span>
|
||
|
<span class="plain">if (flags & BLK_FLAG_MULTIPLE) {</span>
|
||
|
<span class="plain">free_block-->BLK_NEXT = NULL;</span>
|
||
|
<span class="plain">free_block-->BLK_PREV = NULL;</span>
|
||
|
<span class="plain">}</span>
|
||
|
|
||
|
<span class="plain">! Zero out the data bytes in the memory allocated</span>
|
||
|
<span class="plain">for (i=hsize:i<dsize:i++) free_block->i=0;</span>
|
||
|
<span class="plain">return free_block;</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP8"></a><b>§8. Errors. </b>In the event that <code class="display"><span class="extract">FlexAllocate</span></code> returns 0, the caller may not be able
|
||
|
to survive, so the following is provided as a standardised way to halt
|
||
|
the virtual machine.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ FlexError reason;</span>
|
||
|
<span class="plain">print "*** Memory ", (string) reason, " ***^";</span>
|
||
|
<span class="plain">RunTimeProblem(RTP_HEAPERROR);</span>
|
||
|
<span class="plain">@quit;</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP9"></a><b>§9. Merging. </b>Given a free block <code class="display"><span class="extract">block</span></code>, find the maximal contiguous run of free blocks
|
||
|
which contains it, and then call <code class="display"><span class="extract">FlexRecutInternal</span></code> to recut it to conform to
|
||
|
invariant (b) above.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ FlexMergeInternal block first last pv nx;</span>
|
||
|
<span class="plain">first = block; last = block;</span>
|
||
|
<span class="plain">while (last-->BLK_NEXT == last+FlexSize(last))</span>
|
||
|
<span class="plain">last = last-->BLK_NEXT;</span>
|
||
|
<span class="plain">while ((first-->BLK_PREV + FlexSize(first-->BLK_PREV) == first) &&</span>
|
||
|
<span class="plain">(first-->BLK_PREV ~= Flex_Heap))</span>
|
||
|
<span class="plain">first = first-->BLK_PREV;</span>
|
||
|
<span class="plain">pv = first-->BLK_PREV;</span>
|
||
|
<span class="plain">nx = last-->BLK_NEXT;</span>
|
||
|
<span class="plain">#ifdef BLKVALUE_TRACE;</span>
|
||
|
<span class="plain">print "Merging: "; FlexDebugDecomposition(pv-->BLK_NEXT, nx); print "^";</span>
|
||
|
<span class="plain">#endif;</span>
|
||
|
<span class="plain">if (FlexRecutInternal(first, last)) {</span>
|
||
|
<span class="plain">#ifdef BLKVALUE_TRACE;</span>
|
||
|
<span class="plain">print " --> "; FlexDebugDecomposition(pv-->BLK_NEXT, nx); print "^";</span>
|
||
|
<span class="plain">#endif;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP10"></a><b>§10. Recutting. </b>Given a segment of the free block list, containing blocks known to be contiguous
|
||
|
in memory, we recut into a sequence of blocks satisfying invariant (b): we
|
||
|
repeatedly cut the largest 2^m-sized chunk off the back end until it is all
|
||
|
used up.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ FlexRecutInternal first last tsize backsize mfrom mto bnext backend n dsize fine_so_far;</span>
|
||
|
<span class="plain">if (first == last) rfalse;</span>
|
||
|
<span class="plain">mfrom = first; mto = last + FlexSize(last);</span>
|
||
|
<span class="plain">bnext = last-->BLK_NEXT;</span>
|
||
|
<span class="plain">fine_so_far = true;</span>
|
||
|
<span class="plain">for (:mto>mfrom: mto = mto - backsize) {</span>
|
||
|
<span class="plain">for (n=0, backsize=1: backsize*2 <= mto-mfrom: n++) backsize=backsize*2;</span>
|
||
|
<span class="plain">if ((fine_so_far) && (backsize == FlexSize(last))) {</span>
|
||
|
<span class="plain">bnext = last; last = last-->BLK_PREV;</span>
|
||
|
<span class="plain">bnext-->BLK_PREV = last;</span>
|
||
|
<span class="plain">last-->BLK_NEXT = bnext;</span>
|
||
|
<span class="plain">continue;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">fine_so_far = false; ! From this point, "last" is meaningless</span>
|
||
|
<span class="plain">backend = mto - backsize;</span>
|
||
|
<span class="plain">backend->BLK_HEADER_N = n;</span>
|
||
|
<span class="plain">backend-->BLK_HEADER_KOV = 0;</span>
|
||
|
<span class="plain">backend-->BLK_HEADER_RCOUNT = 0;</span>
|
||
|
<span class="plain">backend->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE;</span>
|
||
|
<span class="plain">backend-->BLK_NEXT = bnext;</span>
|
||
|
<span class="plain">if (bnext ~= NULL) {</span>
|
||
|
<span class="plain">bnext-->BLK_PREV = backend;</span>
|
||
|
<span class="plain">bnext = backend;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">if (fine_so_far) rfalse;</span>
|
||
|
<span class="plain">rtrue;</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP11"></a><b>§11. Deallocation. </b>As noted above, <code class="display"><span class="extract">FlexFree</span></code> must be called exactly once on each nonzero
|
||
|
pointer returned by <code class="display"><span class="extract">FlexAllocate</span></code>.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">There are two complications: first, when we free a multiple block we need
|
||
|
to free all of the blocks in the list, starting from the back end and
|
||
|
working forwards to the front — this is the job of <code class="display"><span class="extract">FlexFree</span></code>. Second,
|
||
|
when any given block is freed it has to be put into the free block list
|
||
|
at the correct position to preserve invariant (a): it might either come
|
||
|
after all of the currently free blocks in memory, and have to be added to
|
||
|
the end of the list, or in between two, and have to be inserted mid-list,
|
||
|
but it can't be before all of them because the head-free-block is kept
|
||
|
lowest in memory of all possible blocks. (Note that Glulx can't allocate
|
||
|
memory dynamically which undercuts the ordinary array space created by I6:
|
||
|
I6 arrays fill up memory from the bottom.)
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">Certain blocks outside the heap are marked as "resident" in memory,
|
||
|
that is, are indestructible. This enables Inform to compile constant values.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ FlexFree block fromtxb ptxb;</span>
|
||
|
<span class="plain">if (block == 0) return;</span>
|
||
|
<span class="plain">if ((block->BLK_HEADER_FLAGS) & BLK_FLAG_RESIDENT) return;</span>
|
||
|
<span class="plain">if ((block->BLK_HEADER_N) & $80) return; ! not a flexible block at all</span>
|
||
|
<span class="plain">if ((block->BLK_HEADER_FLAGS) & BLK_FLAG_MULTIPLE) {</span>
|
||
|
<span class="plain">if (block-->BLK_PREV ~= NULL) (block-->BLK_PREV)-->BLK_NEXT = NULL;</span>
|
||
|
<span class="plain">fromtxb = block;</span>
|
||
|
<span class="plain">for (:(block-->BLK_NEXT)~=NULL:block = block-->BLK_NEXT) ;</span>
|
||
|
<span class="plain">while (block ~= fromtxb) {</span>
|
||
|
<span class="plain">ptxb = block-->BLK_PREV; FlexFreeSingleBlockInternal(block); block = ptxb;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">FlexFreeSingleBlockInternal(block);</span>
|
||
|
<span class="plain">];</span>
|
||
|
|
||
|
<span class="plain">[ FlexFreeSingleBlockInternal block free nx;</span>
|
||
|
<span class="plain">block-->BLK_HEADER_KOV = 0;</span>
|
||
|
<span class="plain">block-->BLK_HEADER_RCOUNT = 0;</span>
|
||
|
<span class="plain">block->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE;</span>
|
||
|
<span class="plain">for (free = Flex_Heap:free ~= NULL:free = free-->BLK_NEXT) {</span>
|
||
|
<span class="plain">nx = free-->BLK_NEXT;</span>
|
||
|
<span class="plain">if (nx == NULL) {</span>
|
||
|
<span class="plain">free-->BLK_NEXT = block;</span>
|
||
|
<span class="plain">block-->BLK_PREV = free;</span>
|
||
|
<span class="plain">block-->BLK_NEXT = NULL;</span>
|
||
|
<span class="plain">FlexMergeInternal(block);</span>
|
||
|
<span class="plain">return;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">if (UnsignedCompare(nx, block) == 1) {</span>
|
||
|
<span class="plain">free-->BLK_NEXT = block;</span>
|
||
|
<span class="plain">block-->BLK_PREV = free;</span>
|
||
|
<span class="plain">block-->BLK_NEXT = nx;</span>
|
||
|
<span class="plain">nx-->BLK_PREV = block;</span>
|
||
|
<span class="plain">FlexMergeInternal(block);</span>
|
||
|
<span class="plain">return;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP12"></a><b>§12. Resizing. </b>A block which has been allocated, but not yet freed, can sometimes have
|
||
|
its data capacity changed by <code class="display"><span class="extract">FlexResize</span></code>.
|
||
|
</p>
|
||
|
|
||
|
<p class="inwebparagraph">When the data being stored stretches or shrinks, we will sometimes need
|
||
|
to change the size of the block(s) containing the data — though not always:
|
||
|
we might sometimes need to resize a 1052-byte text to a 1204-byte text and
|
||
|
find that we are sitting in a 2048-byte block in any case. We either shed
|
||
|
blocks from the end of the chain, or add new blocks at the end, that being
|
||
|
the simplest thing to do. Sometimes it might mean preserving a not very
|
||
|
efficient block division, but it minimises the churn of blocks being
|
||
|
allocated and freed, which is probably good.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ FlexResize block req newsize dsize newblk kov n i otxb flags;</span>
|
||
|
<span class="plain">if (block == 0) FlexError("failed resizing null block");</span>
|
||
|
<span class="plain">kov = block-->BLK_HEADER_KOV;</span>
|
||
|
<span class="plain">flags = block->BLK_HEADER_FLAGS;</span>
|
||
|
<span class="plain">if (flags & BLK_FLAG_MULTIPLE == 0) FlexError("failed resizing inextensible block");</span>
|
||
|
<span class="plain">otxb = block;</span>
|
||
|
<span class="plain">newsize = req;</span>
|
||
|
<span class="plain">for (:: block = block-->BLK_NEXT) {</span>
|
||
|
<span class="plain">n = block->BLK_HEADER_N;</span>
|
||
|
<span class="plain">for (dsize=1: n>0: n--) dsize = dsize*2;</span>
|
||
|
<span class="plain">i = dsize - BLK_DATA_MULTI_OFFSET;</span>
|
||
|
<span class="plain">newsize = newsize - i;</span>
|
||
|
<span class="plain">if (newsize > 0) {</span>
|
||
|
<span class="plain">if (block-->BLK_NEXT ~= NULL) continue;</span>
|
||
|
<span class="plain">newblk = FlexAllocate(newsize, kov, flags);</span>
|
||
|
<span class="plain">if (newblk == 0) rfalse;</span>
|
||
|
<span class="plain">block-->BLK_NEXT = newblk;</span>
|
||
|
<span class="plain">newblk-->BLK_PREV = block;</span>
|
||
|
<span class="plain">return;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">if (block-->BLK_NEXT ~= NULL) {</span>
|
||
|
<span class="plain">FlexFree(block-->BLK_NEXT);</span>
|
||
|
<span class="plain">block-->BLK_NEXT = NULL;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">return;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP13"></a><b>§13. Block Size. </b>These two routines are provided for the use of the <code class="display"><span class="extract">BlockValue</span></code> routines
|
||
|
only.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ FlexSize txb bsize n; ! Size of an individual block, including header</span>
|
||
|
<span class="plain">if (txb == 0) return 0;</span>
|
||
|
<span class="plain">for (bsize=1: n<txb->BLK_HEADER_N: bsize=bsize*2) n++;</span>
|
||
|
<span class="plain">return bsize;</span>
|
||
|
<span class="plain">];</span>
|
||
|
|
||
|
<span class="plain">[ FlexTotalSize txb size_in_bytes; ! Combined size of multiple-blocks for a value</span>
|
||
|
<span class="plain">if (txb == 0) return 0;</span>
|
||
|
<span class="plain">if ((txb->BLK_HEADER_FLAGS) & BLK_FLAG_MULTIPLE == 0)</span>
|
||
|
<span class="plain">return FlexSize(txb) - BLK_DATA_OFFSET;</span>
|
||
|
<span class="plain">for (:txb~=NULL:txb=txb-->BLK_NEXT) {</span>
|
||
|
<span class="plain">size_in_bytes = size_in_bytes + FlexSize(txb) - BLK_DATA_MULTI_OFFSET;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">return size_in_bytes;</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<p class="inwebparagraph"><a id="SP14"></a><b>§14. Debugging Routines. </b>These two routines are purely for testing the above code.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="display">
|
||
|
<span class="plain">[ FlexDebug txb n k i bsize tot dtot kov;</span>
|
||
|
<span class="plain">if (txb == 0) "Block never created.";</span>
|
||
|
<span class="plain">kov = txb-->BLK_HEADER_KOV;</span>
|
||
|
<span class="plain">print "Block ", txb, " (kov ", kov, "): ";</span>
|
||
|
<span class="plain">for (:txb~=NULL:txb = txb-->BLK_NEXT) {</span>
|
||
|
<span class="plain">if (k++ == 100) " ... and so on.";</span>
|
||
|
<span class="plain">if (txb-->BLK_HEADER_KOV ~= kov)</span>
|
||
|
<span class="plain">print "*Wrong kov=", txb-->BLK_HEADER_KOV, "* ";</span>
|
||
|
<span class="plain">n = txb->BLK_HEADER_N;</span>
|
||
|
<span class="plain">for (bsize=1:n>0:n--) bsize=bsize*2;</span>
|
||
|
<span class="plain">i = bsize - BLK_DATA_OFFSET;</span>
|
||
|
<span class="plain">dtot = dtot+i;</span>
|
||
|
<span class="plain">tot = tot+bsize;</span>
|
||
|
<span class="plain">print txb, "(", bsize, ") > ";</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">print dtot, " data in ", tot, " bytes^";</span>
|
||
|
<span class="plain">];</span>
|
||
|
|
||
|
<span class="plain">[ FlexDebugDecomposition from to txb pf;</span>
|
||
|
<span class="plain">if (to==0) to = NULL;</span>
|
||
|
<span class="plain">for (txb=from:((txb~=to) && (txb~=NULL)):txb=txb-->BLK_NEXT) {</span>
|
||
|
<span class="plain">if (pf) print "+";</span>
|
||
|
<span class="plain">print FlexSize(txb);</span>
|
||
|
<span class="plain">pf = true;</span>
|
||
|
<span class="plain">}</span>
|
||
|
<span class="plain">print "^";</span>
|
||
|
<span class="plain">];</span>
|
||
|
</pre>
|
||
|
|
||
|
<p class="inwebparagraph"></p>
|
||
|
|
||
|
<hr class="tocbar">
|
||
|
<ul class="toc"><li><a href="S-ft.html">Back to 'FileIO Template'</a></li><li><a href="S-gt.html">Continue with 'Glulx Template'</a></li></ul><hr class="tocbar">
|
||
|
<!--End of weave-->
|
||
|
</body>
|
||
|
</html>
|
||
|
|