Appendix A: A Simple Allocator
dynamic-array-pop

Write da_pop -- remove and return the last element:

fn da_pop(da: i64) i64 {
    var len: i64 = load64(da + 8);
    if (len == 0) { return 0; }
    len -= 1;
    store64(da + 8, len);
    return load64(load64(da) + len * 8);
}

Test:

    // push 10, 20, 30; pop gives 30, 20, 10
    check(
        \\ ... allocator and dynamic array functions ...
        \\var arr: i64 = da_new();
        \\da_push(arr, 10);
        \\da_push(arr, 20);
        \\da_push(arr, 30);
        \\var a: i64 = da_pop(arr);
        \\var b: i64 = da_pop(arr);
        \\a * 100 + b
    , 3020);  // 30 * 100 + 20

### Coalescing

Our allocator has a problem: fragmentation. If you allocate and free many small blocks, the free list fills up with tiny pieces that can't satisfy larger requests -- even though the total free memory is plenty.

The fix is coalescing: when freeing a block, check if the block immediately after it in memory is also free. If so, merge them into one larger block.