I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

Flex.i6t

Flex contents

Blocks.

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.

A "block" is a continuous range of 2n 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.

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 2127 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.

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 BlockValue system.

The data section of a block begins at the byte offset BLK_DATA_OFFSET from the address of the block: but see below for how multiple-blocks behave differently.

These definitions must not be altered without making matching changes to the compiler.

44Constant BLK_HEADER_N = 0; 45Constant BLK_HEADER_FLAGS = 1; 46Constant BLK_FLAG_MULTIPLE = $$00000001; 47Constant BLK_FLAG_16_BIT = $$00000010; 48Constant BLK_FLAG_WORD = $$00000100; 49Constant BLK_FLAG_RESIDENT = $$00001000; 50Constant BLK_FLAG_TRUNCMULT = $$00010000; 51Constant BLK_HEADER_KOV = 1; 52Constant BLK_HEADER_RCOUNT = 2; 53 54Constant BLK_DATA_OFFSET = 3*WORDSIZE;

Multiple Blocks.

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

"But now I worship a celestiall Sunne"

might be stored in a list of blocks like so:

NULL < – BN: "But now I wor" <--> BN2: "ship a celestiall Sunne" --> NULL

Note that the unique pointer to BN2 is the one in the header of the BN 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.

A multiple-block is one whose flags byte contains the BLK_FLAG_MULTIPLE. 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 -->BLK_HEADER_KOV word, but that would be too slow. BLK_FLAG_MULTIPLE can never change for a currently allocated block, just as it can never change its KOV.

A multiple-block header is longer than that of an ordinary block, because it contains two extra words: -->BLK_NEXT is the next block in the doubly-linked list of blocks representing the current value, or NULL if this is the end; -->BLK_PREV is the previous block, or NULL 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 BLK_DATA_MULTI_OFFSET rather than BLK_DATA_OFFSET.

93Constant BLK_DATA_MULTI_OFFSET = BLK_DATA_OFFSET + 2*WORDSIZE; 94Constant BLK_NEXT 3; 95Constant BLK_PREV 4; 96 97! Constant BLKVALUE_TRACE = 1; ! Uncomment this for debugging purposes

The Heap.

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.

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 -->BLK_HEADER_KOV 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 -->BLK_NEXT and < – BLK_PREV.

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 HeapInitialise(). Thus the heap is full if and only if the -->BLK_NEXT of the head-free-block is NULL.

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:

(a) In the free blocks list, B-->BLK_NEXT is always an address after B; (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 2n where n is the largest integer such that 2n ≤ T.

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.

145Array Flex_Heap -> MEMORY_HEAP_SIZE + 16; ! Plus 16 to allow room for head-free-block

Initialisation.

To recap: the constant MEMORY_HEAP_SIZE has been predefined by the NI compiler, and is always itself a power of 2, say 2n. We therefore have 2n + 24 bytes available to us, and we format these as a free space list of two blocks: the 24-sized "head-free-block" described above followed by a 2n-sized block exactly containing the whole of the rest of the heap.

155[ HeapInitialise n bsize blk2; 156    blk2 = Flex_Heap + 16; 157    Flex_Heap->BLK_HEADER_N = 4; 158    Flex_Heap-->BLK_HEADER_KOV = 0; 159    Flex_Heap-->BLK_HEADER_RCOUNT = MAX_POSITIVE_NUMBER; 160    Flex_Heap->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE; 161    Flex_Heap-->BLK_NEXT = blk2; 162    Flex_Heap-->BLK_PREV = NULL; 163    for (bsize=1: bsize < MEMORY_HEAP_SIZE: bsize=bsize*2) n++; 164    blk2->BLK_HEADER_N = n; 165    blk2-->BLK_HEADER_KOV = 0; 166    blk2-->BLK_HEADER_RCOUNT = 0; 167    blk2->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE; 168    blk2-->BLK_NEXT = NULL; 169    blk2-->BLK_PREV = Flex_Heap; 170];

Net Free Space.

"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).

181[ HeapNetFreeSpace multiple txb asize; 182    for (txb=Flex_Heap-->BLK_NEXT: txb~=NULL: txb=txb-->BLK_NEXT) { 183        asize = asize + FlexSize(txb); 184        if (multiple) asize = asize - BLK_DATA_MULTI_OFFSET; 185        else asize = asize - BLK_DATA_OFFSET; 186    } 187    return asize; 188];

Make Space.

The following routine determines if there is enough free space to accommodate another size bytes of data, given that it has to be multiple-block data if the multiple 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 2m further bytes, where 2m is at least size 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.

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.

(The code below is a refinement on the original, suggested by Jesse McGrew, which handles non-multiple blocks better.)

210Constant SMALLEST_BLK_WORTH_ALLOCATING = 12; ! i.e. 2^12 = 4096 bytes 211 212[ HeapMakeSpace size multiple newblocksize newblock B n; 213    for (::) { 214        if (multiple) { 215            if (HeapNetFreeSpace(multiple) >= size) rtrue; 216        } else { 217            if (HeapLargestFreeBlock(0) >= size) rtrue; 218        } 219        newblocksize = 1; 220        for (n=0: (n<SMALLEST_BLK_WORTH_ALLOCATING) || (newblocksize<size): n++) 221            newblocksize = newblocksize*2; 222        while (newblocksize < size+16) newblocksize = newblocksize*2; 223        newblock = VM_AllocateMemory(newblocksize); 224        if (newblock == 0) rfalse; 225        newblock->BLK_HEADER_N = n; 226        newblock-->BLK_HEADER_KOV = 0; 227        newblock-->BLK_HEADER_RCOUNT = 0; 228        newblock->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE; 229        newblock-->BLK_NEXT = NULL; 230        newblock-->BLK_PREV = NULL; 231        for (B = Flex_Heap-->BLK_NEXT:B ~= NULL:B = B-->BLK_NEXT) 232            if (B-->BLK_NEXT == NULL) { 233                B-->BLK_NEXT = newblock; 234                newblock-->BLK_PREV = B; 235                jump Linked; 236            } 237        Flex_Heap-->BLK_NEXT = newblock; 238        newblock-->BLK_PREV = Flex_Heap; 239        .Linked; ; 240        #ifdef BLKVALUE_TRACE; 241        print "Increasing heap to free space map: "; FlexDebugDecomposition(Flex_Heap, 0); 242        #endif; 243    } 244    rtrue; 245]; 246 247[ HeapLargestFreeBlock multiple txb asize best; 248    best = 0; 249    for (txb=Flex_Heap-->BLK_NEXT: txb~=NULL: txb=txb-->BLK_NEXT) { 250        asize = FlexSize(txb); 251        if (multiple) asize = asize - BLK_DATA_MULTI_OFFSET; 252        else asize = asize - BLK_DATA_OFFSET; 253        if (asize > best) best = asize; 254    } 255    return best; 256]; 257 258[ HeapDebug full; 259    if (full) { 260        print "Managing a heap of initially ", MEMORY_HEAP_SIZE+16, " bytes.^"; 261        print HeapNetFreeSpace(false), " bytes currently free.^"; 262        print "Free space decomposition: "; FlexDebugDecomposition(Flex_Heap); 263        print "Free space map: "; FlexDebug(Flex_Heap); 264    } else { 265        print HeapNetFreeSpace(false), " of ", MEMORY_HEAP_SIZE+16, " bytes free.^"; 266    } 267];

Block Allocation.

Now for the Flex routines. Those with names ending in Internal 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.

The routine FlexAllocate(N, K, F) allocates a block with room for size net bytes of data, which will have kind of value K and with flags F. If the flags include BLK_FLAG_MULTIPLE, 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.

If it does succeed and return a nonzero address, then the caller must be able to guarantee that FlexFree will later be called, exactly once, on this address. In other words, FlexAllocate and FlexFree behave somewhat like C's malloc and free routines, with all the advantages and hazards that implies.

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 2n is available and we only need a block of size 2k where k<n then we break it up in memory as a sequence of blocks of size 2k, 2k, 2k+1, 2k+2, ..., 2n-1: note that the sum of these sizes is the 2n we started with. We then use the first block of size 2k. To continue the comparison with binary arithmetic, this is like a subtraction with repeated carries:

100000002 - 000010002 = 011110002

297[ FlexAllocate size kov flags 298    dsize n m free_block min_m max_m smallest_oversized_block secondhalf i hsize head tail; 299     300    if (HeapMakeSpace(size, flags & BLK_FLAG_MULTIPLE) == false) FlexError("ran out"); 301 302    ! Calculate the header size for a block of this KOV 303    if (flags & BLK_FLAG_MULTIPLE) hsize = BLK_DATA_MULTI_OFFSET; 304    else hsize = BLK_DATA_OFFSET; 305    ! Calculate the data size 306    n=0; for (dsize=1: ((dsize < hsize+size) || (n<3+(WORDSIZE/2))): dsize=dsize*2) n++; 307 308    ! Seek a free block closest to the correct size, but starting from the 309    ! block after the fixed head-free-block, which we can't touch 310    min_m = 10000; max_m = 0; 311    for (free_block = Flex_Heap-->BLK_NEXT: 312        free_block ~= NULL: 313        free_block = free_block-->BLK_NEXT) { 314        m = free_block->BLK_HEADER_N; 315        ! Current block the ideal size 316        if (m == n) jump CorrectSizeFound; 317        ! Current block too large: find the smallest which is larger than needed 318        if (m > n) { 319            if (min_m > m) { 320                min_m = m; 321                smallest_oversized_block = free_block; 322            } 323        } 324        ! Current block too small: find the largest which is smaller than needed 325        if (m < n) { 326            if (max_m < m) { 327                max_m = m; 328            } 329        } 330    } 331 332    if (min_m == 10000) { 333        ! Case I: No block is large enough to hold the entire size 334        if (flags & BLK_FLAG_MULTIPLE == 0) FlexError("too fragmented"); 335        ! Set dsize to the size in bytes if the largest block available 336        for (dsize=1: max_m > 0: dsize=dsize*2) max_m--; 337        ! Split as a head (dsize-hsize), which we can be sure fits into one block, 338        ! plus a tail (size-(dsize-hsize), which might be a list of blocks 339        head = FlexAllocate(dsize-hsize, kov, flags); 340        if (head == 0) FlexError("for head block not available"); 341        tail = FlexAllocate(size-(dsize-hsize), kov, flags); 342        if (tail == 0) FlexError("for tail block not available"); 343        head-->BLK_NEXT = tail; 344        tail-->BLK_PREV = head; 345        return head; 346    } 347 348    ! Case II: No block is the right size, but some exist which are too big 349    ! Set dsize to the size in bytes of the smallest oversized block 350    for (dsize=1,m=1: m<=min_m: dsize=dsize*2) m++; 351    free_block = smallest_oversized_block; 352    while (min_m > n) { 353        ! Repeatedly halve free_block at the front until the two smallest 354        ! fragments left are the correct size: then take the frontmost 355        dsize = dsize/2; 356        ! print "Halving size to ", dsize, "^"; 357        secondhalf = free_block + dsize; 358        secondhalf-->BLK_NEXT = free_block-->BLK_NEXT; 359        if (secondhalf-->BLK_NEXT ~= NULL) 360            (secondhalf-->BLK_NEXT)-->BLK_PREV = secondhalf; 361        secondhalf-->BLK_PREV = free_block; 362        free_block-->BLK_NEXT = secondhalf; 363        free_block->BLK_HEADER_N = (free_block->BLK_HEADER_N) - 1; 364        secondhalf->BLK_HEADER_N = free_block->BLK_HEADER_N; 365        secondhalf-->BLK_HEADER_KOV = free_block-->BLK_HEADER_KOV; 366        secondhalf-->BLK_HEADER_RCOUNT = 0; 367        secondhalf->BLK_HEADER_FLAGS = free_block->BLK_HEADER_FLAGS; 368        min_m--; 369    } 370     371    ! Once that is done, free_block points to a block which is exactly the 372    ! right size, so we can fall into... 373     374    ! Case III: There is a free block which has the correct size. 375    .CorrectSizeFound; 376    ! Delete the free block from the double linked list of free blocks: note 377    ! that it cannot be the head of this list, which is fixed 378    if (free_block-->BLK_NEXT == NULL) { 379        ! We remove final block, so previous is now final 380        (free_block-->BLK_PREV)-->BLK_NEXT = NULL; 381    } else { 382        ! We remove a middle block, so join previous to next 383        (free_block-->BLK_PREV)-->BLK_NEXT = free_block-->BLK_NEXT; 384        (free_block-->BLK_NEXT)-->BLK_PREV = free_block-->BLK_PREV; 385    } 386    free_block-->BLK_HEADER_KOV = KindAtomic(kov); 387    free_block-->BLK_HEADER_RCOUNT = 1; 388    free_block->BLK_HEADER_FLAGS = flags; 389    if (flags & BLK_FLAG_MULTIPLE) { 390        free_block-->BLK_NEXT = NULL; 391        free_block-->BLK_PREV = NULL; 392    } 393     394    ! Zero out the data bytes in the memory allocated 395    for (i=hsize:i<dsize:i++) free_block->i=0; 396    return free_block; 397];

Errors.

In the event that FlexAllocate returns 0, the caller may not be able to survive, so the following is provided as a standardised way to halt the virtual machine.

405[ FlexError reason; 406    print "*** Memory ", (string) reason, " ***^"; 407    RunTimeProblem(RTP_HEAPERROR); 408    @quit; 409];

Merging.

Given a free block block, find the maximal contiguous run of free blocks which contains it, and then call FlexRecutInternal to recut it to conform to invariant (b) above.

417[ FlexMergeInternal block first last pv nx; 418    first = block; last = block; 419    while (last-->BLK_NEXT == last+FlexSize(last)) 420        last = last-->BLK_NEXT; 421    while ((first-->BLK_PREV + FlexSize(first-->BLK_PREV) == first) && 422        (first-->BLK_PREV ~= Flex_Heap)) 423        first = first-->BLK_PREV; 424    pv = first-->BLK_PREV; 425    nx = last-->BLK_NEXT; 426    #ifdef BLKVALUE_TRACE; 427    print "Merging: "; FlexDebugDecomposition(pv-->BLK_NEXT, nx); print "^"; 428    #endif; 429    if (FlexRecutInternal(first, last)) { 430        #ifdef BLKVALUE_TRACE; 431        print " --> "; FlexDebugDecomposition(pv-->BLK_NEXT, nx); print "^"; 432        #endif; 433    } 434];

Recutting.

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 2m-sized chunk off the back end until it is all used up.

443[ FlexRecutInternal first last tsize backsize mfrom mto bnext backend n dsize fine_so_far; 444    if (first == last) rfalse; 445    mfrom = first; mto = last + FlexSize(last); 446    bnext = last-->BLK_NEXT; 447    fine_so_far = true; 448    for (:mto>mfrom: mto = mto - backsize) { 449        for (n=0, backsize=1: backsize*2 <= mto-mfrom: n++) backsize=backsize*2; 450        if ((fine_so_far) && (backsize == FlexSize(last))) { 451            bnext = last; last = last-->BLK_PREV; 452            bnext-->BLK_PREV = last; 453            last-->BLK_NEXT = bnext; 454            continue; 455        } 456        fine_so_far = false; ! From this point, "last" is meaningless 457        backend = mto - backsize; 458        backend->BLK_HEADER_N = n; 459        backend-->BLK_HEADER_KOV = 0; 460        backend-->BLK_HEADER_RCOUNT = 0; 461        backend->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE; 462        backend-->BLK_NEXT = bnext; 463        if (bnext ~= NULL) { 464            bnext-->BLK_PREV = backend; 465            bnext = backend; 466        } 467    } 468    if (fine_so_far) rfalse; 469    rtrue; 470];

Deallocation.

As noted above, FlexFree must be called exactly once on each nonzero pointer returned by FlexAllocate.

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 FlexFree. 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.)

Certain blocks outside the heap are marked as "resident" in memory, that is, are indestructible. This enables Inform to compile constant values.

492[ FlexFree block fromtxb ptxb; 493    if (block == 0) return; 494    if ((block->BLK_HEADER_FLAGS) & BLK_FLAG_RESIDENT) return; 495    if ((block->BLK_HEADER_N) & $80) return; ! not a flexible block at all 496    if ((block->BLK_HEADER_FLAGS) & BLK_FLAG_MULTIPLE) { 497        if (block-->BLK_PREV ~= NULL) (block-->BLK_PREV)-->BLK_NEXT = NULL; 498        fromtxb = block; 499        for (:(block-->BLK_NEXT)~=NULL:block = block-->BLK_NEXT) ; 500        while (block ~= fromtxb) { 501            ptxb = block-->BLK_PREV; FlexFreeSingleBlockInternal(block); block = ptxb; 502        } 503    } 504    FlexFreeSingleBlockInternal(block); 505]; 506 507[ FlexFreeSingleBlockInternal block free nx; 508    block-->BLK_HEADER_KOV = 0; 509    block-->BLK_HEADER_RCOUNT = 0; 510    block->BLK_HEADER_FLAGS = BLK_FLAG_MULTIPLE; 511    for (free = Flex_Heap:free ~= NULL:free = free-->BLK_NEXT) { 512        nx = free-->BLK_NEXT; 513        if (nx == NULL) { 514            free-->BLK_NEXT = block; 515            block-->BLK_PREV = free; 516            block-->BLK_NEXT = NULL; 517            FlexMergeInternal(block); 518            return; 519        } 520        if (UnsignedCompare(nx, block) == 1) { 521            free-->BLK_NEXT = block; 522            block-->BLK_PREV = free; 523            block-->BLK_NEXT = nx; 524            nx-->BLK_PREV = block; 525            FlexMergeInternal(block); 526            return; 527        } 528    } 529];

Resizing.

A block which has been allocated, but not yet freed, can sometimes have its data capacity changed by FlexResize.

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.

545[ FlexResize block req newsize dsize newblk kov n i otxb flags; 546    if (block == 0) FlexError("failed resizing null block"); 547    kov = block-->BLK_HEADER_KOV; 548    flags = block->BLK_HEADER_FLAGS; 549    if (flags & BLK_FLAG_MULTIPLE == 0) FlexError("failed resizing inextensible block"); 550    otxb = block; 551    newsize = req; 552    for (:: block = block-->BLK_NEXT) { 553        n = block->BLK_HEADER_N; 554        for (dsize=1: n>0: n--) dsize = dsize*2; 555        i = dsize - BLK_DATA_MULTI_OFFSET; 556        newsize = newsize - i; 557        if (newsize > 0) { 558            if (block-->BLK_NEXT ~= NULL) continue; 559            newblk = FlexAllocate(newsize, kov, flags); 560            if (newblk == 0) rfalse; 561            block-->BLK_NEXT = newblk; 562            newblk-->BLK_PREV = block; 563            return; 564        } 565        if (block-->BLK_NEXT ~= NULL) { 566            FlexFree(block-->BLK_NEXT); 567            block-->BLK_NEXT = NULL; 568        } 569        return; 570    } 571];

Block Size.

These two routines are provided for the use of the BlockValue routines only.

578[ FlexSize txb bsize n; ! Size of an individual block, including header 579    if (txb == 0) return 0; 580    for (bsize=1: n<txb->BLK_HEADER_N: bsize=bsize*2) n++; 581    return bsize; 582]; 583 584[ FlexTotalSize txb size_in_bytes; ! Combined size of multiple-blocks for a value 585    if (txb == 0) return 0; 586    if ((txb->BLK_HEADER_FLAGS) & BLK_FLAG_MULTIPLE == 0) 587        return FlexSize(txb) - BLK_DATA_OFFSET; 588    for (:txb~=NULL:txb=txb-->BLK_NEXT) { 589        size_in_bytes = size_in_bytes + FlexSize(txb) - BLK_DATA_MULTI_OFFSET; 590    } 591    return size_in_bytes; 592];

Debugging Routines.

These two routines are purely for testing the above code.

598[ FlexDebug txb n k i bsize tot dtot kov; 599    if (txb == 0) "Block never created."; 600    kov = txb-->BLK_HEADER_KOV; 601    print "Block ", txb, " (kov ", kov, "): "; 602    for (:txb~=NULL:txb = txb-->BLK_NEXT) { 603        if (k++ == 100) " ... and so on."; 604        if (txb-->BLK_HEADER_KOV ~= kov) 605            print "*Wrong kov=", txb-->BLK_HEADER_KOV, "* "; 606        n = txb->BLK_HEADER_N; 607        for (bsize=1:n>0:n--) bsize=bsize*2; 608        i = bsize - BLK_DATA_OFFSET; 609        dtot = dtot+i; 610        tot = tot+bsize; 611        print txb, "(", bsize, ") > "; 612    } 613    print dtot, " data in ", tot, " bytes^"; 614]; 615 616[ FlexDebugDecomposition from to txb pf; 617    if (to==0) to = NULL; 618    for (txb=from:(txb~=to) && (txb~=NULL):txb=txb-->BLK_NEXT) { 619        if (pf) print "+"; 620        print FlexSize(txb); 621        pf = true; 622    } 623    print "^"; 624];