pub(crate) mod autoloads_image;
pub(crate) mod buffer_image;
pub(crate) mod charset_image;
pub(crate) mod coding_system_image;
pub mod convert;
pub(crate) mod face_image;
pub(crate) mod mapped_heap;
pub(crate) mod mmap_image;
pub(crate) mod obarray_image;
pub(crate) mod object_extra;
pub(crate) mod object_starts;
pub(crate) mod object_value_codec;
pub(crate) mod roots_image;
pub mod runtime;
pub(crate) mod runtime_managers_image;
pub(crate) mod symbol_table_image;
pub mod types;
pub(crate) mod value_fixups;
use std::path::Path;
use std::sync::OnceLock;
use self::convert::*;
use self::mmap_image::{DumpSectionKind, ImageSection};
use self::runtime::*;
use self::types::{DumpContextState, DumpTaggedHeap};
use crate::emacs_core::charset::{
CharsetRegistrySnapshot, restore_charset_registry, snapshot_charset_registry,
};
use crate::emacs_core::eval::Context;
use crate::emacs_core::fontset::{
FontsetRegistrySnapshot, restore_fontset_registry, snapshot_fontset_registry,
};
use crate::emacs_core::value;
const AFTER_PDUMP_LOAD_HOOK_PENDING_SYMBOL: &str = "neovm--after-pdump-load-hook-pending";
const FORMAT_VERSION: u32 = 47;
const FINGERPRINT_PLACEHOLDER: [u8; 32] = *b"NEOMACS_PDUMP_FINGERPRINT_SLOT!!";
#[repr(C)]
struct ExecutableFingerprintRecord {
magic_start: [u8; 16],
fingerprint: [u8; 32],
magic_end: [u8; 16],
}
#[used]
static NEOMACS_PDUMP_FINGERPRINT_RECORD: ExecutableFingerprintRecord =
ExecutableFingerprintRecord {
magic_start: *b"NEOMACS-FP-START",
fingerprint: FINGERPRINT_PLACEHOLDER,
magic_end: *b"NEOMACS-FP-END!!",
};
pub fn fingerprint_hex() -> &'static str {
static HEX: OnceLock<String> = OnceLock::new();
HEX.get_or_init(|| hex_string(&fingerprint_bytes()))
}
fn fingerprint_bytes() -> [u8; 32] {
unsafe {
std::ptr::read_volatile(std::ptr::addr_of!(
NEOMACS_PDUMP_FINGERPRINT_RECORD.fingerprint
))
}
}
fn hex_string(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
use std::fmt::Write as _;
let _ = write!(&mut out, "{byte:02X}");
}
out
}
#[derive(Debug)]
pub enum DumpError {
Io(std::io::Error),
BadMagic,
UnsupportedVersion(u32),
FingerprintMismatch { expected: String, found: String },
ChecksumMismatch,
ImageFormatError(String),
SerializationError(String),
DeserializationError(String),
}
impl std::fmt::Display for DumpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DumpError::Io(e) => write!(f, "I/O error: {e}"),
DumpError::BadMagic => write!(f, "not a valid pdump file (bad magic)"),
DumpError::UnsupportedVersion(v) => write!(f, "unsupported pdump version {v}"),
DumpError::FingerprintMismatch { expected, found } => write!(
f,
"pdump fingerprint mismatch (expected {expected}, found {found})"
),
DumpError::ChecksumMismatch => write!(f, "pdump checksum mismatch (corrupted file)"),
DumpError::ImageFormatError(s) => write!(f, "pdump image format error: {s}"),
DumpError::SerializationError(s) => write!(f, "serialization error: {s}"),
DumpError::DeserializationError(s) => write!(f, "deserialization error: {s}"),
}
}
}
impl std::error::Error for DumpError {}
impl From<std::io::Error> for DumpError {
fn from(e: std::io::Error) -> Self {
DumpError::Io(e)
}
}
fn empty_lisp_string() -> types::DumpLispString {
types::DumpLispString {
data: Vec::new(),
size: 0,
size_byte: 0,
}
}
fn empty_context_state() -> DumpContextState {
DumpContextState {
symbol_table: types::DumpSymbolTable {
names: Vec::new(),
symbols: Vec::new(),
},
tagged_heap: types::DumpTaggedHeap {
objects: Vec::new(),
mapped_cons: Vec::new(),
mapped_floats: Vec::new(),
mapped_strings: Vec::new(),
mapped_veclikes: Vec::new(),
mapped_slots: Vec::new(),
},
obarray: types::DumpObarray {
symbols: Vec::new(),
global_members: Vec::new(),
function_unbound: Vec::new(),
function_epoch: 0,
},
dynamic: Vec::new(),
lexenv: types::DumpValue::Nil,
features: Vec::new(),
require_stack: Vec::new(),
loads_in_progress: Vec::new(),
buffers: buffer_image::empty_buffer_manager(),
autoloads: autoloads_image::empty_autoloads(),
custom: types::DumpCustomManager {
auto_buffer_local_syms: Vec::new(),
auto_buffer_local: Vec::new(),
},
modes: types::DumpModeRegistry {
major_modes: Vec::new(),
minor_modes: Vec::new(),
buffer_major_modes: Vec::new(),
buffer_minor_modes: Vec::new(),
global_minor_modes: Vec::new(),
auto_mode_alist_lisp: Vec::new(),
auto_mode_alist: Vec::new(),
custom_variables: Vec::new(),
custom_groups: Vec::new(),
fundamental_mode: types::DumpValue::Nil,
},
coding_systems: coding_system_image::empty_coding_system_manager(),
charset_registry: charset_image::empty_charset_registry(),
fontset_registry: types::DumpFontsetRegistry {
ordered_names_lisp: Vec::new(),
alias_to_name_lisp: Vec::new(),
fontsets_lisp: Vec::new(),
ordered_names: Vec::new(),
alias_to_name: Vec::new(),
fontsets: Vec::new(),
generation: 0,
},
face_table: face_image::empty_face_table(),
abbrevs: types::DumpAbbrevManager {
tables_syms: Vec::new(),
tables: Vec::new(),
global_table_sym: None,
global_table_name: empty_lisp_string(),
abbrev_mode: false,
},
interactive: types::DumpInteractiveRegistry { specs: Vec::new() },
rectangle: types::DumpRectangleState { killed: Vec::new() },
standard_syntax_table: types::DumpValue::Nil,
standard_category_table: types::DumpValue::Nil,
current_local_map: types::DumpValue::Nil,
kmacro: types::DumpKmacroManager {
current_macro: Vec::new(),
last_macro: None,
macro_ring: Vec::new(),
counter: 0,
counter_format_lisp: None,
counter_format: None,
},
registers: types::DumpRegisterManager {
registers: Vec::new(),
},
bookmarks: types::DumpBookmarkManager {
bookmarks_lisp: Vec::new(),
bookmarks: Vec::new(),
recent: Vec::new(),
},
watchers: types::DumpVariableWatcherList {
watchers: Vec::new(),
},
}
}
#[derive(Clone, Debug)]
pub struct ActiveRuntimeSnapshot {
charset_registry: CharsetRegistrySnapshot,
fontset_registry: FontsetRegistrySnapshot,
}
struct RestoreCleanup;
impl Drop for RestoreCleanup {
fn drop(&mut self) {
finish_load_interner();
}
}
pub fn dump_to_file(eval: &Context, path: &Path) -> Result<(), DumpError> {
let mut state = dump_evaluator(eval);
let symbol_table_payload = symbol_table_image::symbol_table_section_bytes(&state.symbol_table)?;
let heap_payload = mapped_heap::extract_mapped_heap_payloads(&mut state);
let object_starts_payload = object_starts::build_object_starts(&state.tagged_heap)?;
let object_extra_payload = object_extra::build_object_extra(&state.tagged_heap.objects)?;
let value_fixups_payload =
value_fixups::value_fixups_section_bytes(&heap_payload.value_fixups)?;
let obarray_payload = obarray_image::obarray_section_bytes(&state.obarray)?;
let charset_payload = charset_image::charset_section_bytes(&state.charset_registry)?;
let coding_system_payload =
coding_system_image::coding_system_section_bytes(&state.coding_systems)?;
let face_payload = face_image::face_table_section_bytes(&state.face_table)?;
let buffer_payload = buffer_image::buffer_manager_section_bytes(&state.buffers)?;
let autoloads_payload = autoloads_image::autoloads_section_bytes(&state.autoloads)?;
let runtime_managers_payload = runtime_managers_image::runtime_managers_section_bytes(
&runtime_managers_image::RuntimeManagersState::from_context_state(&state),
)?;
let roots_payload = roots_image::roots_section_bytes(&roots_image::DumpRootState {
dynamic: state.dynamic.clone(),
lexenv: state.lexenv.clone(),
features: state.features.clone(),
require_stack: state.require_stack.clone(),
loads_in_progress: state.loads_in_progress.clone(),
standard_syntax_table: state.standard_syntax_table.clone(),
standard_category_table: state.standard_category_table.clone(),
current_local_map: state.current_local_map.clone(),
})?;
let relocation_payload = mmap_image::relocation_section_bytes(&heap_payload.relocations);
let mut sections = Vec::with_capacity(
11 + usize::from(!heap_payload.bytes.is_empty())
+ usize::from(!relocation_payload.is_empty()),
);
sections.push(ImageSection {
kind: DumpSectionKind::SymbolTable,
flags: 0,
bytes: &symbol_table_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::ObjectStarts,
flags: 0,
bytes: &object_starts_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::ObjectExtra,
flags: 0,
bytes: &object_extra_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::Obarray,
flags: 0,
bytes: &obarray_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::CharsetRegistry,
flags: 0,
bytes: &charset_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::CodingSystems,
flags: 0,
bytes: &coding_system_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::FaceTable,
flags: 0,
bytes: &face_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::Buffers,
flags: 0,
bytes: &buffer_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::Roots,
flags: 0,
bytes: &roots_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::Autoloads,
flags: 0,
bytes: &autoloads_payload,
});
sections.push(ImageSection {
kind: DumpSectionKind::RuntimeManagers,
flags: 0,
bytes: &runtime_managers_payload,
});
if !heap_payload.bytes.is_empty() {
sections.push(ImageSection {
kind: DumpSectionKind::HeapImage,
flags: 0,
bytes: &heap_payload.bytes,
});
}
if !relocation_payload.is_empty() {
sections.push(ImageSection {
kind: DumpSectionKind::Relocations,
flags: 0,
bytes: &relocation_payload,
});
}
if !value_fixups_payload.is_empty() {
sections.push(ImageSection {
kind: DumpSectionKind::ValueRelocations,
flags: 0,
bytes: &value_fixups_payload,
});
}
mmap_image::write_image(path, §ions)
}
pub fn load_from_dump(path: &Path) -> Result<Context, DumpError> {
let load_start = std::time::Instant::now();
let mut image = mmap_image::load_image(path)?;
image.apply_relocations()?;
let mut state = empty_context_state();
let _cleanup = RestoreCleanup;
let symbol_table_payload = image
.section(DumpSectionKind::SymbolTable)
.ok_or_else(|| DumpError::ImageFormatError("missing symbol-table section".into()))?;
symbol_table_image::load_symbol_table_section(symbol_table_payload)?;
let object_starts_payload = image
.section(DumpSectionKind::ObjectStarts)
.ok_or_else(|| DumpError::ImageFormatError("missing object-starts section".into()))?;
let spans = object_starts::load_object_starts(object_starts_payload)?;
let object_extra_payload = image
.section(DumpSectionKind::ObjectExtra)
.ok_or_else(|| DumpError::ImageFormatError("missing object-extra section".into()))?;
let extras = object_extra::load_object_extra(object_extra_payload)?;
let tagged_heap = DumpTaggedHeap {
objects: object_extra::reconstruct_heap_objects(&extras),
mapped_cons: spans.mapped_cons,
mapped_floats: spans.mapped_floats,
mapped_strings: spans.mapped_strings,
mapped_veclikes: spans.mapped_veclikes,
mapped_slots: spans.mapped_slots,
};
let value_fixups = image
.section(DumpSectionKind::ValueRelocations)
.map(value_fixups::load_value_fixups_section)
.transpose()?
.unwrap_or_default();
let obarray_payload = image
.section(DumpSectionKind::Obarray)
.ok_or_else(|| DumpError::ImageFormatError("missing obarray section".into()))?;
state.obarray = obarray_image::load_obarray_section(obarray_payload)?;
let charset_payload = image
.section(DumpSectionKind::CharsetRegistry)
.ok_or_else(|| DumpError::ImageFormatError("missing charset-registry section".into()))?;
state.charset_registry = charset_image::load_charset_section(charset_payload)?;
let coding_system_payload = image
.section(DumpSectionKind::CodingSystems)
.ok_or_else(|| DumpError::ImageFormatError("missing coding-systems section".into()))?;
state.coding_systems = coding_system_image::load_coding_system_section(coding_system_payload)?;
let face_payload = image
.section(DumpSectionKind::FaceTable)
.ok_or_else(|| DumpError::ImageFormatError("missing face-table section".into()))?;
state.face_table = face_image::load_face_table_section(face_payload)?;
let buffer_payload = image
.section(DumpSectionKind::Buffers)
.ok_or_else(|| DumpError::ImageFormatError("missing buffers section".into()))?;
state.buffers = buffer_image::load_buffer_manager_section(buffer_payload)?;
let roots_payload = image
.section(DumpSectionKind::Roots)
.ok_or_else(|| DumpError::ImageFormatError("missing roots section".into()))?;
let roots = roots_image::load_roots_section(roots_payload)?;
state.dynamic = roots.dynamic;
state.lexenv = roots.lexenv;
state.features = roots.features;
state.require_stack = roots.require_stack;
state.loads_in_progress = roots.loads_in_progress;
state.standard_syntax_table = roots.standard_syntax_table;
state.standard_category_table = roots.standard_category_table;
state.current_local_map = roots.current_local_map;
let autoloads_payload = image
.section(DumpSectionKind::Autoloads)
.ok_or_else(|| DumpError::ImageFormatError("missing autoloads section".into()))?;
state.autoloads = autoloads_image::load_autoloads_section(autoloads_payload)?;
let runtime_managers_payload = image
.section(DumpSectionKind::RuntimeManagers)
.ok_or_else(|| DumpError::ImageFormatError("missing runtime-managers section".into()))?;
runtime_managers_image::load_runtime_managers_section(runtime_managers_payload)?
.install_into(&mut state);
let mapped_heap = image
.section_mut(DumpSectionKind::HeapImage)
.map(mapped_heap::MappedHeapView::from_mut_slice);
let mut eval = reconstruct_evaluator_after_symbol_table_with_tagged_heap(
&state,
tagged_heap,
mapped_heap,
value_fixups,
)?;
drop(_cleanup);
record_loaded_dump(path, load_start.elapsed());
mark_after_pdump_load_hook_pending(&mut eval);
eval.install_pdump_image(image);
Ok(eval)
}
pub fn snapshot_evaluator(eval: &Context) -> DumpContextState {
dump_evaluator(eval)
}
pub fn snapshot_active_evaluator(eval: &mut Context) -> DumpContextState {
eval.setup_thread_locals();
dump_evaluator(eval)
}
pub fn snapshot_active_runtime(eval: &mut Context) -> ActiveRuntimeSnapshot {
eval.setup_thread_locals();
ActiveRuntimeSnapshot {
charset_registry: snapshot_charset_registry(),
fontset_registry: snapshot_fontset_registry(),
}
}
pub fn restore_active_runtime(eval: &mut Context, snapshot: &ActiveRuntimeSnapshot) {
eval.setup_thread_locals();
restore_charset_registry(snapshot.charset_registry.clone());
restore_fontset_registry(snapshot.fontset_registry.clone());
eval.sync_thread_runtime_bindings();
eval.sync_current_thread_buffer_state();
}
pub fn restore_snapshot(state: &DumpContextState) -> Result<Context, DumpError> {
reconstruct_evaluator(state, None)
}
pub fn clone_evaluator(eval: &Context) -> Result<Context, DumpError> {
restore_snapshot(&snapshot_evaluator(eval))
}
pub fn clone_active_evaluator(eval: &mut Context) -> Result<Context, DumpError> {
restore_snapshot(&snapshot_active_evaluator(eval))
}
fn reconstruct_evaluator(
state: &DumpContextState,
mapped_heap: Option<mapped_heap::MappedHeapView>,
) -> Result<Context, DumpError> {
let _cleanup = RestoreCleanup;
load_symbol_table(&state.symbol_table)?;
let eval = reconstruct_evaluator_after_symbol_table(state, mapped_heap, &[])?;
drop(_cleanup);
Ok(eval)
}
fn reconstruct_evaluator_after_symbol_table(
state: &DumpContextState,
mapped_heap: Option<mapped_heap::MappedHeapView>,
value_fixups: &[value_fixups::RawValueFixup],
) -> Result<Context, DumpError> {
let decoder = LoadDecoder::new_with_mapped_heap_and_fixups(
&state.tagged_heap,
mapped_heap,
value_fixups.to_vec(),
);
reconstruct_evaluator_after_symbol_table_with_decoder(state, decoder)
}
fn reconstruct_evaluator_after_symbol_table_with_tagged_heap(
state: &DumpContextState,
tagged_heap_state: DumpTaggedHeap,
mapped_heap: Option<mapped_heap::MappedHeapView>,
value_fixups: Vec<value_fixups::RawValueFixup>,
) -> Result<Context, DumpError> {
let decoder = LoadDecoder::from_tagged_heap_with_mapped_heap_and_fixups(
tagged_heap_state,
mapped_heap,
value_fixups,
);
reconstruct_evaluator_after_symbol_table_with_decoder(state, decoder)
}
fn reconstruct_evaluator_after_symbol_table_with_decoder(
state: &DumpContextState,
mut decoder: LoadDecoder,
) -> Result<Context, DumpError> {
let mut tagged_heap = Box::new(crate::tagged::gc::TaggedHeap::new());
crate::tagged::gc::set_tagged_heap(&mut tagged_heap);
decoder.preload_tagged_heap()?;
reset_runtime_for_new_heap(HeapResetMode::PdumpRestore);
load_charset_registry(&mut decoder, &state.charset_registry);
load_fontset_registry(&state.fontset_registry);
let obarray = load_obarray(&mut decoder, &state.obarray)?;
let lexenv = decoder.load_value(&state.lexenv);
let features: Vec<_> = state.features.iter().map(load_sym_id).collect();
let require_stack: Vec<_> = state.require_stack.iter().map(load_sym_id).collect();
let loads_in_progress: Vec<_> = state
.loads_in_progress
.iter()
.map(load_lisp_string)
.collect();
let mut eval = Context::from_dump(
tagged_heap,
obarray,
lexenv,
features,
require_stack,
loads_in_progress,
load_buffer_manager(&mut decoder, &state.buffers),
load_autoload_manager(&mut decoder, &state.autoloads),
load_custom_manager(&state.custom),
load_mode_registry(&mut decoder, &state.modes),
load_coding_system_manager(&mut decoder, &state.coding_systems),
load_face_table(&mut decoder, &state.face_table),
load_abbrev_manager(&state.abbrevs),
load_interactive_registry(&mut decoder, &state.interactive),
load_rectangle(&state.rectangle),
decoder.load_value(&state.standard_syntax_table),
decoder.load_value(&state.standard_category_table),
decoder.load_value(&state.current_local_map),
load_kmacro(&mut decoder, &state.kmacro),
load_register_manager(&mut decoder, &state.registers),
load_bookmark_manager(&state.bookmarks),
load_watcher_list(&mut decoder, &state.watchers),
);
{
use crate::buffer::buffer::BUFFER_SLOT_INFO;
use crate::emacs_core::forward::alloc_buffer_objfwd;
use crate::emacs_core::intern::intern;
let obarray = eval.obarray_mut();
for info in BUFFER_SLOT_INFO {
if !info.install_as_forwarder {
continue;
}
let id = intern(info.name);
let predicate = if info.predicate.is_empty() {
intern("null")
} else {
intern(info.predicate)
};
let fwd = alloc_buffer_objfwd(
info.offset as u16,
info.local_flags_idx,
predicate,
info.default.to_value(),
);
obarray.install_buffer_objfwd(id, fwd);
}
}
Ok(eval)
}
fn mark_after_pdump_load_hook_pending(eval: &mut Context) {
eval.obarray_mut()
.set_symbol_value(AFTER_PDUMP_LOAD_HOOK_PENDING_SYMBOL, value::Value::T);
}
pub(crate) fn take_after_pdump_load_hook_pending(eval: &mut Context) -> bool {
let pending = eval
.obarray()
.symbol_value(AFTER_PDUMP_LOAD_HOOK_PENDING_SYMBOL)
.is_some_and(|value| value.is_truthy());
eval.obarray_mut()
.set_symbol_value(AFTER_PDUMP_LOAD_HOOK_PENDING_SYMBOL, value::Value::NIL);
pending
}
#[cfg(test)]
#[path = "pdump_test.rs"]
mod tests;