Skip to content

[WIP] Investigate crashes after launching with doom emacs - #14

Closed
eval-exec with Copilot wants to merge 1 commit into
mainfrom
copilot/investigate-emacs-crash-issue
Closed

eval-exec with Copilot wants to merge 1 commit into
mainfrom
copilot/investigate-emacs-crash-issue

Conversation

Copilot AI commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for assigning this issue to me. I'm starting to work on it and will keep this PR's description up to date as I form a plan and make progress.

Original prompt

This section details on the original issue you should resolve

<issue_title>Crashes soon after launching with doom emacs</issue_title>
<issue_description>Using -Q it does not crash

log.txt</issue_description>

<agent_instructions>Investigate</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@eval-exec Could you have core dump?

What's coredumpctl gdb --debugger-arguments='-batch -ex "bt full"'?</comment_new>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI requested a review from eval-exec February 7, 2026 16:19
@eval-exec eval-exec closed this Feb 26, 2026
eval-exec added a commit that referenced this pull request Apr 9, 2026
GNU src/search.c:2485-2505 classifies the matched text for the
FIXEDCASE=nil branch of replace-match by checking the buffer's
syntax table:
  if (SYNTAX(prevc) != Sword
      && !(case_symbols_as_words && SYNTAX(prevc) == Ssymbol))
    some_nonuppercase_initial = 1;

Audit findings #14 and #20 in drafts/regex-search-audit.md flagged
that neomacs's apply_replace_match_case used Rust's Unicode
is_alphanumeric() for the previous-character check and never read
case-symbols-as-words at all (the variable existed only in the
doc table).

This patch:

1. casefiddle.rs::apply_replace_match_case now delegates to a new
   apply_replace_match_case_with that takes a FnMut(char) -> bool
   word predicate. The default predicate uses GNU's standard
   syntax table baseline (ASCII letters and digits + $ %, the
   chars neomacs SyntaxTable::new_standard puts in Sword).

2. regex.rs gains compute_replacement_with_syntax,
   replace_match_buffer_with_syntax, and
   replace_match_string_with_syntax. They thread an
   Option<&SyntaxTable> and a case_symbols_as_words: bool down to
   apply_match_case_with_syntax, which builds a closure that
   consults table.char_syntax(ch) and accepts SyntaxClass::Symbol
   when case_symbols_as_words is on. The non-_with_syntax variants
   still exist as wrappers that pass None/false to keep external
   call sites unchanged.

3. builtins/search.rs::builtin_replace_match reads
   case-symbols-as-words from the dynamic environment and threads
   the bool through builtin_replace_match_with_state_and_flags,
   which routes string replacements to
   replace_match_string_with_syntax (None table for the pure
   string-arg path, Some(&buf.syntax_table) for the buffer-source
   path) and routes buffer replacements to
   replace_match_buffer_with_syntax. The legacy
   builtin_replace_match_with_state delegates to the new
   _and_flags helper with case_symbols_as_words=false so the
   bytecode VM call site continues to work.

Existing tests stay green; the change is shape-preserving for
the default case-symbols-as-words=nil baseline (which is GNU's
default too) and only diverges from the previous behavior for
inputs where Rust's Unicode is_alphanumeric disagrees with the
buffer's standard syntax table.
eval-exec added a commit that referenced this pull request Apr 9, 2026
)

GNU re_compile_fastmap at regex-emacs.c:3170-3186 reads the buffer
syntax table for both \sX and \SX, iterating every ASCII byte and
asking the table what class it has. neomacs's compile_fastmap
hardcoded Rust Unicode predicates for syntax classes 0-3
(Whitespace/Punctuation/Word/Symbol) and silently dropped classes
4-15 (Open/Close/Quote/StringDelim/Math/Escape/CharQuote/
Comment/EndComment/InheritStd/CommentFence/StringFence). For
patterns like \s( the fastmap was empty, which made the matcher
do unnecessary work but did not break correctness — see audit
finding #16 in drafts/regex-search-audit.md.

This patch:

1. Factors a fastmap_for_syntax_class helper that takes a class
   byte and a negate flag, decodes the SyntaxClass via
   SyntaxClass::from_code, walks 0..=127 against
   SyntaxTable::new_standard, and conservatively allows every
   non-ASCII byte (since the matcher does the per-character
   syntax lookup at match time). Unknown class bytes fall through
   to the GNU "set every byte" fallback.

2. The SyntaxSpec and NotSyntaxSpec arms of compile_fastmap
   delegate to the new helper. The duplicated Rust-Unicode
   classification block is gone.

We use the standard syntax table here because compile_fastmap
runs without a per-buffer table in scope; this matches GNU's
behavior for the standard table and is the same baseline used by
apply_replace_match_case (audit #14). All existing regex tests
stay green.
eval-exec added a commit that referenced this pull request May 30, 2026
DIVERGENCE #14: overlay priority zero vs negative
Neomacs: ERR (args-out-of-range 35 35)
GNU Emacs: ERR (args-out-of-range 35)
Same args-out-of-range format divergence pattern.

7 pass, 1 diverge. 632 total, 14 divergences.
eval-exec added a commit that referenced this pull request Jul 3, 2026
…xes (R1c-sidecar)

Widens AOT from the pure subset to reloc- and symbol-bearing leaves (the real
loadup functions), and folds in the correctness fixes from the adversarial audit.

PER-THREAD SIDECAR: session-specific base addresses (reloc_base + deopt buffers)
can't be baked into a .so loaded cross-session, and the buffers are per-(thread,
leaf) (thread-local heap + COMPILED). So the entry ABI gains a 4th arg — a Rust-
owned Box<LeafSidecar> field on the (already per-thread) CompiledLeaf, passed by
invoke_native via &self (reentrancy-safe per-frame). JIT declares-and-ignores the
4th param (keeps iconst, byte-identical); AOT loads bases from it. aot:bool flag
gates ONLY base materialization. All 3 entry-ptr transmute sites audited + updated.

AUDIT FIXES (the cross-session correctness class the pure-subset tests missed):
- #16 CRITICAL — symbols baked session-specific SymId. const_relocs_for_aot now
  relocs non-nil/t symbols BY NAME (recipe -> intern at load), not iconst. nil/t
  (pre-seeded SymId 0/1) + chars (fixnums) stay baked. The gensym hole the fix
  opens is closed: write_value_recipe rejects non-canonical (uninterned) symbols
  via is_canonical_id -> leaf stays JIT.
- #17 — mir_is_aot_runnable rejects CallBuiltin/CallBuiltinSym (Op-Debug hash is
  non-canonical for raw SymId); guard noted for future SymId-bearing ops.
- #15 — ABI_TAG salted with the full declare_rt_refs shim set.
- #4-9/#12 — fail-closed descriptor: validate magic/version/ABI_TAG before
  recipe_len, MAX_RECIPE_LEN cap, checked_add, bounded recursion + length checks
  (rebuild_value -> Option -> JIT fallback, never abort).
- #1/#2/#10/#11 — doc-invariant fixes (false "COMPILED never evicts" spec-slot
  justification -> "heap identity stable during native execution"; LOADED_UNITS
  outlives COMPILED; alloc-black cross-link).

DEFERRED (flagged): call-bearing AOT (precise-deopt-across-call + host shim export,
task #32); load-time const-vec recheck (#11/#14, defense behind trusted-dir).

Validated: AOT-off full suite byte-identical (7471 pass; the 1 process/timer FAIL
is a confirmed env flake — 4/4 in isolation, runs interpreted so the codegen change
can't reach it; 5 benign load-test timeouts). NEOVM_AOT=force reloc+symbol gate
green (13 AOT tests incl both #16 proofs + gensym-rejected; 119 jit+aot unit).
eval-exec added a commit that referenced this pull request Jul 3, 2026
… R1c (#32)

Completes R1c's runnable subset: AOT now covers call/apply- and reloc/symbol-bearing
leaves (the real loadup functions), with the sidecar-audit fixes folded in.

CALL-BEARING:
- Host shim export: the 6 MIR shims are #[unsafe(no_mangle)] pub extern "C" + a
  #[used] anchor (survives --gc-sections); build.rs emits cargo:rustc-link-arg-tests
  =-rdynamic + --export-dynamic-symbol=<shim> (the `wild` linker needs the explicit
  per-symbol export beyond -rdynamic) so a dlopen'd call/cons .so resolves its
  neovm_jit_* imports instead of aborting on first call (audit #3).
- mir_is_aot_runnable widened to Call/Apply + escaping-cons (still rejects
  CallBuiltin/Sym, #17). Descriptor carries has_precise_deopt/max_depth/
  has_side_effects from the MIR. The precise-deopt-across-a-real-call path (already
  lowered in the sidecar via the sidecar spill/meta bases) is enabled + proven:
  resume-at-pc, the call's side effect runs EXACTLY once.

AUDIT FIXES (sidecar audit wuymgwd3k):
- #A eq-identity (Important): AOT no longer rebuilds string/cons constants as fresh
  objects (which diverged from interp/JIT on eq via deopt/escape). The load path now
  sources reloc_data from the LIVE func.constants (re-collected in emit-time order)
  — eq-identical, exactly like the JIT tier. The descriptor recipe is demoted to
  VERIFICATION (re-encode the live reloc set, compare to the .so's recipe; mismatch →
  JIT), which ALSO closes the const-vec-recheck + truncated-hash-collision gaps
  (#11/#14) and simplifies GC (reloc consts are already-live/rooted).
- #B non-UTF-8 (Important): write_value_recipe encoded names via UTF-8 and PANICKED
  on raw-byte elisp names (reachable every cache miss). Now byte-faithful
  (resolve_sym_lisp_string / LispString::from_emacs_bytes) — round-trips any name,
  never panics. Strings too.
- Minors: deopt-base iconst materialization restored to the COLD deopt block for
  aot=false (no JIT hot-path delta); checked_add on descriptor recipe_len; max_depth
  cap; allocate-black doc corrected to the real invariant.

Validated: AOT-off full suite byte-identical (7470 passed, 0 real failures, 9 benign
load::tests timeouts). Integration test tests/aot_call_bearing.rs (NEOVM_AOT=force +
NEOVM_AOT_DIR via the public try_run_compiled path) proves interp==JIT==AOT for a
call-bearing body INCLUDING side-effect-EXACTLY-once across a forced precise deopt
AND eq-across-deopt (the #A proof). Builds clean.

R1c complete (pure + reloc + symbol + call-bearing). Next: R2 (dump-time loadup AOT).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crashes soon after launching with doom emacs

2 participants