Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions changelog.d/8833-ecs-integration-followthrough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The ECS benchmark specializations now cover their real cross-module integration paths: short
packed spread and exact argument-shape calls receive producer metadata, imported object-literal
methods retain exact own-method capabilities through adapter parameters, and closure-captured
packed loops version nested arrays derived from guarded indexed reads. Every path keeps a guarded
generic side exit. The audit also fixes mixed fixed/spread `Math` calls incorrectly treating their
tail array as a scalar argument and restores iterator-protocol fallback for proxy and Array-subclass
spread tails. On the controlled M1 cohort, the corrected full Wolf workload is 1.73x faster and the
imported-method `perform-ecs` workload is 11.05% faster; the short-spread and argument-clone slices
activate correctly but remain performance-neutral.
132 changes: 129 additions & 3 deletions crates/perry-codegen/src/codegen/argument_shape_clone_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,12 @@ fn guarded_call_routes_to_shadow_rooted_direct_field_clone() {

assert!(
ir.contains(&format!("call double @{clone_name}(")),
"the guarded call site must route to the argument clone:\n{ir}"
"the exact contained call site must route to the argument clone:\n{ir}"
);
assert!(
ir.contains("pshape_arg.fallback")
!ir.contains("pshape_arg.fallback")
&& ir.contains("call double @perry_method_argument_shape_clone_ts__Registry__read("),
"guard failure must retain the ordinary method body:\n{ir}"
"fresh provenance must elide the redundant argument guard while other receiver routes retain the ordinary body:\n{ir}"
);
assert!(
clone.contains("@js_shadow_slot_bind(")
Expand Down Expand Up @@ -302,3 +302,129 @@ fn aliased_or_reassigned_parameter_does_not_get_a_clone() {
"a reassigned parameter must keep only generic semantics:\n{ir}"
);
}

fn define_property(target: Expr) -> Stmt {
Stmt::Expr(Expr::ObjectDefineProperty(
Box::new(target),
Box::new(Expr::String("unrelated".to_string())),
Box::new(Expr::Object(vec![("value".to_string(), Expr::Number(1.0))])),
))
}

#[test]
fn unrelated_module_shape_barrier_keeps_guarded_argument_route() {
let mut module = fixture();
module.init.insert(0, define_property(Expr::Object(vec![])));
let ir = String::from_utf8(compile_module(&module, opts()).expect("module compiles"))
.expect("LLVM IR is UTF-8");
let clone_name = "perry_method_argument_shape_clone_ts__Registry__read$pshape_args";

assert!(
ir.contains(&format!("call double @{clone_name}(")),
"an unrelated barrier must not suppress the exact guarded route:\n{ir}"
);
assert!(
!ir.contains("pshape_arg.fallback")
&& ir.contains("call double @perry_method_argument_shape_clone_ts__Registry__read("),
"an unrelated barrier must not reintroduce a redundant argument guard:\n{ir}"
);
}

#[test]
fn barrier_targeting_argument_stays_on_generic_route() {
let mut module = fixture();
module.init.insert(2, define_property(Expr::LocalGet(11)));
let ir = String::from_utf8(compile_module(&module, opts()).expect("module compiles"))
.expect("LLVM IR is UTF-8");
let clone_name = "perry_method_argument_shape_clone_ts__Registry__read$pshape_args";

assert!(
!ir.contains(&format!("call double @{clone_name}(")),
"a value reshaped before the call must not receive a containment route:\n{ir}"
);
}

#[test]
fn field_read_before_terminal_publication_gets_only_the_guarded_route() {
let mut module = fixture();
let param_id = module.classes[1].methods[0].params[0].id;
module.classes[1].methods[0]
.body
.push(Stmt::Return(Some(Expr::LocalGet(param_id))));

let session = crate::opt_report::test_support::Session::start();
let ir = String::from_utf8(compile_module(&module, opts()).expect("module compiles"))
.expect("LLVM IR is UTF-8");
let clone_name = "perry_method_argument_shape_clone_ts__Registry__read$pshape_args";
assert!(
ir.contains(&format!("call double @{clone_name}(")),
"a direct read performed before publication should use the guarded clone:\n{ir}"
);
let entries = session.entries();
assert!(
!entries.iter().any(|entry| {
entry.name == "entity"
&& entry.local_id == Some(11)
&& entry.outcome == crate::opt_report::Outcome::Selected
}),
"a publishing clone must not preserve the caller's broad Ptr<Shape> fact: {entries:#?}"
);
}

#[test]
fn field_read_after_publication_does_not_get_a_clone() {
let mut module = fixture();
let method = &mut module.classes[1].methods[0];
method
.body
.insert(0, Stmt::Expr(Expr::LocalGet(method.params[0].id)));
let ir = String::from_utf8(compile_module(&module, opts()).expect("module compiles"))
.expect("LLVM IR is UTF-8");
assert!(
!ir.contains("Registry__read$pshape_args"),
"an entry shape proof cannot license a field read after publication:\n{ir}"
);
}

#[test]
fn forwarded_clone_parameter_retains_runtime_guard_and_fallback() {
let mut module = fixture();
let entity_param = 21;
let forward = function(
201,
"forward",
vec![param(
entity_param,
"entity",
Type::Named("Entity".to_string()),
)],
vec![
Stmt::Expr(field_get(entity_param, "id")),
Stmt::Expr(Expr::Call {
callee: Box::new(Expr::PropertyGet {
object: Box::new(Expr::This),
property: "read".to_string(),
byte_offset: 0,
}),
args: vec![Expr::LocalGet(entity_param)],
type_args: Vec::new(),
byte_offset: 0,
}),
],
);
module.classes[1].methods.push(forward);
let ir = String::from_utf8(compile_module(&module, opts()).expect("module compiles"))
.expect("LLVM IR is UTF-8");

assert!(
ir.contains("Registry__forward$pshape_args")
&& ir.contains(
"call double @perry_method_argument_shape_clone_ts__Registry__read$pshape_args("
),
"the forwarding clone must route its selected parameter onward:\n{ir}"
);
assert!(
ir.contains("pshape_arg.fallback"),
"a fact inherited from a dynamic clone boundary must retain an exact guard and generic fallback:\n{ir}"
);
}
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,8 @@ pub(super) fn compile_closure(
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/emission_order_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ fn ir_opts() -> CompileOptions {
namespace_imports: Vec::new(),
namespace_member_nested: Vec::new(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
type_aliases: std::collections::HashMap::new(),
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-codegen/src/codegen/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,8 @@ pub(super) fn compile_module_entry(
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: main_native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
Expand Down Expand Up @@ -1628,6 +1630,8 @@ pub(super) fn compile_module_entry(
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: init_native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/entry/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ fn entry_opts(output_type: &str) -> CompileOptions {
namespace_imports: Vec::new(),
namespace_member_nested: Vec::new(),
imported_classes: Vec::new(),
short_spread_method_candidates: std::sync::Arc::default(),
object_literal_method_candidates: std::sync::Arc::default(),
imported_enums: Vec::new(),
imported_async_funcs: std::collections::HashSet::new(),
type_aliases: std::collections::HashMap::new(),
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,8 @@ pub(super) fn compile_function(
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/codegen/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ pub(super) fn scoped_method_name(
/// changing it desyncs cross-module symbol references (a module's prefix is
/// `sanitize(module_name)` at the definition site and must match the prefix the
/// importing module re-derives).
pub(super) fn sanitize(name: &str) -> String {
pub(crate) fn sanitize(name: &str) -> String {
let mut s: String = name
.chars()
.map(|c| {
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-codegen/src/codegen/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ pub(super) fn compile_method(
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
Expand Down Expand Up @@ -1794,6 +1796,8 @@ pub(super) fn compile_static_method(
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
short_spread_method_candidates: &cross_module.short_spread_method_candidates,
object_literal_method_candidates: &cross_module.object_literal_method_candidates,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
Expand Down
Loading
Loading