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
15 changes: 15 additions & 0 deletions changelog.d/10579-function-source-intern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### Performance

- **Nested `Function.prototype.toString` source is interned at tsc scale
(#10574).** Codegen already shared overlapping function bodies via
`SourcePool`, but intern turned *off* when unique-string lengths summed
past 8 MiB. A CJS bundle like `typescript/lib/_tsc.js` is ~24 MB of
nested slices of a ~6 MB module, so `__cstring` kept one copy per
function (24.3 MB, 28% of an 86 MB tsc binary). Over-budget modules now
still share into the longest parent (the CJS factory / module wrapper).
`fn.toString()` is byte-identical. The remaining unique source (~6 MB)
can be dropped with `--function-source=header` /
`PERRY_FUNCTION_SOURCE=header`, which stores
`function <name>(<params>) { /* source elided */ }` instead of the body
— enough for name extraction and parameter-name DI, not enough to
reconstruct bodies. Full interned source stays the default.
12 changes: 11 additions & 1 deletion crates/perry-codegen/src/codegen/artifact_source_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use super::helpers::{scoped_method_name, scoped_static_method_name};

pub(super) fn extend_class_method_source_text(
hir: &HirModule,
closures: &super::function_source_header::ClosureHeaders<'_>,
module_prefix: &str,
llmod: &LlModule,
user_fn_source: &mut Vec<(String, String, bool)>,
Expand All @@ -35,7 +36,16 @@ pub(super) fn extend_class_method_source_text(
if symbol.is_empty() || !llmod.has_function(&symbol) || !seen.insert(symbol.clone()) {
return;
}
user_fn_source.push((symbol, source.text.clone(), source.is_non_strict_ordinary));
user_fn_source.push((
symbol,
super::function_source_header::retained_function_text(
hir,
closures,
func_id,
&source.text,
),
source.is_non_strict_ordinary,
));
};

for class in &hir.classes {
Expand Down
28 changes: 25 additions & 3 deletions crates/perry-codegen/src/codegen/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1874,13 +1874,21 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
// above); inline closures only have a `perry_closure_*` global when
// materialized, so gate those on `materialized_closure_ids` to avoid
// referencing an undefined global (the #318/#343 clang-failure class).
// #10574: resolve closure params/kind for functions that are not
// `hir.functions` entries, so header mode keeps their names and parameters.
let closure_headers = super::function_source_header::ClosureHeaders::new(closures);
let mut user_fn_source: Vec<(String, String, bool)> = Vec::new();
for f in &hir.functions {
if let Some(src) = hir.closure_source_text.get(&f.id) {
if let Some(sym) = func_names.get(&f.id) {
user_fn_source.push((
format!("__perry_wrap_{}", sym),
src.text.clone(),
super::function_source_header::retained_function_text(
hir,
&closure_headers,
f.id,
&src.text,
),
src.is_non_strict_ordinary,
));
}
Expand All @@ -1907,14 +1915,24 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
materialized_closure_sources.sort_by_key(|(func_id, _)| **func_id);
for (func_id, src) in materialized_closure_sources {
let sym = format!("perry_closure_{}__{}", module_prefix, func_id);
user_fn_source.push((sym, src.text.clone(), src.is_non_strict_ordinary));
user_fn_source.push((
sym,
super::function_source_header::retained_function_text(
hir,
&closure_headers,
*func_id,
&src.text,
),
src.is_non_strict_ordinary,
));
}

// #9468: method/accessor bodies are raw symbols rather than closure
// wrappers. Pair retained MethodDefinition text only with symbols this
// module actually emitted; the helper also preserves the file-size gate.
super::artifact_source_text::extend_class_method_source_text(
hir,
&closure_headers,
module_prefix,
llmod,
&mut user_fn_source,
Expand Down Expand Up @@ -1943,6 +1961,10 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {

progress.checkpoint("runtime registration metadata");

let class_source_elided = super::function_source_header::elide_class_sources(hir);
let class_source_text = class_source_elided
.as_ref()
.unwrap_or(&hir.class_source_text);
emit_string_pool(
llmod,
strings,
Expand All @@ -1954,7 +1976,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
class_table,
imported_class_stubs,
&hir.class_display_names,
&hir.class_source_text,
&class_source_text,
&ctor_arity_overrides,
closure_rest_params,
closure_arities,
Expand Down
72 changes: 72 additions & 0 deletions crates/perry-codegen/src/codegen/emission_order_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,3 +611,75 @@ fn retained_source_ranges_preserve_registrations_and_ownership() {
);
}
}

/// #10574 Part 2: `--function-source=header` must drop the body and keep the
/// name plus parameter names, which is what name-extraction and DI consume.
#[test]
fn header_mode_replaces_bodies_with_a_di_header() {
let _guard = super::function_source_header::override_function_source_header_mode(true);
let mut module = empty_module("function_source_header.ts");
let mut foo = method_fn(100, "foo");
foo.params = vec![
Param {
id: 1,
name: "a".to_string(),
ty: Type::Any,
default: None,
decorators: Vec::new(),
is_rest: false,
arguments_object: None,
},
Param {
id: 2,
name: "b".to_string(),
ty: Type::Any,
default: None,
decorators: Vec::new(),
is_rest: false,
arguments_object: None,
},
];
module.functions.push(foo);
module.closure_source_text.insert(
100,
perry_hir::FunctionSourceMetadata {
text: "function foo(a, b) {\n return 'DISTINCTIVE_BODY_10574';\n}".to_string(),
is_non_strict_ordinary: true,
},
);
module
.classes
.push(plain_class(3, "Envelope", method_fn(200, "m")));
module.closure_source_text.insert(
200,
perry_hir::FunctionSourceMetadata {
text: "m() { return 'METHOD_BODY_10574'; }".to_string(),
is_non_strict_ordinary: false,
},
);
module.class_source_text.insert(
3,
"class Envelope { m() { return 'METHOD_BODY_10574'; } }".to_string(),
);
let emitted = ir(&module);
assert!(
!emitted.contains("DISTINCTIVE_BODY_10574"),
"header mode must not retain the function body"
);
assert!(
!emitted.contains("METHOD_BODY_10574"),
"header mode must not retain method or class bodies"
);
assert!(
emitted.contains("source elided"),
"header mode must emit the elided-source stand-in"
);
assert!(
emitted.contains("function foo(a, b)"),
"header must keep the name and parameter names"
);
assert!(
emitted.contains("class Envelope"),
"class toString header must keep the class name"
);
}
Loading
Loading