emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

scratch/text-index d0a9025fc6f 2/8: marker.c: Keep markers in arrays rat


From: Stefan Monnier
Subject: scratch/text-index d0a9025fc6f 2/8: marker.c: Keep markers in arrays rather than linked lists
Date: Wed, 23 Apr 2025 01:55:47 -0400 (EDT)

branch: scratch/text-index
commit d0a9025fc6fd998fbce4d9dd1e1674c07edf47ef
Author: Gerd Möllmann <gerd.moellmann@gmail.com>
Commit: Stefan Monnier <monnier@iro.umontreal.ca>

    marker.c: Keep markers in arrays rather than linked lists
    
    * src/marker.c (attach_marker): Use `marker_vector_add`.
    (unchain_marker): Rewrite.
    
    * src/pdumper.c (dump_marker): Handle new field `entry`.
    (dump_buffer): Adjust code for `markers` field.
    
    * src/lisp.h (gc_vsize): New function.
    (gc_asize): Use it.
    (struct Lisp_Marker): Delete `next` field, add field `entry` instead.
    
    * src/alloc.c (Fmake_marker): Don't initialize `next`.
    * src/undo.c (record_marker_adjustments):
    * src/insdel.c (adjust_markers_for_delete, adjust_markers_for_insert)
    (adjust_markers_for_replace, adjust_markers_bytepos):
    * src/coding.c (decode_coding_object, encode_coding_object):
    * src/editfns.c (transpose_markers): Use `DO_MARKERS`.
    
    * src/buffer.h (struct buffer_text): Change type of `markers` field.
---
 src/Makefile.in     |   1 +
 src/alloc.c         |  41 +++++---
 src/buffer.c        |  78 +++++++-------
 src/buffer.h        |  12 +--
 src/coding.c        |  89 ++++++++--------
 src/editfns.c       |   4 +-
 src/insdel.c        |  19 ++--
 src/lisp.h          |  18 ++--
 src/marker-vector.c | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/marker-vector.h |  70 +++++++++++++
 src/marker.c        |  40 +------
 src/pdumper.c       |   7 +-
 src/undo.c          |   3 +-
 13 files changed, 504 insertions(+), 171 deletions(-)

diff --git a/src/Makefile.in b/src/Makefile.in
index 451e047162a..d6f4893ce49 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -464,6 +464,7 @@ base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o 
$(XMENU_OBJ) window.o     \
        profiler.o decompress.o                                                \
        thread.o systhread.o sqlite.o  treesit.o                               \
        itree.o json.o                                                         \
+       marker-vector.o                                                         
       \
        $(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ)        \
        $(W32_OBJ) $(WINDOW_SYSTEM_OBJ) $(XGSELOBJ)                            \
        $(HAIKU_OBJ) $(PGTK_OBJ) $(ANDROID_OBJ)
diff --git a/src/alloc.c b/src/alloc.c
index 07ca8474bf3..8c169accf88 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -3809,7 +3809,6 @@ DEFUN ("make-marker", Fmake_marker, Smake_marker, 0, 0, 0,
   p->buffer = 0;
   p->bytepos = 0;
   p->charpos = 0;
-  p->next = NULL;
   p->insertion_type = 0;
   p->need_adjustment = 0;
   return make_lisp_ptr (p, Lisp_Vectorlike);
@@ -3834,8 +3833,7 @@ build_marker (struct buffer *buf, ptrdiff_t charpos, 
ptrdiff_t bytepos)
   m->bytepos = bytepos;
   m->insertion_type = 0;
   m->need_adjustment = 0;
-  m->next = BUF_MARKERS (buf);
-  BUF_MARKERS (buf) = m;
+  marker_vector_add (buf, m);
   return make_lisp_ptr (m, Lisp_Vectorlike);
 }
 
@@ -6264,6 +6262,13 @@ mark_buffer (struct buffer *buffer)
 
   mark_interval_tree (buffer_intervals (buffer));
 
+  /* Mark the marker vector itself live, but don't process its contents
+     yet, because these are weak references.  The marker vector can
+     already have been set to nil in kill-buffer.  */
+  Lisp_Object mv = BUF_MARKERS (buffer);
+  if (!NILP (mv))
+    set_vector_marked (XVECTOR (mv));
+
   /* For now, we just don't mark the undo_list.  It's done later in
      a special way just before the sweep phase, and after stripping
      some of its elements that are not needed any more.
@@ -6271,7 +6276,7 @@ mark_buffer (struct buffer *buffer)
      for dead buffers, the undo_list should be nil (set by Fkill_buffer),
      but just to be on the safe side, we mark it here.  */
   if (!BUFFER_LIVE_P (buffer))
-      mark_object (BVAR (buffer, undo_list));
+    mark_object (BVAR (buffer, undo_list));
 
   if (!itree_empty_p (buffer->overlays))
     mark_overlays (buffer->overlays->root);
@@ -7103,21 +7108,23 @@ sweep_symbols (void)
   gcstat.total_free_symbols = num_free;
 }
 
-/* Remove BUFFER's markers that are due to be swept.  This is needed since
-   we treat BUF_MARKERS and markers's `next' field as weak pointers.  */
+/* Remove BUFFER's markers that are due to be swept.  This is needed
+   since we treat marker references from BUF_MARKER weak references.
+   This relies on the fact that marker vectors only contain markers,
+   fixnums and nil, which means we only have to look at the marker
+   slots.  If such a marker is not yet marked, it's dead and we remove
+   it from the marker vector.  If it is not dead, it won't be freed
+   my sweep_vectors which is called after sweep_buffers.  */
+
 static void
-unchain_dead_markers (struct buffer *buffer)
+unchain_dead_markers (struct buffer *b)
 {
-  struct Lisp_Marker *this, **prev = &BUF_MARKERS (buffer);
-
-  while ((this = *prev))
-    if (vectorlike_marked_p (&this->header))
-      prev = &this->next;
-    else
-      {
-        this->buffer = NULL;
-        *prev = this->next;
-      }
+  DO_MARKERS (b, m)
+    {
+      if (!vectorlike_marked_p (&m->header))
+       marker_vector_remove (XVECTOR (BUF_MARKERS (b)), m);
+    }
+  END_DO_MARKERS;
 }
 
 NO_INLINE /* For better stack traces */
diff --git a/src/buffer.c b/src/buffer.c
index 6d066aa304d..ea80ad91887 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -618,6 +618,8 @@ even if it is dead.  The return value is never nil.  */)
   b->begv_byte = BEG_BYTE;
   b->zv_byte = BEG_BYTE;
 
+  BUF_MARKERS (b) = make_marker_vector ();
+
   BUF_GPT (b) = BEG;
   BUF_GPT_BYTE (b) = BEG_BYTE;
 
@@ -661,8 +663,6 @@ even if it is dead.  The return value is never nil.  */)
   reset_buffer_local_variables (b, 1);
 
   bset_mark (b, Fmake_marker ());
-  BUF_MARKERS (b) = NULL;
-
   /* Put this in the alist of all live buffers.  */
   XSETBUFFER (buffer, b);
   Vbuffer_alist = nconc2 (Vbuffer_alist, list1 (Fcons (name, buffer)));
@@ -1894,7 +1894,6 @@ cleaning up all windows currently displaying the buffer 
to be killed. */)
   Lisp_Object buffer;
   struct buffer *b;
   Lisp_Object tem;
-  struct Lisp_Marker *m;
 
   if (NILP (buffer_or_name))
     buffer = Fcurrent_buffer ();
@@ -2077,18 +2076,14 @@ cleaning up all windows currently displaying the buffer 
to be killed. */)
       /* Unchain all markers that belong to this indirect buffer.
         Don't unchain the markers that belong to the base buffer
         or its other indirect buffers.  */
-      struct Lisp_Marker **mp = &BUF_MARKERS (b);
-      while ((m = *mp))
+      DO_MARKERS (b, m)
        {
          if (m->buffer == b)
-           {
-             m->buffer = NULL;
-             *mp = m->next;
-           }
-         else
-           mp = &m->next;
+           marker_vector_remove (XVECTOR (BUF_MARKERS (b)), m);
        }
-      /* Intervals should be owned by the base buffer (Bug#16502).  */
+      END_DO_MARKERS;
+
+     /* Intervals should be owned by the base buffer (Bug#16502).  */
       i = buffer_intervals (b);
       if (i)
        {
@@ -2101,14 +2096,7 @@ cleaning up all windows currently displaying the buffer 
to be killed. */)
     {
       /* Unchain all markers of this buffer and its indirect buffers.
         and leave them pointing nowhere.  */
-      for (m = BUF_MARKERS (b); m; )
-       {
-         struct Lisp_Marker *next = m->next;
-         m->buffer = 0;
-         m->next = NULL;
-         m = next;
-       }
-      BUF_MARKERS (b) = NULL;
+      marker_vector_reset (b);
       set_buffer_intervals (b, NULL);
 
       /* Perhaps we should explicitly free the interval tree here...  */
@@ -2633,22 +2621,28 @@ results, see Info node `(elisp)Swapping Text'.  */)
   other_buffer->text->end_unchanged = other_buffer->text->gpt;
   swap_buffer_overlays (current_buffer, other_buffer);
   {
-    struct Lisp_Marker *m;
-    for (m = BUF_MARKERS (current_buffer); m; m = m->next)
-      if (m->buffer == other_buffer)
-       m->buffer = current_buffer;
-      else
-       /* Since there's no indirect buffer in sight, markers on
-          BUF_MARKERS(buf) should either be for `buf' or dead.  */
-       eassert (!m->buffer);
-    for (m = BUF_MARKERS (other_buffer); m; m = m->next)
-      if (m->buffer == current_buffer)
-       m->buffer = other_buffer;
-      else
-       /* Since there's no indirect buffer in sight, markers on
-          BUF_MARKERS(buf) should either be for `buf' or dead.  */
-       eassert (!m->buffer);
+    DO_MARKERS (current_buffer, m)
+      {
+       if (m->buffer == other_buffer)
+         m->buffer = current_buffer;
+       else
+         /* Since there's no indirect buffer in sight, markers on
+            BUF_MARKERS(buf) should either be for `buf' or dead.  */
+         eassert (!m->buffer);
+      }
+    END_DO_MARKERS;
+    DO_MARKERS (other_buffer, m)
+      {
+       if (m->buffer == current_buffer)
+         m->buffer = other_buffer;
+       else
+         /* Since there's no indirect buffer in sight, markers on
+            BUF_MARKERS(buf) should either be for `buf' or dead.  */
+         eassert (!m->buffer);
+      }
+    END_DO_MARKERS;
   }
+
   { /* Some of the C code expects that both window markers of a
        live window points to that window's buffer.  So since we
        just swapped the markers between the two buffers, we need
@@ -2710,7 +2704,6 @@ If the multibyte flag was really changed, undo 
information of the
 current buffer is cleared.  */)
   (Lisp_Object flag)
 {
-  struct Lisp_Marker *tail, *markers;
   Lisp_Object btail, other;
   ptrdiff_t begv, zv;
   bool narrowed = (BEG != BEGV || Z != ZV);
@@ -2756,9 +2749,11 @@ current buffer is cleared.  */)
       GPT = GPT_BYTE;
       TEMP_SET_PT_BOTH (PT_BYTE, PT_BYTE);
 
-
-      for (tail = BUF_MARKERS (current_buffer); tail; tail = tail->next)
-       tail->charpos = tail->bytepos;
+      DO_MARKERS (current_buffer, tail)
+       {
+         tail->charpos = tail->bytepos;
+       }
+      END_DO_MARKERS;
 
       /* Convert multibyte form of 8-bit characters to unibyte.  */
       pos = BEG;
@@ -2908,13 +2903,12 @@ current buffer is cleared.  */)
        TEMP_SET_PT_BOTH (position, byte);
       }
 
-      tail = markers = BUF_MARKERS (current_buffer);
-
-      for (; tail; tail = tail->next)
+      DO_MARKERS (current_buffer, tail)
        {
          tail->bytepos = advance_to_char_boundary (tail->bytepos);
          tail->charpos = BYTE_TO_CHAR (tail->bytepos);
        }
+      END_DO_MARKERS;
 
       /* Do this last, so it can calculate the new correspondences
         between chars and bytes.  */
diff --git a/src/buffer.h b/src/buffer.h
index 6f817c6dd61..55f3b457939 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -274,14 +274,8 @@ struct buffer_text
     /* Properties of this buffer's text.  */
     INTERVAL intervals;
 
-    /* The markers that refer to this buffer.
-       This is actually a single marker ---
-       successive elements in its marker `chain'
-       are the other markers referring to this buffer.
-       This is a singly linked unordered list, which means that it's
-       very cheap to add a marker to the list and it's also very cheap
-       to move a marker within a buffer.  */
-    struct Lisp_Marker *markers;
+    /* Marker vector.  */
+    Lisp_Object markers;
 
     /* Usually false.  Temporarily true in decode_coding_gap to
        prevent Fgarbage_collect from shrinking the gap and losing
@@ -1750,4 +1744,6 @@ INLINE_HEADER_END
 int compare_overlays (const void *v1, const void *v2);
 void make_sortvec_item (struct sortvec *item, Lisp_Object overlay);
 
+#include "marker-vector.h"
+
 #endif /* EMACS_BUFFER_H */
diff --git a/src/coding.c b/src/coding.c
index 63b0dbeb18b..c38fcabd0b9 100644
--- a/src/coding.c
+++ b/src/coding.c
@@ -8118,14 +8118,13 @@ decode_coding_object (struct coding_system *coding,
        move_gap_both (from, from_byte);
       if (BASE_EQ (src_object, dst_object))
        {
-         struct Lisp_Marker *tail;
-
-         for (tail = BUF_MARKERS (current_buffer); tail; tail = tail->next)
+         DO_MARKERS (current_buffer, tail)
            {
              tail->need_adjustment
                = tail->charpos == (tail->insertion_type ? from : to);
              need_marker_adjustment |= tail->need_adjustment;
            }
+         END_DO_MARKERS;
          saved_pt = PT, saved_pt_byte = PT_BYTE;
          TEMP_SET_PT_BOTH (from, from_byte);
          current_buffer->text->inhibit_shrinking = true;
@@ -8250,25 +8249,26 @@ decode_coding_object (struct coding_system *coding,
 
       if (need_marker_adjustment)
        {
-         struct Lisp_Marker *tail;
-
-         for (tail = BUF_MARKERS (current_buffer); tail; tail = tail->next)
-           if (tail->need_adjustment)
-             {
-               tail->need_adjustment = 0;
-               if (tail->insertion_type)
-                 {
-                   tail->bytepos = from_byte;
-                   tail->charpos = from;
-                 }
-               else
-                 {
-                   tail->bytepos = from_byte + coding->produced;
-                   tail->charpos
-                     = (NILP (BVAR (current_buffer, 
enable_multibyte_characters))
-                        ? tail->bytepos : from + coding->produced_char);
-                 }
-             }
+         DO_MARKERS (current_buffer, tail)
+           {
+             if (tail->need_adjustment)
+               {
+                 tail->need_adjustment = 0;
+                 if (tail->insertion_type)
+                   {
+                     tail->bytepos = from_byte;
+                     tail->charpos = from;
+                   }
+                 else
+                   {
+                     tail->bytepos = from_byte + coding->produced;
+                     tail->charpos
+                       = (NILP (BVAR (current_buffer, 
enable_multibyte_characters))
+                          ? tail->bytepos : from + coding->produced_char);
+                   }
+               }
+           }
+         END_DO_MARKERS;
        }
     }
 
@@ -8338,16 +8338,14 @@ encode_coding_object (struct coding_system *coding,
   bool same_buffer = false;
   if (BASE_EQ (src_object, dst_object) && BUFFERP (src_object))
     {
-      struct Lisp_Marker *tail;
-
       same_buffer = true;
-
-      for (tail = BUF_MARKERS (XBUFFER (src_object)); tail; tail = tail->next)
+      DO_MARKERS (XBUFFER (src_object), tail)
        {
          tail->need_adjustment
            = tail->charpos == (tail->insertion_type ? from : to);
          need_marker_adjustment |= tail->need_adjustment;
        }
+      END_DO_MARKERS;
     }
 
   if (! NILP (CODING_ATTR_PRE_WRITE (attrs)))
@@ -8505,25 +8503,26 @@ encode_coding_object (struct coding_system *coding,
 
       if (need_marker_adjustment)
        {
-         struct Lisp_Marker *tail;
-
-         for (tail = BUF_MARKERS (current_buffer); tail; tail = tail->next)
-           if (tail->need_adjustment)
-             {
-               tail->need_adjustment = 0;
-               if (tail->insertion_type)
-                 {
-                   tail->bytepos = from_byte;
-                   tail->charpos = from;
-                 }
-               else
-                 {
-                   tail->bytepos = from_byte + coding->produced;
-                   tail->charpos
-                     = (NILP (BVAR (current_buffer, 
enable_multibyte_characters))
-                        ? tail->bytepos : from + coding->produced_char);
-                 }
-             }
+         DO_MARKERS (current_buffer, tail)
+           {
+             if (tail->need_adjustment)
+               {
+                 tail->need_adjustment = 0;
+                 if (tail->insertion_type)
+                   {
+                     tail->bytepos = from_byte;
+                     tail->charpos = from;
+                   }
+                 else
+                   {
+                     tail->bytepos = from_byte + coding->produced;
+                     tail->charpos
+                       = (NILP (BVAR (current_buffer, 
enable_multibyte_characters))
+                          ? tail->bytepos : from + coding->produced_char);
+                   }
+               }
+           }
+         END_DO_MARKERS;
        }
     }
 
diff --git a/src/editfns.c b/src/editfns.c
index c4f23ccbe5b..6ef93a35fa5 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -4402,7 +4402,6 @@ transpose_markers (ptrdiff_t start1, ptrdiff_t end1,
                   ptrdiff_t start2_byte, ptrdiff_t end2_byte)
 {
   register ptrdiff_t amt1, amt1_byte, amt2, amt2_byte, diff, diff_byte, mpos;
-  register struct Lisp_Marker *marker;
 
   /* Update point as if it were a marker.  */
   if (PT < start1)
@@ -4437,7 +4436,7 @@ transpose_markers (ptrdiff_t start1, ptrdiff_t end1,
   amt1_byte = (end2_byte - start2_byte) + (start2_byte - end1_byte);
   amt2_byte = (end1_byte - start1_byte) + (start2_byte - end1_byte);
 
-  for (marker = BUF_MARKERS (current_buffer); marker; marker = marker->next)
+  DO_MARKERS (current_buffer, marker)
     {
       mpos = marker->bytepos;
       if (mpos >= start1_byte && mpos < end2_byte)
@@ -4462,6 +4461,7 @@ transpose_markers (ptrdiff_t start1, ptrdiff_t end1,
        }
       marker->charpos = mpos;
     }
+  END_DO_MARKERS;
 }
 
 DEFUN ("transpose-regions", Ftranspose_regions, Stranspose_regions, 4, 5,
diff --git a/src/insdel.c b/src/insdel.c
index a10cb7c4898..7bfa8dc52c2 100644
--- a/src/insdel.c
+++ b/src/insdel.c
@@ -250,13 +250,12 @@ void
 adjust_markers_for_delete (ptrdiff_t from, ptrdiff_t from_byte,
                           ptrdiff_t to, ptrdiff_t to_byte)
 {
-  struct Lisp_Marker *m;
   ptrdiff_t charpos;
 
   text_index_invalidate (current_buffer, from_byte);
 
   adjust_suspend_auto_hscroll (from, to);
-  for (m = BUF_MARKERS (current_buffer); m; m = m->next)
+  DO_MARKERS (current_buffer, m)
     {
       charpos = m->charpos;
       eassert (charpos <= Z);
@@ -275,6 +274,7 @@ adjust_markers_for_delete (ptrdiff_t from, ptrdiff_t 
from_byte,
          m->bytepos = from_byte;
        }
     }
+  END_DO_MARKERS;
   adjust_overlays_for_delete (from, to - from);
 }
 
@@ -293,12 +293,11 @@ adjust_markers_for_insert (ptrdiff_t from, ptrdiff_t 
from_byte,
 {
   text_index_invalidate (current_buffer, from_byte);
 
-  struct Lisp_Marker *m;
   ptrdiff_t nchars = to - from;
   ptrdiff_t nbytes = to_byte - from_byte;
 
   adjust_suspend_auto_hscroll (from, to);
-  for (m = BUF_MARKERS (current_buffer); m; m = m->next)
+  DO_MARKERS (current_buffer, m)
     {
       eassert (m->bytepos >= m->charpos
               && m->bytepos - m->charpos <= Z_BYTE - Z);
@@ -317,6 +316,7 @@ adjust_markers_for_insert (ptrdiff_t from, ptrdiff_t 
from_byte,
          m->charpos += nchars;
        }
     }
+  END_DO_MARKERS;
   adjust_overlays_for_insert (from, to - from, before_markers);
 }
 
@@ -350,7 +350,6 @@ adjust_markers_for_replace (ptrdiff_t from, ptrdiff_t 
from_byte,
 {
   text_index_invalidate (current_buffer, from_byte);
 
-  register struct Lisp_Marker *m;
   ptrdiff_t prev_to_byte = from_byte + old_bytes;
   ptrdiff_t diff_chars = new_chars - old_chars;
   ptrdiff_t diff_bytes = new_bytes - old_bytes;
@@ -369,7 +368,7 @@ adjust_markers_for_replace (ptrdiff_t from, ptrdiff_t 
from_byte,
 
   adjust_suspend_auto_hscroll (from, from + old_chars);
 
-  for (m = BUF_MARKERS (current_buffer); m; m = m->next)
+  DO_MARKERS (current_buffer, m)
     {
       if (m->bytepos >= prev_to_byte)
        {
@@ -382,6 +381,7 @@ adjust_markers_for_replace (ptrdiff_t from, ptrdiff_t 
from_byte,
          m->bytepos = from_byte;
        }
     }
+  END_DO_MARKERS;
 
   check_markers ();
 
@@ -420,7 +420,6 @@ void
 adjust_markers_bytepos (ptrdiff_t from, ptrdiff_t from_byte,
                        ptrdiff_t to, ptrdiff_t to_byte, int to_z)
 {
-  register struct Lisp_Marker *m;
   ptrdiff_t beg = from, begbyte = from_byte;
 
   adjust_suspend_auto_hscroll (from, to);
@@ -429,16 +428,17 @@ adjust_markers_bytepos (ptrdiff_t from, ptrdiff_t 
from_byte,
     {
       /* Make sure each affected marker's bytepos is equal to
         its charpos.  */
-      for (m = BUF_MARKERS (current_buffer); m; m = m->next)
+      DO_MARKERS (current_buffer, m)
        {
          if (m->bytepos > from_byte
              && (to_z || m->bytepos <= to_byte))
            m->bytepos = m->charpos;
        }
+      END_DO_MARKERS;
     }
   else
     {
-      for (m = BUF_MARKERS (current_buffer); m; m = m->next)
+      DO_MARKERS (current_buffer, m)
        {
          /* Recompute each affected marker's bytepos.  */
          if (m->bytepos > from_byte
@@ -455,6 +455,7 @@ adjust_markers_bytepos (ptrdiff_t from, ptrdiff_t from_byte,
              begbyte = m->bytepos;
            }
        }
+      END_DO_MARKERS;
     }
 }
 
diff --git a/src/lisp.h b/src/lisp.h
index 99d4d79f354..c8ac8a877a0 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -1757,11 +1757,17 @@ ASIZE (Lisp_Object array)
   return size;
 }
 
+INLINE ptrdiff_t
+gc_vsize (const struct Lisp_Vector *v)
+{
+  return v->header.size & ~ARRAY_MARK_FLAG;
+}
+
 INLINE ptrdiff_t
 gc_asize (Lisp_Object array)
 {
   /* Like ASIZE, but also can be used in the garbage collector.  */
-  return XVECTOR (array)->header.size & ~ARRAY_MARK_FLAG;
+  return gc_vsize (XVECTOR (array));
 }
 
 INLINE ptrdiff_t
@@ -2839,13 +2845,6 @@ struct Lisp_Marker
   /* The remaining fields are meaningless in a marker that
      does not point anywhere.  */
 
-  /* For markers that point somewhere,
-     this is used to chain of all the markers in a given buffer.
-     The chain does not preserve markers from garbage collection;
-     instead, markers are removed from the chain when freed by GC.  */
-  /* We could remove it and use an array in buffer_text instead.
-     That would also allow us to preserve it ordered.  */
-  struct Lisp_Marker *next;
   /* This is the char position where the marker points.  */
   ptrdiff_t charpos;
   /* This is the byte position.
@@ -2853,6 +2852,9 @@ struct Lisp_Marker
      used to implement the functionality of markers, but rather to (ab)use
      markers as a cache for char<->byte mappings).  */
   ptrdiff_t bytepos;
+
+  /* If in a buffer's marker vector, this is the entry where it is stored.  */
+  ptrdiff_t entry;
 } GCALIGNED_STRUCT;
 
 struct Lisp_Overlay
diff --git a/src/marker-vector.c b/src/marker-vector.c
new file mode 100644
index 00000000000..0583d44b98c
--- /dev/null
+++ b/src/marker-vector.c
@@ -0,0 +1,293 @@
+/* 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 <https://www.gnu.org/licenses/>. */
+
+/* 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 <config.h>
+#include "lisp.h"
+#include "buffer.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);
+  DO_MARKERS_OF_VECTOR (mv, m)
+    {
+      eassert (m->entry == e_);
+      eassert (m->buffer != NULL);
+      if (!allocating)
+       {
+         struct Lisp_Vector *mv = XVECTOR (BUF_MARKERS (m->buffer));
+         eassert (mv == v);
+       }
+      ++nused;
+    }
+  END_DO_MARKERS;
+
+  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);
+  /* The old GC contains at least one assertion that unchaining markers
+     in kill-buffer resets the markers' buffers.  IGC does not do this,
+     can't do this, and does not need it.  */
+  m->buffer = NULL;
+  check_marker_vector (v, false);
+}
+
+/* Reset markers of buffer B.  Called from kill-buffer.  */
+
+void
+marker_vector_reset (struct buffer *b)
+{
+  /* The old GC contains at least one assertion that unchaining markers
+     in kill-buffer resets the markers' buffers.  IGC does not do this,
+     can't do this, and does not need it.  */
+  DO_MARKERS (b, m)
+    {
+      m->buffer = NULL;
+    }
+  END_DO_MARKERS;
+  BUF_MARKERS (b) = Qnil;
+}
diff --git a/src/marker-vector.h b/src/marker-vector.h
new file mode 100644
index 00000000000..c6abcb0e294
--- /dev/null
+++ b/src/marker-vector.h
@@ -0,0 +1,70 @@
+/* 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 <https://www.gnu.org/licenses/>. */
+
+#ifndef EMACS_MARKER_VECTOR_H
+#define EMACS_MARKER_VECTOR_H
+
+#include <config.h>
+#include "lisp.h"
+
+/* A marker vector is a Lisp vector starting with a header of
+   MARKER_VECTOR_HEADER_SIZE Lisp_Objects, followed by entries of
+   MARKER_VECTOR_ENTRY_SIZE Lisp_Objects.  */
+
+enum
+{
+  /* Header. */
+  MARKER_VECTOR_FREE = 0,
+  MARKER_VECTOR_MAX_ENTRY = 1,
+  MARKER_VECTOR_HEADER_SIZE = 2,
+
+  /* Entries.  */
+  MARKER_VECTOR_OFFSET_MARKER = 0,
+  MARKER_VECTOR_OFFSET_CHARPOS = 1,
+  MARKER_VECTOR_ENTRY_SIZE = 2,
+};
+
+/* Iterate over markers in marker vector MV, binding a variable with
+   name M to a pointer to Lisp_Marker.  The loop must be ended
+   with an END_DO_MARKERS.  */
+
+# define DO_MARKERS_OF_VECTOR(mv, m)                                   \
+  for (ptrdiff_t e_ = MARKER_VECTOR_HEADER_SIZE,                       \
+        end_ = XFIXNUM (AREF (mv, MARKER_VECTOR_MAX_ENTRY));           \
+       e_ <= end_;                                                     \
+       e_ += MARKER_VECTOR_ENTRY_SIZE)                                 \
+    {                                                                  \
+       Lisp_Object m_ = AREF (mv, e_ + MARKER_VECTOR_OFFSET_MARKER);   \
+       if (MARKERP (m_))                                               \
+        {                                                              \
+            struct Lisp_Marker *m = XMARKER (m_);
+
+/* Iterate over markers of buffer B, binding a variable with name M to a
+   pointer to Lisp_Marker.  The loop must be ended with an
+   END_DO_MARKERS.  */
+
+# define DO_MARKERS(b, m) DO_MARKERS_OF_VECTOR (BUF_MARKERS (b), m)
+# define END_DO_MARKERS }}
+
+Lisp_Object make_marker_vector (void);
+Lisp_Object alloc_marker_vector (ptrdiff_t len);
+void marker_vector_add (struct buffer *b, struct Lisp_Marker *m);
+void marker_vector_remove (struct Lisp_Vector *v, struct Lisp_Marker *m);
+void marker_vector_reset (struct buffer *b);
+
+#endif /* EMACS_MARKER_VECTOR_H */
diff --git a/src/marker.c b/src/marker.c
index 22929638e51..a528bd315fc 100644
--- a/src/marker.c
+++ b/src/marker.c
@@ -141,9 +141,7 @@ attach_marker (struct Lisp_Marker *m, struct buffer *b,
   if (m->buffer != b)
     {
       unchain_marker (m);
-      m->buffer = b;
-      m->next = BUF_MARKERS (b);
-      BUF_MARKERS (b) = m;
+      marker_vector_add (b, m);
     }
 }
 
@@ -336,40 +334,12 @@ detach_marker (Lisp_Object marker)
    buffer NULL.  */
 
 void
-unchain_marker (register struct Lisp_Marker *marker)
+unchain_marker (struct Lisp_Marker *marker)
 {
-  register struct buffer *b = marker->buffer;
-
-  if (b)
+  if (marker->buffer)
     {
-      register struct Lisp_Marker *tail, **prev;
-
-      /* No dead buffers here.  */
-      eassert (BUFFER_LIVE_P (b));
-
-      marker->buffer = NULL;
-      prev = &BUF_MARKERS (b);
-
-      for (tail = BUF_MARKERS (b); tail; prev = &tail->next, tail = *prev)
-       if (marker == tail)
-         {
-           if (*prev == BUF_MARKERS (b))
-             {
-               /* Deleting first marker from the buffer's chain.  Crash
-                  if new first marker in chain does not say it belongs
-                  to the same buffer, or at least that they have the same
-                  base buffer.  */
-               if (tail->next && b->text != tail->next->buffer->text)
-                 emacs_abort ();
-             }
-           *prev = tail->next;
-           /* We have removed the marker from the chain;
-              no need to scan the rest of the chain.  */
-           break;
-         }
-
-      /* Error if marker was not in it's chain.  */
-      eassert (tail != NULL);
+      Lisp_Object mv = BUF_MARKERS (marker->buffer);
+      marker_vector_remove (XVECTOR (mv), marker);
     }
 }
 
diff --git a/src/pdumper.c b/src/pdumper.c
index c8255ecab83..3fd25a80b3f 100644
--- a/src/pdumper.c
+++ b/src/pdumper.c
@@ -2129,8 +2129,7 @@ dump_marker (struct dump_context *ctx, const struct 
Lisp_Marker *marker)
     {
       dump_field_lv_rawptr (ctx, out, marker, &marker->buffer,
                            Lisp_Vectorlike, WEIGHT_NORMAL);
-      dump_field_lv_rawptr (ctx, out, marker, &marker->next,
-                           Lisp_Vectorlike, WEIGHT_STRONG);
+      DUMP_FIELD_COPY (out, marker, entry);
       DUMP_FIELD_COPY (out, marker, charpos);
       DUMP_FIELD_COPY (out, marker, bytepos);
     }
@@ -2874,8 +2873,8 @@ dump_buffer (struct dump_context *ctx, const struct 
buffer *in_buffer)
       DUMP_FIELD_COPY (out, buffer, own_text.overlay_unchanged_modified);
       if (buffer->own_text.intervals)
         dump_field_fixup_later (ctx, out, buffer, &buffer->own_text.intervals);
-      dump_field_lv_rawptr (ctx, out, buffer, &buffer->own_text.markers,
-                            Lisp_Vectorlike, WEIGHT_NORMAL);
+      dump_field_lv (ctx, out, buffer, &buffer->own_text.markers,
+                    WEIGHT_NORMAL);
       DUMP_FIELD_COPY (out, buffer, own_text.inhibit_shrinking);
       DUMP_FIELD_COPY (out, buffer, own_text.redisplay);
     }
diff --git a/src/undo.c b/src/undo.c
index 87d3fc2602d..21389bdfe36 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -128,7 +128,7 @@ record_marker_adjustments (ptrdiff_t from, ptrdiff_t to)
 {
   prepare_record ();
 
-  for (struct Lisp_Marker *m = BUF_MARKERS (current_buffer); m; m = m->next)
+  DO_MARKERS (current_buffer, m)
     {
       ptrdiff_t charpos = m->charpos;
       eassert (charpos <= Z);
@@ -154,6 +154,7 @@ record_marker_adjustments (ptrdiff_t from, ptrdiff_t to)
             }
         }
     }
+  END_DO_MARKERS;
 }
 
 /* Record that a deletion is about to take place, of the characters in



reply via email to

[Prev in Thread] Current Thread [Next in Thread]