/* Marker vectors. Copyright (C) 2025 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU Emacs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Emacs. If not, see . */ /* A marker vector is used to hold the markers of a buffer. The vector is a normal Lisp vector that consists of a header and a number of entries for each marker. A Lisp vector is used because the vector references markers "weakly", and that's what easy for igc. +------+-----------+---------+---------+--------------+ | FREE | MAX_ENTRY | entry 0 | entry 1 | ... | +------+-----------+---------+---------+--------------+ |<----- header --->| Entries consist of 2 vector slots MARKER and CHARPOS. MARKER holds a marker, if the entry is in use. CHARPOS is not yet used. (The idea is to move the positions from Lisp_Marker here, which speeds up adjusting positions when the text changes.) FREE is the array index of the start of the next free entry in the marker vector. Free entries form a singly-linked list using the MARKER field of entries. MAX_ENTRY is the largest index ever used to store a marker. This is used to (supposedly) speed up iteration over the marker vector, with the assumption that there might be a tail of slots in the marker vector that is never used. Or, IOW, that we over-allocate room in the marker vector. Lisp_Marker objects contain the index under which they are stored in the marker vector. The use of a free list gives O(1) for adding a marker. The index stored in the Lisp_Marker provides O(1) deletion of a marker from the markers of a buffer. Iteration over marker vectors is done by iterating over all slots of the vector that can contain markers, skipping those that don't. Iteration over markers is O(N) where N is the size of the marker vector. This could be improved to N being the number of live markers by putting marker entries in a doubly-linked list. The downside is that iteration then might access the marker vector slots in an unpredictable order, while the current method scans the vector sequentially which should be fast. */ #include #include "lisp.h" #include "buffer.h" #include "text-index.h" #include "marker-vector.h" #ifdef HAVE_MPS #include "igc.h" #endif /* Number of entries to allocate initially. */ #define MARKER_VECTOR_INITIAL_ENTRIES 20 /* Access fields of an entry E of marker vector V as lvalues. */ #define MARKER(v, e) (v)->contents[(e) + MARKER_VECTOR_OFFSET_MARKER] #define CHARPOS(v, e) (v)->contents[(e) + MARKER_VECTOR_OFFSET_CHARPOS] /* Index of next free entry in the header of a marker vector. This must use the marker field so that putting an entry on the free list implicitly sets the marker slot to a non-marker. See fix_marker_vector in igc.c. */ #define NEXT_FREE(v, e) MARKER (v, e) /* Value denoting end of the free list. */ #define FREE_LIST_END make_fixnum (0) #define IS_FREE_LIST_END(x) EQ ((x), FREE_LIST_END) /* Access header fields of marker vector V as lvalues. */ #define FREE(v) (v)->contents[MARKER_VECTOR_FREE] #define MAX_ENTRY(v) (v)->contents[MARKER_VECTOR_MAX_ENTRY] /* Check that index ENTRY is a valid entry start index in vector V. */ static void check_is_entry (const struct Lisp_Vector *v, ptrdiff_t entry) { eassert (entry >= MARKER_VECTOR_HEADER_SIZE); eassert (entry < gc_vsize (v)); eassert ((entry - MARKER_VECTOR_HEADER_SIZE) % MARKER_VECTOR_ENTRY_SIZE == 0); } /* Push entry ENTRY of V to its free-list. This must set MARKER to not be a marker, which is done by using the MARKER field of entry to form the free-list. */ static void push_free (struct Lisp_Vector *v, const ptrdiff_t entry) { check_is_entry (v, entry); NEXT_FREE (v, entry) = FREE (v); FREE (v) = make_fixnum (entry); } /* Pop the next free entry from the free-list of V and return its entry start index. */ static ptrdiff_t pop_free (struct Lisp_Vector *v) { const ptrdiff_t free = XFIXNUM (FREE (v)); FREE (v) = NEXT_FREE (v, free); check_is_entry (v, free); return free; } /* Add a new entry for marker M to marker vector MV and return its entry start index. */ static ptrdiff_t add_entry (Lisp_Object mv, struct Lisp_Marker *m) { struct Lisp_Vector *v = XVECTOR (mv); const ptrdiff_t entry = pop_free (v); MARKER (v, entry) = make_lisp_ptr (m, Lisp_Vectorlike); const ptrdiff_t max_entry = XFIXNUM (MAX_ENTRY (v)); MAX_ENTRY (v) = make_fixnum (max (entry, max_entry)); return entry; } /* Allocate a marker vector of length LEN. */ Lisp_Object alloc_marker_vector (ptrdiff_t len) { #ifdef HAVE_MPS return igc_alloc_marker_vector (len, FREE_LIST_END); #else return make_vector (len, FREE_LIST_END); #endif } /* Expensive pre- and post-condition checking. V is the marker vector to check. ALLOCATING true means we are called from allocation functions where V may be different from the underlying buffer's marker vector. */ static void check_marker_vector (struct Lisp_Vector *v, bool allocating) { #ifdef ENABLE_CHECKING size_t nfree = 0; for (Lisp_Object e = FREE (v); !IS_FREE_LIST_END (e); e = NEXT_FREE (v, XFIXNUM (e))) ++nfree; size_t nused = 0; Lisp_Object mv = make_lisp_ptr (v, Lisp_Vectorlike); FOR_EACH_MARKER_OF_VECTOR (mv, m) { eassert (m->buffer != NULL); if (!allocating) { struct Lisp_Vector *mv = XVECTOR (BUF_MARKERS (m->buffer)); eassert (mv == v); } ++nused; } eassert ((nused + nfree) * MARKER_VECTOR_ENTRY_SIZE + MARKER_VECTOR_HEADER_SIZE == gc_vsize (v)); #endif } /* Add all entries of MV starting with FIRST to the end of marker vector MV to its free list. */ static void add_to_free_list (Lisp_Object mv, ptrdiff_t first) { struct Lisp_Vector *v = XVECTOR (mv); for (ptrdiff_t e = ASIZE (mv) - MARKER_VECTOR_ENTRY_SIZE; e >= first; e -= MARKER_VECTOR_ENTRY_SIZE) push_free (v, e); } /* Make a new marker vector. */ Lisp_Object make_marker_vector (void) { const ptrdiff_t len = (MARKER_VECTOR_INITIAL_ENTRIES * MARKER_VECTOR_ENTRY_SIZE + MARKER_VECTOR_HEADER_SIZE); Lisp_Object mv = alloc_marker_vector (len); add_to_free_list (mv, MARKER_VECTOR_HEADER_SIZE); check_marker_vector (XVECTOR (mv), true); return mv; } /* Return a new marker vector that is like OLD_MV but larger. */ static Lisp_Object larger_marker_vector (Lisp_Object old_mv) { const ptrdiff_t old_size = ASIZE (old_mv); const ptrdiff_t old_entries_size = old_size - MARKER_VECTOR_HEADER_SIZE; const ptrdiff_t new_size = 2 * old_entries_size + MARKER_VECTOR_HEADER_SIZE; /* Allocate a new marker vector. */ Lisp_Object new_mv = alloc_marker_vector (new_size); struct Lisp_Vector *new_v = XVECTOR (new_mv); /* Copy existing entries. */ const struct Lisp_Vector *old_v = XVECTOR (old_mv); const size_t nbytes = old_size * sizeof (Lisp_Object); memcpy (new_v->contents, old_v->contents, nbytes); /* Add new entries to free-list. */ add_to_free_list (new_mv, old_size); check_marker_vector (new_v, true); return new_mv; } /* Make sure that the marker vector of buffer B has room for a new entry. Make a larger marker vector if not. Value is the marker vector of B at the end. */ static Lisp_Object ensure_room (struct buffer *b) { Lisp_Object mv = BUF_MARKERS (b); if (IS_FREE_LIST_END (FREE (XVECTOR (mv)))) { mv = larger_marker_vector (mv); BUF_MARKERS (b) = mv; } return mv; } /* Add marker M to the marker vector of buffer B. */ void marker_vector_add (struct buffer *b, struct Lisp_Marker *m) { const Lisp_Object mv = ensure_room (b); check_marker_vector (XVECTOR (mv), false); m->buffer = b; m->entry = add_entry (mv, m); check_marker_vector (XVECTOR (mv), false); } /* Remove marker M from marker vector V. */ void marker_vector_remove (struct Lisp_Vector *v, struct Lisp_Marker *m) { check_marker_vector (v, false); check_is_entry (v, m->entry); eassert (MARKERP (MARKER (v, m->entry))); eassert (XMARKER (MARKER (v, m->entry)) == m); push_free (v, m->entry); m->buffer = NULL; m->entry = - XFIXNUM (CHARPOS (v, m->entry)); check_marker_vector (v, false); } /* Reset markers of buffer B. Called from kill-buffer. */ void marker_vector_reset (struct buffer *b) { FOR_EACH_MARKER (b, m) { const struct Lisp_Vector *v = XVECTOR (BUF_MARKERS (m->buffer)); m->entry = - XFIXNUM (CHARPOS (v, m->entry)); m->buffer = NULL; } BUF_MARKERS (b) = Qnil; } /* Set marker M's character position to CHARPOS. */ void marker_vector_set_charpos (struct Lisp_Marker *m, ptrdiff_t charpos) { eassert (m->buffer); struct Lisp_Vector *v = XVECTOR (BUF_MARKERS (m->buffer)); check_is_entry (v, m->entry); CHARPOS (v, m->entry) = make_fixnum (charpos); } /* Return marker M's character position. */ ptrdiff_t marker_vector_charpos (const struct Lisp_Marker *m) { eassert (m->buffer); struct Lisp_Vector *v = XVECTOR (BUF_MARKERS (m->buffer)); check_is_entry (v, m->entry); return XFIXNUM (CHARPOS (v, m->entry)); } /* Return marker M's last character position. */ ptrdiff_t marker_vector_last_charpos (const struct Lisp_Marker *m) { eassert (m->buffer == NULL); eassert (m->entry < 0); return - m->entry; } /* Return marker M's byte position. */ ptrdiff_t marker_vector_bytepos (const struct Lisp_Marker *m) { const ptrdiff_t charpos = marker_vector_charpos (m); return buf_charpos_to_bytepos (m->buffer, charpos); } /* Adjust marker positions in buffer B for an insertion that stretches from FROM_CHARPOS to TO_CHARPOS. When a marker points at the insertion point FROM_CHARPOS, we advance it if either its insertion-type is t or BEFORE_MARKERS is true. */ void marker_vector_adjust_for_insert (struct buffer *b, const ptrdiff_t from_charpos, const ptrdiff_t to_charpos, const bool before_markers) { const ptrdiff_t nchars = to_charpos - from_charpos; struct Lisp_Vector *v = XVECTOR (BUF_MARKERS (b)); FOR_EACH_MARKER (b, m) { const ptrdiff_t charpos = XFIXNUM (CHARPOS (v, m->entry)); if (charpos == from_charpos) { if (m->insertion_type || before_markers) CHARPOS (v, m->entry) = make_fixnum (to_charpos); } else if (charpos > from_charpos) CHARPOS (v, m->entry) = make_fixnum (charpos + nchars); } } /* Adjust marker positions of buffer Bs for a replacement of text at FROM_CHARPOS of length OLD_NCHARS to a new text of length NEW_NCHARS. It is assumed that OLD_CHARS > 0, i.e., this is not an insertion. */ void marker_vector_adjust_for_replace (struct buffer *b, const ptrdiff_t from_charpos, const ptrdiff_t old_nchars, const ptrdiff_t new_nchars) { const ptrdiff_t diff_nchars = new_nchars - old_nchars; const ptrdiff_t old_to_charpos = from_charpos + old_nchars; struct Lisp_Vector *v = XVECTOR (BUF_MARKERS (b)); FOR_EACH_MARKER (b, m) { const ptrdiff_t charpos = XFIXNUM (CHARPOS (v, m->entry)); if (charpos >= old_to_charpos) CHARPOS (v, m->entry) = make_fixnum (charpos + diff_nchars); else if (charpos > from_charpos) CHARPOS (v, m->entry) = make_fixnum (from_charpos); } }