/* vi:ts=4:sw=4 * * VIM - Vi IMproved * * Code Contributions By: Bram Moolenaar mool@oce.nl * Tim Thompson twitch!tjt * Tony Andrews onecom!wldrdg!tony * G. R. (Fred) Walter watmath!watcgl!grwalter */ /* * storage.c: allocation of lines and management of them * * part 1: storage allocation for the lines and blocks of the current file * part 2: managing of the pointer blocks */ #include "vim.h" #include "globals.h" #include "proto.h" /*************************************************************************** * part 1: storage allocation for the lines and blocks of the current file * ***************************************************************************/ /* * Memory is allocated in relatively large blocks. These blocks are linked * in the allocated block list, headed by block_head. They are all freed * when abandoning a file, so we don't have to free every single line. The * list is kept sorted on memory address. * block_alloc() allocates a block. * m_blockfree() frees all blocks. * * The available chunks of memory are kept in free chunk lists. There is * one free list for each block of allocated memory. The list is kept sorted * on memory address. * alloc_line() gets a chunk from the free lists. * free_line() returns a chunk to the free lists. * m_search points to the chunk before the chunk that was freed/allocated the * last time. * mb_current points to the b_head where m_search points into the free list. * * * block_head /---> block #1 /---> block #2 * mb_next ---/ mb_next ---/ mb_next ---> NULL * mb_info mb_info mb_info * | | | * V V V * NULL free chunk #1.1 free chunk #2.1 * | | * V V * free chunk #1.2 NULL * | * V * NULL * * When a single free chunk list would have been used, it could take a lot * of time in free_line() to find the correct place to insert a chunk in the * free list. The single free list would become very long when many lines are * changed (e.g. with :%s/^M$//). */ /* * this blocksize is used when allocating new lines * When reading files larger blocks are used (see fileio.c) * on the Amiga the blocksize must not be a multiple of 256 * with MS-Dos the blocksize must be larger than 255 * For Unix it does not really matter */ #define MEMBLOCKSIZE 2044 typedef struct m_info info_t; /* * There are two types of in-use memory chunks: * 1. those that are allocated by readfile(). These are always preceded * by a NUL character and end in a NUL character. The chunk must not * contain other NUL characters. The preceding NUL is used to * determine the chunk type. The ending NUL is used to determine the * end of the chunk. The preceding NUL is not part of the chunk, the * ending NUL is. * 2. the other chunks have been allocated with alloc_line(). They are * preceded by a non-NUL character. This is used to determine the chunk * type. The non-NUL may be part of a size field or may be an extra 0xff * byte. The chunk always ends in a NUL character and may also contain * a NUL character. The size field contains the size of the chunk, * including the size field. The trailing NUL may be used by a possibly * follwing type 1 chunk. The preceding size, the optional 0xff and the * trailing NUL are all part of the chunk. * * When the chunk is not in-use it is preceded with the m_info structure. * The m_next field links it in one of the free chunk lists. It must still * end in a NUL byte, because it may be followed by a type 1 chunk! * * When in-use we must make sure there is a non-NUL byte in front of type * 2 chunks. * * On the Amiga this means that the size must not be a multiple of 256. * This is done by multiplying the size by 2 and adding one. * * On MS-DOS the size must be larger than 255. This is done by adding 256 * to the size. * * On Unix systems an extra 0xff byte is added. This costs 4 bytes because * pointers must be kept long-aligned. * * On most unix systems structures have to be longword (32 or 64 bit) aligned. * On most other systems they are short (16 bit) aligned. */ #if defined(UNIX) || defined(WIN32) # define ALIGN_LONG /* longword alignment and use filler byte */ # define ALIGN_SIZE (sizeof(long)) # define ALIGN_MASK (ALIGN_SIZE - 1) #else # ifdef AMIGA # define LOWBYTE /* size must not be multiple of 256 */ # else # ifdef MSDOS # define HIGHBYTE /* size must be larger than 255 */ # else you must define something! # endif # endif #endif /* * stucture used to link chunks in one of the free chunk lists. */ struct m_info { #ifdef ALIGN_LONG u_long m_size; /* size of the chunk (including m_info) */ #else u_short m_size; /* size of the chunk (including m_info) */ #endif info_t *m_next; /* pointer to next free chunk in the list */ }; #ifdef ALIGN_LONG /* size of m_size + room for 0xff byte */ # define M_OFFSET (sizeof(u_long) * 2) #else /* size of m_size */ # define M_OFFSET (sizeof(u_short)) #endif /* * structure used to link blocks in the list of allocated blocks. */ struct m_block { struct m_block *mb_next; /* pointer to next allocated block */ info_t mb_info; /* head of free chuck list for this block */ }; static struct m_block block_head = {NULL, {0, NULL}}; /* head of allocated memory block list */ static info_t *m_search = NULL; /* pointer to chunk before previously allocated/freed chunk */ static struct m_block *mb_current = NULL; /* block where m_search points in */ /* * Allocate a block of memory and link it in the allocated block list. */ char * m_blockalloc(size, message) u_long size; int message; { struct m_block *p; struct m_block *mp, *next; p = (struct m_block *)lalloc(size + sizeof(struct m_block), message); if (p != NULL) { /* Insert the block into the allocated block list, keeping it sorted on address. */ for (mp = &block_head; (next = mp->mb_next) != NULL && next < p; mp = next) ; p->mb_next = next; /* link in block list */ mp->mb_next = p; p->mb_info.m_next = NULL; /* clear free list */ p->mb_info.m_size = 0; mb_current = p; /* remember current block */ m_search = NULL; p++; /* return usable memory */ } return (char *)p; } /* * free all allocated memory blocks */ void m_blockfree() { struct m_block *p, *np; for (p = block_head.mb_next; p != NULL; p = np) { np = p->mb_next; free((char *)p); } block_head.mb_next = NULL; m_search = NULL; mb_current = NULL; } /* * Free a chunk of memory which was * 1. inserted with readfile(); these are preceded with a NUL byte * 2. allocated with alloc_line(); these are preceded with a non-NUL byte * Insert the chunk into the correct free list, keeping it sorted on address. */ void free_line(ptr) char *ptr; { register info_t *next; register info_t *prev, *curr; register info_t *mp; long len; struct m_block *nextb; if (ptr == NULL || ptr == IObuff) return; /* illegal address can happen in out-of-memory situations */ if (*(ptr - 1) == NUL) /* type 1 chunk: no size field */ { #ifdef ALIGN_LONG /* use longword alignment */ long c; len = strlen(ptr) + 1; if ((c = ((long)ptr & ALIGN_MASK)) != 0) /* lose some bytes */ { c = ALIGN_SIZE - c; ptr += c; len -= c; } #else /* use short (16 bit) alignment */ len = strlen(ptr) + 1; if (len > 0xff00) /* can't handle this large (cannot happen?) */ len = 0xff00; if ((long)ptr & 1) /* lose a byte */ { ++ptr; --len; } #endif /* ALIGN_LONG */ /* we must be able to store size, pointer and a trailing NUL */ /* otherwise we can't fit it in the free list */ if (len <= (long)sizeof(info_t)) return; /* these bytes are not used until you quit the file */ mp = (info_t *)ptr; mp->m_size = len; } #ifdef ALIGN_LONG else if ((*(ptr - 1) & 0xff) == 0xff) /* type 2 chunk: has size field */ { mp = (info_t *)(ptr - M_OFFSET); } else /* illegal situation: there is no NUL or 0xff in front of the line */ { emsg("Illegal chunk"); return; } #endif #ifdef LOWBYTE else /* type 2 chunk: has size field */ { mp = (info_t *)(ptr - M_OFFSET); mp->m_size >>= 1; } #endif #ifdef HIGHBYTE else /* type 2 chunk: has size field */ { mp = (info_t *)(ptr - M_OFFSET); mp->m_size -= 256; } #endif /* find block where chunk could be a part off */ /* if we change mb_current, m_search is set to NULL */ if (mb_current == NULL || mp < (info_t *)mb_current) { mb_current = block_head.mb_next; m_search = NULL; } if ((nextb = mb_current->mb_next) != NULL && (info_t *)nextb < mp) { mb_current = nextb; m_search = NULL; } while ((nextb = mb_current->mb_next) != NULL && (info_t *)nextb < mp) mb_current = nextb; curr = NULL; /* if mp is smaller than m_search->m_next we go to the start of the free list */ if (m_search == NULL || mp < (m_search->m_next)) next = &(mb_current->mb_info); else next = m_search; /* * The following loop is executed very often. * Therefore it has been optimized at the cost of readability. * Keep it fast! */ #ifdef SLOW_BUT_EASY_TO_READ do { prev = curr; curr = next; next = next->m_next; } while (mp > next && next != NULL); #else do /* first, middle, last */ { prev = next->m_next; /* curr, next, prev */ if (prev == NULL || mp <= prev) { prev = curr; curr = next; next = next->m_next; break; } curr = prev->m_next; /* next, prev, curr */ if (curr == NULL || mp <= curr) { prev = next; curr = prev->m_next; next = curr->m_next; break; } next = curr->m_next; /* prev, curr, next */ } while (mp > next && next != NULL); #endif /* if *mp and *next are concatenated, join them into one chunk */ if ((char *)mp + mp->m_size == (char *)next) { mp->m_size += next->m_size; mp->m_next = next->m_next; } else mp->m_next = next; /* if *curr and *mp are concatenated, join them */ if (prev != NULL && (char *)curr + curr->m_size == (char *)mp) { curr->m_size += mp->m_size; curr->m_next = mp->m_next; m_search = prev; } else { curr->m_next = mp; m_search = curr; /* put m_search before freed chunk */ } } /* * Allocate and initialize a new line structure with room for at least * 'size' characters. */ char * alloc_line(size) register unsigned size; { register info_t *mp, *mprev, *mp2; struct m_block *mbp; int size_align; /* * Add room for size field, optional 0xff byte and trailing NUL byte. * Adjust for minimal size (must be able to store info_t * plus a trailing NUL, so the chunk can be released again) */ size += M_OFFSET + 1; if (size < sizeof(info_t) + 1) size = sizeof(info_t) + 1; /* * round size up for alignment */ #ifdef ALIGN_LONG /* use longword alignment */ size_align = (size + ALIGN_MASK) & ~ALIGN_MASK; #else /* ALIGN_LONG */ /* use short (16 bit) alignment */ size_align = (size + 1) & ~1; #endif /* ALIGN_LONG */ /* if m_search is NULL (uninitialized free list) we start at block_head */ if (mb_current == NULL || m_search == NULL) { mb_current = &block_head; m_search = &(block_head.mb_info); } /* search for space in free list */ mprev = m_search; mbp = mb_current; mp = m_search->m_next; if (mp == NULL) { if (mbp->mb_next) mbp = mbp->mb_next; else mbp = &block_head; mp = m_search = &(mbp->mb_info); } while (mp->m_size < size) { if (mp == m_search) /* back where we started in free chunk list */ { if (mbp->mb_next) mbp = mbp->mb_next; else mbp = &block_head; mp = m_search = &(mbp->mb_info); if (mbp == mb_current) /* back where we started in block list */ { int n = (size_align > (MEMBLOCKSIZE / 4) ? size_align : MEMBLOCKSIZE); mp = (info_t *)m_blockalloc((u_long)n, TRUE); if (mp == NULL) return (NULL); #ifdef HIGHBYTE mp->m_size = n + 256; #endif #ifdef LOWBYTE mp->m_size = (n << 1) + 1; #endif #ifdef ALIGN_LONG mp->m_size = n; *((u_char *)mp + M_OFFSET - 1) = 0xff; #endif free_line((char *)mp + M_OFFSET); mp = m_search; mbp = mb_current; } } mprev = mp; if ((mp = mp->m_next) == NULL) /* at end of the list */ mp = &(mbp->mb_info); /* wrap around to begin */ } /* if the chunk we found is large enough, split it up in two */ if ((long)mp->m_size - size_align >= (long)(sizeof(info_t) + 1)) { mp2 = (info_t *)((char *)mp + size_align); mp2->m_size = mp->m_size - size_align; mp2->m_next = mp->m_next; mprev->m_next = mp2; mp->m_size = size_align; } else /* remove *mp from the free list */ { mprev->m_next = mp->m_next; } m_search = mprev; mb_current = mbp; #ifdef HIGHBYTE mp->m_size += 256; #endif #ifdef LOWBYTE mp->m_size = (mp->m_size << 1) + 1; #endif mp = (info_t *)((char *)mp + M_OFFSET); #ifdef ALIGN_LONG *((u_char *)mp - 1) = 0xff; /* mark type 2 chunk */ #endif *(char *)mp = NUL; /* set the first byte to NUL */ return ((char *)mp); } /* * save_line(): allocate memory with alloc_line() and copy the * string 'src' into it. */ char * save_line(src) register char *src; { register char *dst; register unsigned len; len = strlen(src); if ((dst = alloc_line(len)) != NULL) memmove(dst, src, (size_t)(len + 1)); return (dst); } /****************************************** * part 2: managing of the pointer blocks * ******************************************/ typedef struct block block_t; #ifdef BLOCK_SIZE # undef BLOCK_SIZE /* for Linux: is in limits.h */ #endif #define BLOCK_SIZE 40 struct block { char *b_ptr[BLOCK_SIZE]; /* pointers to the lines */ char b_flags[BLOCK_SIZE]; /* see below */ u_short b_count; /* current number of pointers in b_ptr */ block_t *b_next; /* pointer to next block */ block_t *b_prev; /* pointer to previous block */ }; #define B_MARKED 0x01 /* mark for :global command */ static block_t *first_block; /* pointer to first block in block list */ static block_t *last_block; /* pointer to last block in block list */ static block_t *curr_block; /* block used by nr2ptr */ static linenr_t curr_count; /* first line number of block curr_block */ static linenr_t curr_count_max; /* curr_count + curr_block->b_count */ static block_t *alloc_block __ARGS((void)); static block_t * alloc_block() { block_t *p; p = (block_t *)(alloc_line((unsigned)sizeof(block_t))); if (p != NULL) { memset((char *)p, 0, sizeof(block_t)); } return (p); } /* * filealloc() - construct an initial empty file buffer */ void filealloc() { first_block = last_block = alloc_block(); if (first_block == NULL || (first_block->b_ptr[0] = alloc_line(0)) == NULL) getout(1); first_block->b_count = 1; Curpos.lnum = 1; Curswant = Curpos.col = 0; Topline = 1; Botline = 2; line_count = 1; curr_count = 0; clrallmarks(); clrtags(); UNCHANGED; } /* * freeall() - free the current buffer * * Free all lines in the current buffer. */ void freeall() { m_blockfree(); line_count = 0; s_ins(0, 0, TRUE); /* invalidate Line arrays */ u_clearall(); } /* * Get the pointer to the line 'nr'. * This function is used a lot for sequential access (writeit, search), * so that is what it is optimized for. */ char * nr2ptr(nr) register linenr_t nr; { register linenr_t count = curr_count; /* * if we don't have a current block or the line is not in the current block, * make the block containing the line the current block. */ if (count == 0 || nr >= curr_count_max || nr < count) { register block_t *bp = curr_block; if (nr < 1 || nr > line_count) { emsg("nr2ptr: illegal nr"); return (IObuff); /* always return a valid ptr */ } /* * three ways to find the pointer: * 1. first pointer in the next block (fast for sequential access) * 2. search forward * 3. search backward */ if (count && nr == count + bp->b_count) /* in next block */ { count = nr; bp = bp->b_next; } else if (nr <= (count + line_count) / 2 || (nr <= count && nr <= count / 2)) { /* search forward */ if (nr < count || count == 0) { count = 1; bp = first_block; } while (bp != NULL) { count += bp->b_count; if (nr < count) { count -= bp->b_count; break; } bp = bp->b_next; } } else { /* search backward */ if (nr < count) bp = bp->b_prev; else { bp = last_block; count = line_count + 1; } while (bp != NULL) { count -= bp->b_count; if (nr >= count) break; bp = bp->b_prev; } } if (bp == NULL) { emsg("nr2ptr: strorage corrupt"); curr_count = 0; return (IObuff); } curr_count = count; curr_count_max = count + bp->b_count; curr_block = bp; } return (curr_block->b_ptr[nr - count]); } /* * pos2ptr: get pointer to position 'pos' */ char * pos2ptr(pos) FPOS *pos; { return (nr2ptr(pos->lnum) + pos->col); } char * Curpos2ptr() { return (nr2ptr(Curpos.lnum) + Curpos.col); } /* * set the B_MARKED flag for line 'lnum' */ void setmarked(lnum) linenr_t lnum; { nr2ptr(lnum); curr_block->b_flags[lnum - curr_count] |= B_MARKED; } /* * find the first line with its B_MARKED flag set */ linenr_t firstmarked() { register block_t *bp; register linenr_t lnum; register u_short i; for (bp = first_block, lnum = 1; bp != NULL; bp = bp->b_next) for (i = 0; i < bp->b_count; ++i, ++lnum) if (bp->b_flags[i] & B_MARKED) { bp->b_flags[i] &= ~B_MARKED; return lnum; } return (linenr_t) 0; } /* * clear all B_MARKED flags */ void clearmarked() { register block_t *bp; register int i; for (bp = first_block; bp != NULL; bp = bp->b_next) for (i = bp->b_count; --i >= 0; ) bp->b_flags[i] &= ~B_MARKED; } /* * a pointer to a line is converted into a line number * we start at line number 'start' * this is a bit slow, but it is used for marks and undo only */ linenr_t ptr2nr(ptr, start) char *ptr; linenr_t start; { block_t *bp; register linenr_t nr; register char **pp; register int i; if (ptr == NULL) return (linenr_t)0; if (start == 0) start = 1; nr2ptr(start); /* set curr_block and curr_count */ for (nr = curr_count, bp = curr_block; bp != NULL; bp = bp->b_next) for (pp = bp->b_ptr, i = bp->b_count; --i >= 0; ++nr) if (*pp++ == ptr) return (nr); return (linenr_t)0; } /* * appendline: add a line * return TRUE when succesful */ int appendline(after, s) linenr_t after; char *s; { register block_t *bp; block_t *nbp; linenr_t count; register int i; if (s == NULL) /* don't insert NULL pointers! */ return FALSE; if (after == 0) /* insert in front of first line */ { bp = first_block; count = 1; if (bufempty()) /* simply replace dummy line */ { free_line(bp->b_ptr[0]); bp->b_ptr[0] = s; return TRUE; } curr_count = 0; /* curr_block will become invalid */ } else { (void)nr2ptr(after); /* find block */ bp = curr_block; count = curr_count; } ++line_count; i = bp->b_count; if (i < BLOCK_SIZE) /* there is place in the current block */ /* move ptrs one place forward to make space for new one */ { register char **pp; register char *fp; pp = &(bp->b_ptr[i]); fp = &(bp->b_flags[i]); for (i += count - after - 1; --i >= 0; --pp, --fp) { *pp = *(pp - 1); *fp = *(fp - 1); } *pp = s; *fp = 0; ++bp->b_count; ++curr_count_max; return TRUE; } /* need to allocate a new block */ nbp = alloc_block(); if (nbp == NULL) { --line_count; free_line(s); return FALSE; } /* put new block in linked list */ if (after == 0) /* put new block in front of linked list */ { bp->b_prev = nbp; nbp->b_next = bp; first_block = nbp; nbp->b_ptr[0] = s; nbp->b_count = 1; return TRUE; } /* insert new block in linked list after bp */ nbp->b_next = bp->b_next; bp->b_next = nbp; nbp->b_prev = bp; if (nbp->b_next == NULL) last_block = nbp; else nbp->b_next->b_prev = nbp; if (after - count + 1 == BLOCK_SIZE) /* put s in new block */ { nbp->b_ptr[0] = s; nbp->b_count = 1; return TRUE; } /* move some ptrs from full block to new block */ { register int j = 0; bp->b_count = after - count + 1; /* number of ptrs remaining */ i = BLOCK_SIZE - bp->b_count; /* number of ptrs to be moved */ nbp->b_count = i; while (--i >= 0) { j = bp->b_count + i; nbp->b_ptr[i] = bp->b_ptr[j]; nbp->b_flags[i] = bp->b_flags[j]; } bp->b_ptr[j] = s; bp->b_flags[j] = 0; ++bp->b_count; curr_count_max = curr_count + bp->b_count; } return TRUE; } /* * delsline: delete line from storage * * the line is turned over to the caller */ char * delsline(nr, delmarks) linenr_t nr; int delmarks; { char *ptr; register block_t *bp; register char **pp; register char *fp; register int i; if (nr < 1 || nr > line_count) { emsg("delsline: nr wrong"); return (alloc_line(0)); } ptr = nr2ptr(nr); if (delmarks) adjustmark(ptr, NULL); /* remove marks for this line (slow!) */ bp = curr_block; if (line_count == 1) /* don't delete the last line in the file */ { bp->b_ptr[0] = alloc_line(0); return (ptr); } --line_count; /* move the rest of the ptrs in this block one down */ pp = &(bp->b_ptr[nr - curr_count]); fp = &(bp->b_flags[nr - curr_count]); for (i = bp->b_count + curr_count - nr - 1; --i >= 0; ++pp, ++fp) { *pp = *(pp + 1); *fp = *(fp + 1); } if (--bp->b_count == 0) /* the block became empty, remove it from the list */ { if (bp->b_prev == NULL) first_block = bp->b_next; else bp->b_prev->b_next = bp->b_next; if (bp->b_next == NULL) last_block = bp->b_prev; else bp->b_next->b_prev = bp->b_prev; free_line((char *)bp); curr_count = 0; /* curr_block invalid */ } else --curr_count_max; return (ptr); } /* * replace the line "lnum" with the line "new". * return the old line (which should be freed by the caller) */ char * replaceline(lnum, new) linenr_t lnum; char *new; { char *old; old = nr2ptr(lnum); if (new == NULL || curr_count == 0) /* we don't want NULL pointers in the list */ return (alloc_line(0)); /* be friendly to the caller */ curr_block->b_ptr[lnum - curr_count] = new; curr_block->b_flags[lnum - curr_count] = 0; adjustmark(old, new); return (old); } /* * canincrease(n) - returns TRUE if the current line can be increased 'n' * bytes * * This routine returns immediately if the requested space is available. If not, * it attempts to allocate the space and adjust the data structures * accordingly. If everything fails it returns FALSE. */ int canincrease(n) int n; { register char *old; register char *new; /* pointer to new space */ register unsigned newsize; old = nr2ptr(Curpos.lnum); newsize = strlen(old) + n; new = alloc_line(newsize); if (new == NULL) return FALSE; strcpy(new, old); adjustmark(old, new); free_line(old); curr_block->b_ptr[Curpos.lnum - curr_count] = new; curr_block->b_flags[Curpos.lnum - curr_count] = 0; return TRUE; }