Skip to content
Merged
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/8595-entry-outline-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
perf(codegen): finish structured module-entry outlining (#8595). Entry bodies with at least 1,000 top-level HIR statements or 4,000 estimated safepoints now split automatically into ordered functions capped at roughly 200 statements or 1,000 safepoints (`PERRY_OUTLINE_ENTRY=0` disables and `=1` forces the transform). Original declarations move unchanged, bindings that need cross-function storage are promoted to rooted module globals, and declaration/export/const/static-field/early-`process.env` scans reconstruct the logical source-order entry stream. Exports, Script `globalThis` reflection, and structured control flow are supported; top-level await and module-level TDZ preallocation remain fail-safe exclusions. This bounds per-function RS4GC fan-out, instruction selection, optimization, and register allocation without changing the requested optimization level.
1 change: 1 addition & 0 deletions changelog.d/8596-transitive-leaf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
perf(gc): reduce native-root safepoint density with a whole-module Perry-GC effect closure (#8596). Direct calls to generated functions are now marked `gc-leaf-function` when every transitive path stays within proven non-collecting runtime helpers and generated callees; pure recursive SCCs are supported. Allocation/poll paths, indirect calls, unknown externals, and cross-module calls remain statepoints. The proof and annotations are shared by whole-module text emission, split codegen units, and native LLVM construction (including `invoke` inside `try`). Shadow-frame bookkeeping helpers are also classified non-collecting; their only allocation is the raw Rust shadow-buffer `Vec`, which cannot trigger Perry GC. This is the sound polling-style reduction available with LLVM statepoints: a caller edge whose callee may collect must retain its relocation map so moving GC can rewrite the suspended caller frame.
2 changes: 2 additions & 0 deletions crates/perry-api-manifest/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ pub const NATIVE_MODULES: &[&str] = &[
"perry/tui", // terminal-UI framework
"perry/yoga", // Yoga flexbox layout
"perry/ui", // native UI (AppKit/UIKit/Win32/GTK4/…)
"perry/ios", // iOS-only UIKit/Foundation Models APIs
"perry/system", // OS integration (keychain, notifications, …)
"perry/plugin", // compile-time plugin surface
"perry/widget", // home-screen widgets (WidgetKit/Glance)
Expand Down Expand Up @@ -265,6 +266,7 @@ pub const RUNTIME_ONLY_MODULES: &[&str] = &[
// (registry + fs interception); no perry-stdlib surface needed.
"perry",
"perry/ui",
"perry/ios",
"perry/system",
"perry/widget",
"perry/i18n",
Expand Down
8 changes: 8 additions & 0 deletions crates/perry-api-manifest/src/entries/part_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,14 @@ pub(crate) const API_MANIFEST_PART_4: &[ApiEntry] = &[
method("perry/media", "onTimeUpdate", false, None),
method("perry/media", "setNowPlaying", false, None),
method("perry/media", "destroy", false, None),
// --- perry/ios (issue #5536) — auto-derivable from PERRY_IOS_TABLE. ---
method("perry/ios", "getLayoutEnvironment", false, None),
method("perry/ios", "onLayoutChange", false, None),
method("perry/ios", "offLayoutChange", false, None),
method("perry/ios", "foundationModelAvailability", false, None),
method("perry/ios", "createLanguageModelSession", false, None),
method("perry/ios", "respond", false, None),
method("perry/ios", "destroyLanguageModelSession", false, None),
// --- perry/audio (issue #1867) — auto-derivable from PERRY_AUDIO_TABLE. ---
method("perry/audio", "loadSound", false, None),
method("perry/audio", "unload", false, None),
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/codegen/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1831,7 +1831,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
// name (`const bar = function namedBar(){}` ⇒ `"namedBar"`).
let mut named_inline_closure_ids: std::collections::HashSet<perry_hir::types::FuncId> =
std::collections::HashSet::new();
for stmt in &hir.init {
for stmt in super::entry_outline::logical_entry_stmts(hir) {
if let perry_hir::Stmt::Let { name, init, .. } = stmt {
if name.is_empty() || name.starts_with('_') {
continue;
Expand Down
8 changes: 5 additions & 3 deletions crates/perry-codegen/src/codegen/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ fn emit_plugin_abi_shim(llmod: &mut LlModule, hir: &HirModule, module_prefix: &s
/// (function(){ ... })()`), which is where the wrapped entry's top-level
/// statements live. Assignments nested in conditionals or inner functions are
/// deliberately skipped — those run conditionally/lazily, exactly as in Node.
fn collect_entry_env_literals(init: &[perry_hir::Stmt]) -> Vec<(String, String)> {
fn collect_entry_env_literals(hir: &HirModule) -> Vec<(String, String)> {
use perry_hir::{Expr, Stmt};

fn record(expr: &Expr, out: &mut Vec<(String, String)>) {
Expand Down Expand Up @@ -176,7 +176,9 @@ fn collect_entry_env_literals(init: &[perry_hir::Stmt]) -> Vec<(String, String)>
}

let mut out = Vec::new();
scan(init, &mut out, 0);
for stmt in super::entry_outline::logical_entry_stmts(hir) {
scan(std::slice::from_ref(stmt), &mut out, 0);
}
out
}

Expand Down Expand Up @@ -620,7 +622,7 @@ pub(super) fn compile_module_entry(
// `collect_entry_env_literals`. The "NODE_ENV"/"production" string
// handles are interned here and populated by the strings-init call
// above (the entry body also references them, so they share slots).
for (name, value) in collect_entry_env_literals(&hir.init) {
for (name, value) in collect_entry_env_literals(hir) {
let name_idx = strings.intern(&name);
let value_idx = strings.intern(&value);
let name_global = format!("@{}", strings.entry(name_idx).handle_global);
Expand Down
Loading
Loading