browser cgen: an ELF unit retired with its code is placed again when the object is mapped again

With 705dc708 a dlclose retires the object's unit, but ELF_PLACED still had
(path, base), so the same library mapped again at the same base (ALSA's
dlopen/dlclose cycles: libasound six times in HK's start-up) ran on the JIT:
3.1 MiB of libasound wasm in hp25/hp26. ELF_PLACED now keeps each object's
executable span and forgets it when code there is gone.

hp27 (UnityPlayer + system-library units): first frame 37 s, 100 presents
69 s, 40.3 presents/s at 213 s; hp25/hp26 before it: 40 s, 70-71 s, 39.8-41.2.
Left to the JIT by the first frame: ld.so 1.8 MiB (mapped by the kernel, no
unit), Mesa's libGL 0.2 MiB, UnityPlayer misses 0.1 MiB.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_1896c5b302a54696aaa92c5f4047f20b
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-09 13:14:15 -04:00
1 parent cf240bf759
commit 87176416e3
2 files changed
+19 -8

No files matched your search

+1
View File
@@ -1730,6 +1730,7 @@ pub(crate) fn leaves() -> u64 {
/// old object's functions.
pub(crate) fn on_retire(ranges: &[(u64, u64)], gone: &[(u64, u64)]) {
on_retire_pages(ranges, gone);
libprov::forget_gone(gone);
// After the bits: a lookup once the probe cache is cleared sees them.
let memory = crate::guest_bytes();
ftab(|m| {
+18 -8
View File
@@ -723,8 +723,17 @@ pub extern "C" fn gex_libprov_start() {
/// The objects that get units (file names, or `all`).
static ELF_UNITS: Mutex<Vec<String>> = Mutex::new(Vec::new());
/// Objects placed already in this process: (path, bias).
static ELF_PLACED: Mutex<Vec<(String, u64)>> = Mutex::new(Vec::new());
/// Objects placed already in this process: (path, bias, the span of their
/// executable segments).
static ELF_PLACED: Mutex<Vec<(String, u64, (u64, u64))>> = Mutex::new(Vec::new());
/// Code no longer mapped executable (`cgen::on_retire`'s `gone`): an object
/// whose code was there is placed again if it is mapped again (ALSA's
/// dlopen/dlclose cycles reload libasound at the same base), since its unit
/// was retired with the pages.
pub(crate) fn forget_gone(gone: &[(u64, u64)]) {
lock(&ELF_PLACED).retain(|(_, _, (lo, hi))| !gone.iter().any(|&(s, e)| s < *hi && *lo < e));
}
/// `name,name,...` (the page's `?elfunits=`), before the program starts.
#[no_mangle]
@@ -776,20 +785,21 @@ pub(crate) fn elf_unit_maps(mapped: &[gex_kernel::CodeMap]) -> bool {
}
let Some(bytes) = crate::with_host(|h| h.kernel.read_file(&m.path)) else { continue };
let Some(base) = elf_bias(&bytes, m.start, m.offset) else { continue };
{
let mut placed = lock(&ELF_PLACED);
if placed.iter().any(|(p, b)| *p == m.path && *b == base) {
continue;
}
placed.push((m.path.clone(), base));
if lock(&ELF_PLACED).iter().any(|(p, b, _)| *p == m.path && *b == base) {
continue;
}
let mut image = match gex_ld::elf::load(&bytes, base) {
Ok(i) => i,
Err(e) => {
crate::warn_line(&format!("gex-cgen: {}: {e:?}; no unit\n", m.path));
lock(&ELF_PLACED).push((m.path.clone(), base, (0, 0)));
continue;
}
};
let span = image.segments.iter().filter(|s| s.executable).fold((u64::MAX, 0u64), |(lo, hi), s| {
(lo.min(s.vaddr), hi.max(s.vaddr + s.bytes.len() as u64))
});
lock(&ELF_PLACED).push((m.path.clone(), base, span));
gex_ld::elf::apply_own_relative(&mut image);
crate::warn_line(&format!("gex-cgen: {} at {base:#x}: wanting its unit\n", m.path));
super::place(&image, &m.path, base, true, None);