Repository navigation
runtime: build-time init snapshot census (instr-only): Supabase check differ=0, D 1,643 (93.9%), production bytes = main #13935
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
abe1d47
8178b4a
942edda
fb35b74
141816f
c0e6c0b
f9c3343
231e1f4
282f40d
20f98f1
b787b33
f100398
38d75c6
0d6cc87
93a125b
9f4a2db
3648b80
194405e
4ef5a8a
777531e
ebee3b1
62e8b30
1a9d535
57eef33
4d17b80
a29a64b
ddc8363
2d4e3f0
0d2b991
6ae688e
bcfe3b1
4a5e7d3
e9e6b50
1e31a04
f0c34b9
0da6530
351d152
0853e13
82779af
d1c74ad
e875bac
75e577e
a916ecc
8f3d5df
a610085
349b9d1
310e4e1
07965bf
c076654
49c95de
d7b18c6
aff6899
05ff2cf
997a26a
b3bfa0e
73bea41
f670222
76185f4
6269431
afddbb8
2271779
8fa8ddf
3cb4a26
a095249
3c091e0
a4ea4f1
5cb9986
2ecbc93
1c81323
f34c2cc
ab9e6d8
776e99b
a391626
a27c19f
3db25cf
edb7f5f
becba6d
4107e60
c24e54f
2b486a0
c2bd0d8
7a097d0
b59277e
ffe56c3
5bf0820
352b0b4
5eb71be
3ad95e7
d14976c
b5623eb
b3f7ea7
6913231
71e036e
9e2f5c2
e69fb22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Instrumented builds gain the startup-snapshot census (`PERRY_SNAP_CENSUS=<out>` on a binary compiled against the instr runtime): it reports which module inits a startup image could hold, with env cells, one environment guard, the entry body, key-granular globalThis locations and a check run. It is off by default and absent from production builds, whose code and startup cost are unchanged. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -192,6 +192,16 @@ pub(super) fn compile_module_entry( | |
| llmod.declare_function(&format!("{}__init", prefix), VOID, &[]); | ||
| llmod.declare_function(&format!("__perry_prepare_literals_{}", prefix), VOID, &[]); | ||
| } | ||
| if std::env::var_os("PERRY_SNAP_CENSUS").is_some_and(|v| !v.is_empty()) { | ||
| llmod.declare_function("js_snap_entry_body", VOID, &[]); | ||
| llmod.declare_function("js_snap_tls_program", VOID, &[PTR, PTR, PTR, PTR]); | ||
|
Comment on lines
+195
to
+197
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| for anchor in crate::stubs::SNAP_TLS_ANCHORS { | ||
| llmod.add_external_tls_global(anchor, "i8", "localexec"); | ||
|
Comment on lines
+198
to
+199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| if entry_in_init { | ||
| llmod.declare_function("js_snap_entry_module", VOID, &[]); | ||
| } | ||
| } | ||
| // Issue #753: emit a no-op `<entry_prefix>__init` stub so the | ||
| // dispatch site in some other module that does `await | ||
| // import("./entry.ts")` resolves at link time. The entry | ||
|
|
@@ -468,6 +478,16 @@ pub(super) fn compile_module_entry( | |
| let _ = main.create_block("entry"); | ||
| { | ||
| let blk = main.block_mut(0).unwrap(); | ||
| // SNAP census: the program's TLS slots lie between the anchors the | ||
| // driver links before and after the program objects; the runner | ||
| // protects those pages only (never runtime thread-locals). | ||
| if std::env::var_os("PERRY_SNAP_CENSUS").is_some_and(|v| !v.is_empty()) { | ||
| let a = crate::stubs::SNAP_TLS_ANCHORS.map(|n| format!("@{n}")); | ||
| blk.call_void( | ||
| "js_snap_tls_program", | ||
| &[(PTR, &a[0]), (PTR, &a[1]), (PTR, &a[2]), (PTR, &a[3])], | ||
| ); | ||
| } | ||
| if let Some((const_name, byte_len)) = process_entry_path.as_ref() { | ||
| let path_ptr = format!("@{}", const_name); | ||
| let len_str = byte_len.to_string(); | ||
|
|
@@ -713,6 +733,11 @@ pub(super) fn compile_module_entry( | |
| } | ||
| blk.call_void(&format!("{}__init", prefix), &[]); | ||
| } | ||
| // SNAP census (`PERRY_SNAP_CENSUS` set while compiling, with | ||
| // `PERRY_GC_INSTRUMENTS=1`): the entry's own body starts here. | ||
| if std::env::var_os("PERRY_SNAP_CENSUS").is_some_and(|v| !v.is_empty()) { | ||
| blk.call_void("js_snap_entry_body", &[]); | ||
| } | ||
| } | ||
| // Mark the boundary between init prelude and user code so | ||
| // hoisted post-init setup (cached `@perry_class_keys_*` loads | ||
|
|
@@ -1131,6 +1156,10 @@ pub(super) fn compile_module_entry( | |
| buffer_alias_scopes, | ||
| }; | ||
| if entry_in_init { | ||
| // SNAP census: the entry module's evaluation is the entry body. | ||
| if std::env::var_os("PERRY_SNAP_CENSUS").is_some_and(|v| !v.is_empty()) { | ||
| ctx.block().call_void("js_snap_entry_module", &[]); | ||
| } | ||
| ctx.block() | ||
| .call_void(&format!("{}__init", module_prefix), &[]); | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| //! SNAP census: the consumer kind of a `process.env` read site | ||
| //! (`secret-tests/nightly/CW/SNAP_DESIGN_2026-10-30.md` §2.10.5), emitted in | ||
| //! instrumented compiles only (`PERRY_SNAP_CENSUS` set while compiling, the | ||
| //! switch `js_snap_entry_body` uses). An ordinary compile emits nothing. | ||
| //! | ||
| //! A read is a *store* site when its value is directly the stored value of a | ||
| //! module-global binding (`let`/`const` lowered to a module global, an | ||
| //! assignment to one, `GlobalSet`), a property or index set, or an element of | ||
| //! an array or object literal. Every other read is an *observe* site. Codegen | ||
| //! calls `js_snap_env_site(kind)` right before the read's runtime call; the | ||
| //! runtime keeps the kind for that frame's next `js_getenv`. No table is | ||
| //! emitted: the kind is the call's argument. | ||
|
|
||
| use std::cell::RefCell; | ||
|
|
||
| use perry_hir::Expr; | ||
|
|
||
| use crate::types::I32; | ||
|
|
||
| use super::FnCtx; | ||
|
|
||
| /// `js_snap_env_site` kinds (the runtime reads the same numbers). | ||
| const OBSERVE: i32 = 0; | ||
| const STORE: i32 = 1; | ||
|
|
||
| thread_local! { | ||
| /// Env-read expressions (by address) whose parent stores their value, | ||
| /// noted when the parent is lowered and taken when the read is. | ||
| static STORE_SITES: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) }; | ||
| } | ||
|
|
||
| pub(crate) fn census_compile() -> bool { | ||
| std::env::var_os("PERRY_SNAP_CENSUS").is_some_and(|v| !v.is_empty()) | ||
| } | ||
|
|
||
| fn is_env_read(e: &Expr) -> bool { | ||
| matches!(e, Expr::EnvGet(_) | Expr::EnvGetDynamic(_)) | ||
| } | ||
|
|
||
| fn note<'e>(children: impl Iterator<Item = &'e Expr> + Clone) { | ||
| if children.clone().any(is_env_read) && census_compile() { | ||
| STORE_SITES.with(|s| { | ||
| let mut s = s.borrow_mut(); | ||
| for c in children.filter(|c| is_env_read(c)) { | ||
| s.push(c as *const Expr as usize); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /// `expr` is about to be lowered: when it stores the value of a direct | ||
| /// env-read child, that child is a store site. | ||
| pub(crate) fn note_store_parent(ctx: &FnCtx<'_>, expr: &Expr) { | ||
| match expr { | ||
| Expr::GlobalSet(_, v) => note(std::iter::once(&**v)), | ||
| Expr::LocalSet(id, v) if ctx.module_globals.contains_key(id) => note(std::iter::once(&**v)), | ||
| Expr::PropertySet { value, .. } | Expr::IndexSet { value, .. } => { | ||
| note(std::iter::once(&**value)) | ||
| } | ||
| Expr::Object(fields) => note(fields.iter().map(|(_, v)| v)), | ||
|
Comment on lines
+55
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| // A plain object literal lowers to `new __AnonShape_*(values...)`: | ||
| // the literal shape has no constructor, its values are its fields. | ||
| Expr::New { | ||
| class_name, args, .. | ||
| } if ctx | ||
| .classes | ||
| .get(class_name) | ||
| .is_some_and(|c| c.is_literal_shape()) => | ||
| { | ||
| note(args.iter()) | ||
| } | ||
| Expr::Array(elems, _) => note(elems.iter()), | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
||
| /// A `let`/`const` binding `id` with initializer `init` is being lowered. | ||
| pub(crate) fn note_let(ctx: &FnCtx<'_>, id: u32, init: Option<&Expr>) { | ||
| if let Some(init) = init { | ||
| if ctx.module_globals.contains_key(&id) { | ||
| note(std::iter::once(init)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Emit the site hook for the env read `expr`, right before its runtime call. | ||
| pub(crate) fn emit(ctx: &mut FnCtx<'_>, expr: &Expr) { | ||
| if !census_compile() { | ||
| return; | ||
| } | ||
| let addr = expr as *const Expr as usize; | ||
| let store = STORE_SITES.with(|s| { | ||
| let mut s = s.borrow_mut(); | ||
| match s.iter().position(|&a| a == addr) { | ||
| Some(i) => { | ||
| s.swap_remove(i); | ||
| true | ||
| } | ||
| None => false, | ||
| } | ||
| }); | ||
| let kind = if store { STORE } else { OBSERVE }; | ||
| ctx.block() | ||
| .call_void("js_snap_env_site", &[(I32, &kind.to_string())]); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -374,7 +374,16 @@ return crate::lower_array_record_length::lower(ctx, &args[0]); | |
| let init_fn = format!("{prefix}__init"); | ||
| ctx.pending_declares | ||
| .push((init_fn.clone(), crate::types::VOID, vec![])); | ||
| // SNAP census: the evaluation this guard starts was reached | ||
| // by `require`, not by `import()` (instrumented compiles only). | ||
| let census = crate::expr::snap_env_site::census_compile(); | ||
| if census { | ||
| ctx.block().call_void("js_snap_require_edge", &[(crate::types::I32, "1")]); | ||
| } | ||
| ctx.block().call_void(&init_fn, &[]); | ||
| if census { | ||
| ctx.block().call_void("js_snap_require_edge", &[(crate::types::I32, "0")]); | ||
| } | ||
|
Comment on lines
+380
to
+386
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return lower_expr(ctx, &args[0]); | ||
| } | ||
| "isBuiltin" => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -434,10 +434,37 @@ pub(super) unsafe fn map(kind: Kind, mapped: usize) -> *mut u8 { | |
| d.publication | ||
| .store(serial() | kind_bits(kind) | MAPPED, SeqCst); | ||
| } | ||
| crate::snap_census::region_mapped(base, mapped); | ||
| return data.cast(); | ||
| } | ||
| std::ptr::null_mut() | ||
| } | ||
| /// `[base, base + len)` of the reservation's payload (empty before it | ||
| /// exists). The SNAP census sizes its page map with it. | ||
| #[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))] | ||
| pub(crate) fn payload_bounds() -> (usize, usize) { | ||
| initialized().map_or((0, 0), |r| (r.base, r.base + r.len)) | ||
| } | ||
|
|
||
| /// Every mapped region, `[base, end)`, once each. | ||
| #[cfg(all(perry_gc_instruments, target_os = "linux", target_arch = "x86_64"))] | ||
| pub(crate) fn for_each_mapped(mut f: impl FnMut(usize, usize)) { | ||
| let Some(r) = existing() else { | ||
| return; | ||
| }; | ||
| let mut slot = 0; | ||
| while slot < r.len / ALIGN { | ||
| let d = r.descriptor(slot); | ||
| if d.publication.load(SeqCst) & MAPPED != 0 { | ||
| let (base, end) = (d.base.load(SeqCst), d.end.load(SeqCst)); | ||
| f(base, end); | ||
| slot = (end - r.base).div_ceil(ALIGN).max(slot + 1); | ||
|
Comment on lines
+451
to
+461
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } else { | ||
| slot += 1; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub(super) unsafe fn unmap(data: *mut u8, mapped: usize) { | ||
| let r = existing().expect("region reservation missing"); | ||
| let _guard = r.allocation.lock().unwrap_or_else(|e| e.into_inner()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: PerryTS/perry
Length of output: 713
Script executed:
Repository: PerryTS/perry
Length of output: 21646
Script executed:
Repository: PerryTS/perry
Length of output: 42371
Script executed:
Repository: PerryTS/perry
Length of output: 42706
Script executed:
Repository: PerryTS/perry
Length of output: 21361
Script executed:
Repository: PerryTS/perry
Length of output: 24913
Script executed:
Repository: PerryTS/perry
Length of output: 15843
Document the compile-time census setting.
Set
PERRY_SNAP_CENSUSto a nonempty value during compilation as well as when running the binary. Setting it only at runtime can produce an incomplete census: the entry-body and TLS tracking hooks are missing.