Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/13935-snap-census-protection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix instrumented startup census page protection after the first-write watcher ends in the weights run; keep bulk and late-root protection consistent with the page policy.
34 changes: 34 additions & 0 deletions crates/perry-runtime/src/calendar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,40 @@ pub(crate) fn timestamp_to_local_components(secs: i64) -> (i32, u32, u32, u32, u
/// via the `/etc/localtime` symlink; falls back to `"UTC"`. Cached for the
/// process lifetime (the host zone doesn't change under us).
pub fn host_time_zone_name() -> &'static str {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call("locale.time_zone", &format!("{:?}", ()), || {
use std::sync::OnceLock;
static TZ: OnceLock<String> = OnceLock::new();
crate::once_init::get_or_init(&TZ, || {
if let Some(raw) = std::env::var_os("TZ") {
if let Some(s) = raw.to_str() {
// A `TZ` of `:Europe/Berlin` / `:/path/zoneinfo/Europe/Berlin`
// (leading colon) or a bare IANA id. Empty `TZ` means UTC.
let s = s.trim().strip_prefix(':').unwrap_or_else(|| s.trim());
if s.is_empty() {
return "UTC".to_string();
}
if let Some(name) = iana_id_from_zoneinfo_path(s) {
return name;
}
return s.to_string();
}
}
#[cfg(unix)]
{
if let Ok(target) = std::fs::read_link("/etc/localtime") {
if let Some(name) = iana_id_from_zoneinfo_path(&target.to_string_lossy()) {
return name;
}
}
}
"UTC".to_string()
})
.as_str()
});
}

use std::sync::OnceLock;
static TZ: OnceLock<String> = OnceLock::new();
crate::once_init::get_or_init(&TZ, || {
Expand Down
22 changes: 22 additions & 0 deletions crates/perry-runtime/src/clock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
//! Numeric wall and performance clocks.
use std::time::{SystemTime, UNIX_EPOCH};
pub(crate) fn wall_millis() -> f64 {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call("clock.wall_millis", &format!("{:?}", ()), || {
crate::effect!(host_read, clock);
if let Some(now) = crate::timer::js_mock_timers_date_now() {
return now;
}
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as f64)
.unwrap_or(0.0)
});
}

crate::effect!(host_read, clock);
if let Some(now) = crate::timer::js_mock_timers_date_now() {
return now;
Expand All @@ -15,6 +29,14 @@ pub(crate) fn wall_millis() -> f64 {
/// Returns ms since performance.timeOrigin as f64.
#[no_mangle]
pub extern "C" fn js_performance_now() -> f64 {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call("clock.performance", &format!("{:?}", ()), || {
crate::effect!(host_read, clock);
crate::perf_clock::performance_now_ms()
});
}

crate::effect!(host_read, clock);
crate::perf_clock::performance_now_ms()
}
62 changes: 62 additions & 0 deletions crates/perry-runtime/src/error_stack_frames.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,28 @@ fn capture_encoded(
origin: walk::Origin,
want: usize,
) -> ([u8; MAX_CAPTURED_FRAMES * PC_CHARS], usize) {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call("stack.capture", &format!("{:?}", (want,)), || {
let mut pcs = [0usize; MAX_CAPTURED_FRAMES];
let n = walk::capture_js(origin, &mut pcs, want);
if diag_enabled() {
eprintln!(
"[stackdiag] capture: frames={n} want={want} top={:#x} fp0={:#x} pcs={:x?}",
walk::diag_stack_top(),
walk::diag_frame_pointer(),
&pcs[..n]
);
}
let mut blob = [0u8; MAX_CAPTURED_FRAMES * PC_CHARS];
if n == 0 {
return (blob, 0);
}
let len = encode_pcs(&pcs[..n], &mut blob);
(blob, len)
});
}

let mut pcs = [0usize; MAX_CAPTURED_FRAMES];
let n = walk::capture_js(origin, &mut pcs, want);
if diag_enabled() {
Expand Down Expand Up @@ -901,6 +923,14 @@ fn stack_trace_limit() -> usize {
/// it (see [`walk::origin`]).
#[inline(always)]
pub(crate) fn capture_frames_payload() -> Vec<u8> {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call("stack.payload", &format!("{:?}", ()), || {
let origin = walk::origin();
capture_frames_payload_from(origin)
});
}

let origin = walk::origin();
capture_frames_payload_from(origin)
}
Expand Down Expand Up @@ -946,6 +976,38 @@ pub(crate) fn join_stack(head: &str, lines: &str) -> String {
/// ran under (an [`ALPHABET`] index); the frame lines stop there. An empty
/// payload is a capture that found nothing.
pub(crate) fn frames_payload_to_lines(payload: &[u8]) -> String {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call(
"stack.rendered",
&format!("{:?}", (payload,)),
|| {
let (limit, payload) = match payload.split_first() {
Some((c, rest)) => match decode_char(*c) {
Some(limit) => (limit as usize, rest),
None => (RENDER_LIMIT, payload),
},
None => (RENDER_LIMIT, payload),
};
if limit == 0 {
return String::new();
}
let (blob, recorded) = match payload.iter().position(|b| *b == b'\n') {
Some(at) => (&payload[..at], std::str::from_utf8(&payload[at + 1..]).ok()),
None => (payload, None),
};
// The recorded #5247 line is the innermost frame and counts toward it.
let resolved = render_frames(blob, limit - usize::from(recorded.is_some()));
match (recorded, resolved) {
(Some(line), Some(frames)) => format!("{line}\n{frames}"),
(Some(line), None) => line.to_string(),
(None, Some(frames)) => frames,
(None, None) => " at <anonymous>".to_string(),
}
},
);
}

let (limit, payload) = match payload.split_first() {
Some((c, rest)) => match decode_char(*c) {
Some(limit) => (limit as usize, rest),
Expand Down
59 changes: 59 additions & 0 deletions crates/perry-runtime/src/i18n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,65 @@ pub extern "C" fn perry_i18n_init(

/// Detect the system locale from environment or platform APIs.
fn detect_system_locale() -> Option<String> {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call("locale.system", &format!("{:?}", ()), || {
crate::effect!(host_read, locale);
// 1. Explicit POSIX env overrides first: LANGUAGE (GNU extension,
// colon-separated list), then LC_ALL > LC_MESSAGES > LANG. A user who
// runs `LANG=de_DE.UTF-8 ./app` has stated a preference; platform
// APIs like CFLocaleCopyCurrent would otherwise mask it on macOS,
// which pinned every console binary to the Mac's system language no
// matter what the environment said. GUI launches (Finder,
// SpringBoard, …) don't set these vars, so apps still fall through
// to the platform-native detection below.
if let Ok(lang) = std::env::var("LANGUAGE") {
if let Some(first) = lang.split(':').next() {
if !first.is_empty() && first != "C" && first != "POSIX" {
return Some(first.to_string());
}
}
}
for var in &["LC_ALL", "LC_MESSAGES", "LANG"] {
if let Ok(val) = std::env::var(var) {
if !val.is_empty() && val != "C" && val != "POSIX" {
return Some(val);
}
}
}

// 2. Platform-native APIs (GUI apps and env-less environments).
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "watchos",
target_os = "tvos",
target_os = "visionos"
))]
{
if let Some(locale) = detect_apple_locale() {
return Some(locale);
}
}

#[cfg(target_os = "windows")]
{
if let Some(locale) = detect_windows_locale() {
return Some(locale);
}
}

#[cfg(target_os = "android")]
{
if let Some(locale) = detect_android_locale() {
return Some(locale);
}
}

None
});
}

crate::effect!(host_read, locale);
// 1. Explicit POSIX env overrides first: LANGUAGE (GNU extension,
// colon-separated list), then LC_ALL > LC_MESSAGES > LANG. A user who
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-runtime/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,15 @@ pub extern "C" fn js_math_hypot(a: f64, b: f64) -> f64 {
/// Math.random() -> number (0 <= x < 1)
#[no_mangle]
pub extern "C" fn js_math_random() -> f64 {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call("random.Math", &format!("{:?}", ()), || {
crate::effect!(host_read, random);
let mut rng = rand::rng();
rng.random::<f64>()
});
}

crate::effect!(host_read, random);
let mut rng = rand::rng();
rng.random::<f64>()
Expand Down
14 changes: 14 additions & 0 deletions crates/perry-runtime/src/navigator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ fn normalize_navigator_locale(locale: &str) -> Option<String> {
}

fn navigator_language() -> String {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
{
return crate::snap_census::input_call("locale.navigator", &format!("{:?}", ()), || {
crate::effect!(host_read, locale);
std::env::var("LC_ALL")
.ok()
.filter(|locale| !locale.is_empty() && locale != "C" && locale != "POSIX")
.or_else(|| std::env::var("LANG").ok())
.as_deref()
.and_then(normalize_navigator_locale)
.unwrap_or_else(|| "en-US".to_string())
});
}

crate::effect!(host_read, locale);
std::env::var("LC_ALL")
.ok()
Expand Down
12 changes: 11 additions & 1 deletion crates/perry-runtime/src/object/shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2456,7 +2456,11 @@ fn shape_descriptor_mint_fresh(
// complete descriptor.
// SAFETY: no slab reference is held; `slab()` above went out of scope.
let id = unsafe { table.slab_mut().publish_dynamic(id, record) }
.map_err(|_| ShapeDescriptorError::IdExhausted)?
.map_err(|_error| {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
snap22::snapshot("publish_error", _error as u64 + 1);
ShapeDescriptorError::IdExhausted
})?
.word
.0;
// `id` was just handed out by `alloc_shape_id`, which never reuses a
Expand Down Expand Up @@ -2730,6 +2734,8 @@ pub(crate) unsafe fn receiver_extra_summary(_obj: *const crate::object::ObjectHe
#[optimize(size)]
#[inline(never)]
fn shape_id_exhausted_abort() -> ! {
#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
snap22::snapshot("abort", 0);
eprintln!("Perry ShapeId space exhausted; refusing to publish an untracked object shape");
std::process::abort()
}
Expand Down Expand Up @@ -7405,3 +7411,7 @@ pub(crate) fn recompute_record_handle_carriers() {
}
}
}

#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
#[path = "shapes_snap22.rs"]
pub(crate) mod snap22;
58 changes: 58 additions & 0 deletions crates/perry-runtime/src/object/shapes_snap22.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Native-only diagnostic evidence; no shape authority or allocator behavior changes.
use super::*;
use std::fmt::Write;
struct Line {
bytes: [u8; 2048],
len: usize,
}
impl std::fmt::Write for Line {
fn write_str(&mut self, text: &str) -> std::fmt::Result {
if self.len + text.len() > self.bytes.len() {
return Err(std::fmt::Error);
}
self.bytes[self.len..self.len + text.len()].copy_from_slice(text.as_bytes());
self.len += text.len();
Ok(())
}
}
pub(crate) fn snapshot(label: &str, error: u64) {
let saved_errno = unsafe { *libc::__errno_location() };
let _scope = crate::snap_census::record_scope();
let mut out = Line {
bytes: [0; 2048],
len: 0,
};
let _ = write!(out, "SNAP22_SHAPES {label} error={error} counters=");
for next in SHAPE_ID_NEXT.iter().chain(EXOTIC_SHAPE_ID_NEXT.iter()) {
let _ = write!(
out,
"{:08x},",
next.load(std::sync::atomic::Ordering::Relaxed)
);
}
let table = &crate::state::state().shapes;
let slab = table.slab();
let _ = write!(
out,
" semantic={:x} slab={:?}",
SHAPE_SEMANTIC_NEXT.load(std::sync::atomic::Ordering::Relaxed),
slab.snap22_counts()
);
let mut pcs = [0; crate::error::MAX_CAPTURED_FRAMES];
let n = crate::error::capture_ips(&mut pcs);
let _ = writeln!(out, " pcs={:x?}", &pcs[..n.min(12)]);
unsafe {
let mut at = 0;
while at < out.len {
let n = libc::write(2, out.bytes.as_ptr().add(at).cast(), out.len - at);
if n > 0 {
at += n as usize;
} else if n < 0 && *libc::__errno_location() == libc::EINTR {
continue;
} else {
break;
}
}
*libc::__errno_location() = saved_errno;
}
}
7 changes: 7 additions & 0 deletions crates/perry-runtime/src/object/shapes_store/slab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,3 +733,10 @@ impl ShapeSlab {
dir.clear();
}
}

#[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))]
impl ShapeSlab {
pub(in super::super) fn snap22_counts(&self) -> (usize, u32, u32, usize) {
(self.len, self.next_row, self.chunk_end, self.region.capacity())
}
}
Loading