commit 90de836831c032000968bd36f353640bd115e67f Author: Pip Cet Date: Fri Jun 12 16:19:37 2026 +0000 Unbind specpdl when leaving replace-region-contents (bug#81169) * src/editfns.c (Freplace_region_contents): Call SPECPDL_INDEX before code_conversion_save adds specpdl entries. diff --git a/src/editfns.c b/src/editfns.c index 191ea0d9053..10980c96aa1 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -2065,6 +2065,8 @@ a buffer or a string. But this is deprecated. */) time_limit = tlim; } + specpdl_ref count = SPECPDL_INDEX (); + /* The rest of the code is not prepared to handle a string SOURCE. */ if (!b) { @@ -2077,8 +2079,6 @@ a buffer or a string. But this is deprecated. */) } Lisp_Object source_buffer = make_lisp_ptr (b, Lisp_Vectorlike); - specpdl_ref count = SPECPDL_INDEX (); - ptrdiff_t diags = size_a + size_b + 3; ptrdiff_t bytes_per_diag = 2 * sizeof (ptrdiff_t) + sizeof (int); ptrdiff_t surplus_char_bytes = 3 * sizeof (int); commit f5cb95423e76ebefbcb6456474cfd4fac8da923d Author: Paul Eggert Date: Sat Jun 6 16:43:45 2026 -0700 replace-region-contents malloc fallback * src/editfns.c (Freplace_region_contents): If the temporary storage is so large that malloc fails, do not signal an error. Instead, fall back on destructive replacement. diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index a5535df601b..0b9dc4951a5 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -4863,7 +4863,7 @@ faster but suboptimal solution. The default value is 1000000. @code{replace-region-contents} returns @code{t} if a non-destructive replacement could be performed. Otherwise, i.e., if @var{max-secs} -was exceeded, it returns @code{nil}. +was exceeded or too much memory would have been needed, it returns @code{nil}. Note: When using the refined replacement algorithm, if the replacement is a string, it will be internally copied to a temporary buffer. diff --git a/src/editfns.c b/src/editfns.c index 31bf911a8be..191ea0d9053 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -1961,8 +1961,8 @@ in a temporary buffer. Therefore, all else being equal, it is preferable to pass a buffer rather than a string as SOURCE argument. This function returns t if a non-destructive replacement could be -performed. Otherwise, i.e., if MAX-SECS was exceeded, it returns -nil. +performed. Otherwise, i.e., if MAX-SECS was exceeded or too much +memory would have been needed, it returns nil. SOURCE can also be a function that will be called with no arguments and with current buffer narrowed to BEG..END, and should return @@ -2065,6 +2065,18 @@ a buffer or a string. But this is deprecated. */) time_limit = tlim; } + /* The rest of the code is not prepared to handle a string SOURCE. */ + if (!b) + { + Lisp_Object workbuf + = code_conversion_save (true, STRING_MULTIBYTE (source)); + b = XBUFFER (workbuf); + set_buffer_internal (b); + CALLN (Finsert, source); + set_buffer_internal (a); + } + Lisp_Object source_buffer = make_lisp_ptr (b, Lisp_Vectorlike); + specpdl_ref count = SPECPDL_INDEX (); ptrdiff_t diags = size_a + size_b + 3; @@ -2078,60 +2090,65 @@ a buffer or a string. But this is deprecated. */) ptrdiff_t bytes_needed; if (ckd_mul (&bytes_needed, diags, bytes_per_diag) || ckd_add (&bytes_needed, bytes_needed, - align_bytes - surplus_char_bytes + del_bytes + ins_bytes)) - memory_full_up (); - USE_SAFE_ALLOCA; - buffer = SAFE_ALLOCA (bytes_needed); - - /* The rest of the code is not prepared to handle a string SOURCE. */ - if (!b) + align_bytes - surplus_char_bytes + del_bytes + ins_bytes) + || SIZE_MAX < bytes_needed) + buffer = NULL; + else if (bytes_needed <= MAX_ALLOCA) + buffer = alloca (bytes_needed); + else { - Lisp_Object workbuf - = code_conversion_save (true, STRING_MULTIBYTE (source)); - b = XBUFFER (workbuf); - set_buffer_internal (b); - CALLN (Finsert, source); - set_buffer_internal (a); + buffer = malloc (bytes_needed); + if (buffer) + { + if (profiler_memory_running) + malloc_probe (bytes_needed); + record_unwind_protect_ptr (xfree, buffer); + } } - Lisp_Object source_buffer = make_lisp_ptr (b, Lisp_Vectorlike); - - /* Copy the characters to arrays of C integers. This speeds up - comparison dramatically in multibyte buffers. */ - int *chars_a = (int *) (((uintptr_t) (buffer + 2 * diags) + align_bytes) - & ~align_bytes); - for (ptrdiff_t i = 0; i < size_a; i++) - chars_a[i] - = BUF_FETCH_CHAR_AS_MULTIBYTE (a, buf_charpos_to_bytepos (a, min_a + i)); - - int *chars_b = chars_a + size_a; - for (ptrdiff_t i = 0; i < size_b; i++) - chars_b[i] - = BUF_FETCH_CHAR_AS_MULTIBYTE (b, buf_charpos_to_bytepos (b, min_b + i)); - - unsigned char *deletions_insertions = memset (chars_b + size_b, 0, - del_bytes + ins_bytes); - - /* FIXME: It is not documented how to initialize the contents of the - context structure. This code cargo-cults from the existing - caller in src/analyze.c of GNU Diffutils, which appears to - work. */ - struct context ctx = { - .chars_a = chars_a, - .chars_b = chars_b, - .deletions = deletions_insertions, - .insertions = deletions_insertions + del_bytes, - .fdiag = buffer + size_b + 1, - .bdiag = buffer + diags + size_b + 1, - .heuristic = true, - .too_expensive = too_expensive, - .time_limit = time_limit, - }; - /* compareseq requires indices to be zero-based. We add BEGV back - later. */ bool early_abort; - if (! sys_setjmp (ctx.jmp)) - early_abort = compareseq (0, size_a, 0, size_b, false, &ctx); + struct context ctx; + + if (buffer) + { + /* Copy the characters to arrays of C ints. This speeds up + comparison dramatically in multibyte buffers. */ + int *chars_a = (int *) (((uintptr_t) (buffer + 2 * diags) + align_bytes) + & ~align_bytes); + for (ptrdiff_t i = 0; i < size_a; i++) + chars_a[i] = (BUF_FETCH_CHAR_AS_MULTIBYTE + (a, buf_charpos_to_bytepos (a, min_a + i))); + + int *chars_b = chars_a + size_a; + for (ptrdiff_t i = 0; i < size_b; i++) + chars_b[i] = (BUF_FETCH_CHAR_AS_MULTIBYTE + (b, buf_charpos_to_bytepos (b, min_b + i))); + + unsigned char *deletions_insertions = memset (chars_b + size_b, 0, + del_bytes + ins_bytes); + + /* FIXME: It is not documented how to initialize the contents of the + context structure. This code cargo-cults from the existing + caller in src/analyze.c of GNU Diffutils, which appears to + work. */ + ctx = (struct context) { + .chars_a = chars_a, + .chars_b = chars_b, + .deletions = deletions_insertions, + .insertions = deletions_insertions + del_bytes, + .fdiag = buffer + size_b + 1, + .bdiag = buffer + diags + size_b + 1, + .heuristic = true, + .too_expensive = too_expensive, + .time_limit = time_limit, + }; + + /* compareseq wants zero-based indices. We add BEGV back later. */ + if (! sys_setjmp (ctx.jmp)) + early_abort = compareseq (0, size_a, 0, size_b, false, &ctx); + else + early_abort = true; + } else early_abort = true; @@ -2141,8 +2158,7 @@ a buffer or a string. But this is deprecated. */) make_fixnum (BUF_BEGV (b)), make_fixnum (BUF_ZV (b))); replace_range (min_a, min_a + size_a, src, true, false, inh); - SAFE_FREE_UNBIND_TO (count, Qnil); - return Qnil; + return unbind_to (count, Qnil); } Fundo_boundary (); @@ -2196,7 +2212,7 @@ a buffer or a string. But this is deprecated. */) --j; } - SAFE_FREE_UNBIND_TO (count, Qnil); + unbind_to (count, Qnil); if (modification_hooks_inhibited) { commit 90f25503d8145fc94b9a500d93332ae6fc35f6b8 Author: Paul Eggert Date: Sat Jun 6 14:30:15 2026 -0700 Refactor replace-region-contents init * src/editfns.c (Freplace_region_contents): Make it more obvious that everything fits. diff --git a/src/editfns.c b/src/editfns.c index 78f60ca1da5..31bf911a8be 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -2099,14 +2099,14 @@ a buffer or a string. But this is deprecated. */) comparison dramatically in multibyte buffers. */ int *chars_a = (int *) (((uintptr_t) (buffer + 2 * diags) + align_bytes) & ~align_bytes); - for (ptrdiff_t p = min_a; p < min_a + size_a; p++) - chars_a[p - min_a] - = BUF_FETCH_CHAR_AS_MULTIBYTE (a, buf_charpos_to_bytepos (a, p)); + for (ptrdiff_t i = 0; i < size_a; i++) + chars_a[i] + = BUF_FETCH_CHAR_AS_MULTIBYTE (a, buf_charpos_to_bytepos (a, min_a + i)); int *chars_b = chars_a + size_a; - for (ptrdiff_t p = min_b; p < min_b + size_b; p++) - chars_b[p - min_b] - = BUF_FETCH_CHAR_AS_MULTIBYTE (b, buf_charpos_to_bytepos (b, p)); + for (ptrdiff_t i = 0; i < size_b; i++) + chars_b[i] + = BUF_FETCH_CHAR_AS_MULTIBYTE (b, buf_charpos_to_bytepos (b, min_b + i)); unsigned char *deletions_insertions = memset (chars_b + size_b, 0, del_bytes + ins_bytes); commit 09047cde59354ea37b717256596cac6e471460da Author: Paul Eggert Date: Sat Jun 6 12:28:17 2026 -0700 Speed up replace-region-contents memory allocation * src/editfns.c (Freplace_region_contents): Coalesce three SAFE_ALLOCAs into one. diff --git a/src/editfns.c b/src/editfns.c index 50391a7ce0b..78f60ca1da5 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -2068,17 +2068,20 @@ a buffer or a string. But this is deprecated. */) specpdl_ref count = SPECPDL_INDEX (); ptrdiff_t diags = size_a + size_b + 3; + ptrdiff_t bytes_per_diag = 2 * sizeof (ptrdiff_t) + sizeof (int); + ptrdiff_t surplus_char_bytes = 3 * sizeof (int); + ptrdiff_t align_bytes = (alignof (ptrdiff_t) < alignof (int) + ? alignof (int) - alignof (ptrdiff_t) : 0); ptrdiff_t del_bytes = size_a / CHAR_BIT + 1; ptrdiff_t ins_bytes = size_b / CHAR_BIT + 1; ptrdiff_t *buffer; ptrdiff_t bytes_needed; - if (ckd_mul (&bytes_needed, diags, 2 * sizeof *buffer) - || ckd_add (&bytes_needed, bytes_needed, del_bytes + ins_bytes)) + if (ckd_mul (&bytes_needed, diags, bytes_per_diag) + || ckd_add (&bytes_needed, bytes_needed, + align_bytes - surplus_char_bytes + del_bytes + ins_bytes)) memory_full_up (); USE_SAFE_ALLOCA; buffer = SAFE_ALLOCA (bytes_needed); - unsigned char *deletions_insertions = memset (buffer + 2 * diags, 0, - del_bytes + ins_bytes); /* The rest of the code is not prepared to handle a string SOURCE. */ if (!b) @@ -2094,16 +2097,20 @@ a buffer or a string. But this is deprecated. */) /* Copy the characters to arrays of C integers. This speeds up comparison dramatically in multibyte buffers. */ - int *chars_a = SAFE_ALLOCA (sizeof (chars_a[0]) * size_a); + int *chars_a = (int *) (((uintptr_t) (buffer + 2 * diags) + align_bytes) + & ~align_bytes); for (ptrdiff_t p = min_a; p < min_a + size_a; p++) chars_a[p - min_a] = BUF_FETCH_CHAR_AS_MULTIBYTE (a, buf_charpos_to_bytepos (a, p)); - int *chars_b = SAFE_ALLOCA (sizeof (chars_b[0]) * size_b); + int *chars_b = chars_a + size_a; for (ptrdiff_t p = min_b; p < min_b + size_b; p++) chars_b[p - min_b] = BUF_FETCH_CHAR_AS_MULTIBYTE (b, buf_charpos_to_bytepos (b, p)); + unsigned char *deletions_insertions = memset (chars_b + size_b, 0, + del_bytes + ins_bytes); + /* FIXME: It is not documented how to initialize the contents of the context structure. This code cargo-cults from the existing caller in src/analyze.c of GNU Diffutils, which appears to commit 565639eb32e85a22c09422ee1d8e172821191763 Author: Pip Cet Date: Tue Jun 2 14:58:04 2026 +0000 Simplify character comparisons in Freplace_region_contents We expect 'compareseq' to call 'buffer_chars_equal' very often, so it's worth it to use some memory and make it as fast as possible. * src/editfns.c (EXTRA_CONTEXT_FIELDS): Drop buffer, beg, and unibyte fields; add arrays of character codes stored as C ints. (Freplace_region_contents): Generate such arrays from the two regions to be compared. (buffer_chars_equal): Compare array elements instead of buffer characters. diff --git a/src/editfns.c b/src/editfns.c index 84f1e5cef03..50391a7ce0b 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -1895,15 +1895,9 @@ determines whether case is significant or ignored. */) #define OFFSET ptrdiff_t #define EXTRA_CONTEXT_FIELDS \ - /* Buffers to compare. */ \ - struct buffer *buffer_a; \ - struct buffer *buffer_b; \ - /* BEGV of each buffer */ \ - ptrdiff_t beg_a; \ - ptrdiff_t beg_b; \ - /* Whether each buffer is unibyte/plain-ASCII or not. */ \ - bool a_unibyte; \ - bool b_unibyte; \ + /* Arrays of character codes to compare. */ \ + int *chars_a; \ + int *chars_b; \ /* Bit vectors recording for each character whether it was deleted or inserted. */ \ unsigned char *deletions; \ @@ -2098,17 +2092,25 @@ a buffer or a string. But this is deprecated. */) } Lisp_Object source_buffer = make_lisp_ptr (b, Lisp_Vectorlike); + /* Copy the characters to arrays of C integers. This speeds up + comparison dramatically in multibyte buffers. */ + int *chars_a = SAFE_ALLOCA (sizeof (chars_a[0]) * size_a); + for (ptrdiff_t p = min_a; p < min_a + size_a; p++) + chars_a[p - min_a] + = BUF_FETCH_CHAR_AS_MULTIBYTE (a, buf_charpos_to_bytepos (a, p)); + + int *chars_b = SAFE_ALLOCA (sizeof (chars_b[0]) * size_b); + for (ptrdiff_t p = min_b; p < min_b + size_b; p++) + chars_b[p - min_b] + = BUF_FETCH_CHAR_AS_MULTIBYTE (b, buf_charpos_to_bytepos (b, p)); + /* FIXME: It is not documented how to initialize the contents of the context structure. This code cargo-cults from the existing caller in src/analyze.c of GNU Diffutils, which appears to work. */ struct context ctx = { - .buffer_a = a, - .buffer_b = b, - .beg_a = min_a, - .beg_b = min_b, - .a_unibyte = BUF_ZV (a) == BUF_ZV_BYTE (a), - .b_unibyte = BUF_ZV (b) == BUF_ZV_BYTE (b), + .chars_a = chars_a, + .chars_b = chars_b, .deletions = deletions_insertions, .insertions = deletions_insertions + del_bytes, .fdiag = buffer + size_b + 1, @@ -2219,17 +2221,17 @@ bit_is_set (const unsigned char *a, ptrdiff_t i) return a[i / CHAR_BIT] & (1 << (i % CHAR_BIT)); } -/* Return true if the characters at position POS_A of buffer - CTX->buffer_a and at position POS_B of buffer CTX->buffer_b are +/* Return true if the characters at position POS_A in the first stretch + of text to be compared and at position POS_B of the second one are equal. POS_A and POS_B are zero-based. Text properties are ignored. Implementation note: this function is called inside the inner-most loops of compareseq, so it absolutely must be optimized for speed, - every last bit of it. E.g., each additional use of BEGV or such - likes will slow down replace-buffer-contents by dozens of percents, - because builtin_lisp_symbol will be called one more time in the - innermost loop. */ + every last bit of it. E.g., each use of BEGV or such likes will slow + down replace-buffer-contents by dozens of percents, because + builtin_lisp_symbol will be called one more time in the innermost + loop. */ static bool buffer_chars_equal (struct context *ctx, @@ -2242,29 +2244,7 @@ buffer_chars_equal (struct context *ctx, sys_longjmp (ctx->jmp, 1); } - pos_a += ctx->beg_a; - pos_b += ctx->beg_b; - - ptrdiff_t bpos_a = - ctx->a_unibyte ? pos_a : buf_charpos_to_bytepos (ctx->buffer_a, pos_a); - ptrdiff_t bpos_b = - ctx->b_unibyte ? pos_b : buf_charpos_to_bytepos (ctx->buffer_b, pos_b); - - /* We make the below a series of specific test to avoid using - BUF_FETCH_CHAR_AS_MULTIBYTE, which references Lisp symbols, and - is therefore significantly slower (see the note in the commentary - to this function). */ - if (ctx->a_unibyte && ctx->b_unibyte) - return BUF_FETCH_BYTE (ctx->buffer_a, bpos_a) - == BUF_FETCH_BYTE (ctx->buffer_b, bpos_b); - if (ctx->a_unibyte && !ctx->b_unibyte) - return UNIBYTE_TO_CHAR (BUF_FETCH_BYTE (ctx->buffer_a, bpos_a)) - == BUF_FETCH_MULTIBYTE_CHAR (ctx->buffer_b, bpos_b); - if (!ctx->a_unibyte && ctx->b_unibyte) - return BUF_FETCH_MULTIBYTE_CHAR (ctx->buffer_a, bpos_a) - == UNIBYTE_TO_CHAR (BUF_FETCH_BYTE (ctx->buffer_b, bpos_b)); - return BUF_FETCH_MULTIBYTE_CHAR (ctx->buffer_a, bpos_a) - == BUF_FETCH_MULTIBYTE_CHAR (ctx->buffer_b, bpos_b); + return ctx->chars_a[pos_a] == ctx->chars_b[pos_b]; } static bool