Index: firmware/buflib.c =================================================================== --- firmware/buflib.c (revision 31089) +++ firmware/buflib.c (working copy) @@ -237,11 +237,23 @@ { BDEBUGF("%s(): Compacting!\n", __func__); union buflib_data *block, - *first_free = find_first_free(ctx); + *hole = NULL; int shift = 0, len; /* Store the results of attempting to shrink the handle table */ bool ret = handle_table_shrink(ctx); - for(block = first_free; block < ctx->alloc_end; block += len) + /* compaction has basically two modes of operation: + * 1) the buffer is nicely movable: In this mode, blocks can be simply + * moved towards the beginning. Free blocks add to a shift value, + * which is the amount to move. + * 2) the buffer contains unmovable blocks: unmovable blocks create + * holes and reset shift. Once a hole is found, we're trying to fill + * holes first, moving by shift is the fallback. As the shift is reset, + * this effectively splits the buffer into portions of movable blocks. + * This mode cannot be used if no holes are found yet as it only works + * when it moves blocks across the portions. On the other side, + * moving by shift only works within the same portion + * For simplicity only 1 hole at a time is considered */ + for(block = find_first_free(ctx); block < ctx->alloc_end; block += len) { len = block->val; /* This block is free, add its length to the shift value */ @@ -252,43 +264,49 @@ continue; } /* attempt to fill any hole */ - if (-first_free->val >= block->val) + else if (hole && -hole->val >= len) { - intptr_t size = -first_free->val; - union buflib_data* next_block = block + block->val; - if (move_block(ctx, block, first_free - block)) + if (move_block(ctx, block, hole - block)) { - /* moving was successful. Move alloc_end down if necessary */ - if (ctx->alloc_end == next_block) - ctx->alloc_end = block; - /* Mark the block behind the just moved as free - * be careful to not overwrite an existing block */ - if (size != block->val) + /* Move was successful, and the former location is now + * free. The destination is always before a previous + * unmovable block, shift is always increased. */ + shift -= len; + /* Reduce the size of the hole accordingly + * but be careful to not overwrite an existing block */ + intptr_t hlen = -hole->val; + if (hlen != len) { - first_free += block->val; - first_free->val = block->val - size; /* negative */ + hole += len; + hole->val = len - hlen; /* negative */ } + else /* hole closed */ + hole = NULL; continue; } + /* else failure to move handled at bottom of loop */ } /* attempt move the allocation by shift */ - if (shift) + else if (shift) { - /* failing to move creates a hole, - * therefore mark this block as not allocated */ - union buflib_data* target_block = block + shift; - if (!move_block(ctx, block, shift)) - { - target_block->val = shift; /* this is a hole */ - shift = 0; - } - else - { /* need to update the next free block, since the above hole - * handling might make shift 0 before alloc_end is reached */ - union buflib_data* new_free = target_block + target_block->val; - new_free->val = shift; - } + if (move_block(ctx, block, shift)) + continue; + /* else failure to move handled at bottom of loop */ } + else /* block has no potential destination */ + continue; + + /* move failed */ + if (shift) { + /* free space before unmovable block becomes a hole */ + union buflib_data* new_hole = block + shift; + new_hole->val = shift; + + /* always fill the most recent hole */ + hole = new_hole; + + shift = 0; + } } /* Move the end-of-allocation mark, and return true if any new space has * been freed.