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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions benchmarks/compiler_output/workloads.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2134,3 +2134,62 @@ name = "imported_registry_generic_fallback_retained"
consumer = "proven_this_method_direct_call"
notes_contains = "generic_dispatch_fallback=js_native_call_method_by_id"
min = 2

[workloads.issue_8775_imported_object]
source = "test-files/fixtures/issue_8775_imported_object/main.js"
kind = "imported_object_literal_method_specialization"
allow_hot_loop_conversions = true
allow_dynamic_property_runtime = true

[workloads.issue_8775_imported_object.vectorization]
min_vectorized_loops = 0
scalar_baseline = "allowed: this fixture gates cross-module own-method dispatch"
allowed_missed_reason_kinds = [
"call_instruction",
"control_flow",
"generic_not_vectorized",
"not_beneficial",
"uncountable_loop",
"unknown_trip_count",
"unsupported_instruction",
"unsupported_reduction",
]

[workloads.issue_8775_imported_object.runtime_budgets]

[[workloads.issue_8775_imported_object.stdout_checks]]
name = "imported_object_checksum"
equals = "{\"checksum\":400000,\"remaining\":0}\n"
detail = "stable imported object methods preserve the Node checksum"

[[workloads.issue_8775_imported_object.ir_checks]]
name = "direct_producer_closure_calls"
section = "llvm_before"
contains_all = [
"call double @perry_closure_adapter_js__6",
"call double @perry_closure_adapter_js__7",
"call double @perry_closure_adapter_js__8",
"call double @perry_closure_adapter_js__9",
]
detail = "stable arms directly call producer closure bodies"

[[workloads.issue_8775_imported_object.ir_checks]]
name = "generic_method_fallback_retained"
section = "llvm_before"
contains = "call double @js_native_call_method_by_id"
detail = "guard failures retain the universal method dispatcher"

[workloads.issue_8775_imported_object.native_rep_checks]
allow_materialization_reasons = ["runtime_api"]

[[workloads.issue_8775_imported_object.native_rep_checks.require_records]]
name = "imported_object_direct_selection"
consumer = "imported_object_literal_method_direct_call"
notes_contains = "receiver_provenance=imported_object_literal_metadata"
min = 5

[[workloads.issue_8775_imported_object.native_rep_checks.require_records]]
name = "imported_object_generic_fallback_retained"
consumer = "imported_object_literal_method_direct_call"
notes_contains = "generic_dispatch_fallback=js_native_call_method_by_id"
min = 5
1 change: 1 addition & 0 deletions changelog.d/8750-ext-error-objects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reject native-extension failures with real JavaScript `Error` objects, including `.message`/`.stack`, and preserve mysql2-compatible `.code`/`.errno` metadata for common MySQL server errors.
1 change: 1 addition & 0 deletions changelog.d/8785-imported-object-method-specialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Stable imported object-literal methods now use guarded direct calls to their defining closure bodies. The fast path validates the exported receiver identity, exact own-property shape, and live function identity, while replacements, deletions, accessors, proxies, receiver changes, and other dynamic cases retain the universal method-dispatch fallback.
1 change: 1 addition & 0 deletions changelog.d/8786-closure-captured-packed-loops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Optimize immutable closure-captured packed Array and Array-subclass loops, including nested arrays derived from guarded indexed reads, while retaining generic side exits for rebinding, layout changes, and moving GC.
3 changes: 3 additions & 0 deletions changelog.d/8788-short-packed-spread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## perf(codegen): direct-call stable methods with short packed spread tails

`receiver.method(fixed, ...args)` now emits guarded direct-call arms when the final spread is an exact ordinary packed Array with zero through four present elements. The guard rejects holes, iterator/prototype overrides, proxies, Array subclasses, descriptors, and oversized tails; method class/shape/invalidation guards select the concrete body, and every miss retains the full iterator-aware apply dispatcher. This removes argument-array materialization and dynamic method lookup from the common empty/one-element ECS dispatch path while preserving source-order evaluation and moving-GC roots.
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 @@ -1116,7 +1116,9 @@ pub(super) fn compile_closure(
local_class_field_aliases: HashMap::new(),
local_id_to_name: HashMap::new(),
local_value_aliases: HashMap::new(),
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
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 @@ -914,7 +914,9 @@ pub(super) fn compile_module_entry(
local_class_field_aliases: HashMap::new(),
local_id_to_name: HashMap::new(),
local_value_aliases: HashMap::new(),
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
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 @@ -1619,7 +1621,9 @@ pub(super) fn compile_module_entry(
local_class_field_aliases: HashMap::new(),
local_id_to_name: HashMap::new(),
local_value_aliases: HashMap::new(),
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
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/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1166,7 +1166,9 @@ pub(super) fn compile_function(
local_class_field_aliases: HashMap::new(),
local_id_to_name: HashMap::new(),
local_value_aliases: HashMap::new(),
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
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 @@ -498,7 +498,9 @@ pub(super) fn compile_method(
local_class_field_aliases: HashMap::new(),
local_id_to_name: HashMap::new(),
local_value_aliases: HashMap::new(),
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
Expand Down Expand Up @@ -1770,7 +1772,9 @@ pub(super) fn compile_static_method(
local_class_field_aliases: HashMap::new(),
local_id_to_name: HashMap::new(),
local_value_aliases: HashMap::new(),
local_imported_object_aliases: HashMap::new(),
imported_vars: &cross_module.imported_vars,
imported_object_literals: &cross_module.imported_object_literals,
compile_time_constants: native_facts.compile_time_constants(),
target_triple: &cross_module.target_triple,
app_metadata: &cross_module.app_metadata,
Expand Down
26 changes: 25 additions & 1 deletion crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ pub(crate) use helpers::{
module_callable_count, set_full_outline_ic, write_barriers_enabled,
};
pub use opts::{
AppMetadata, CompileOptions, FpContractMode, ImportedClass, NamespaceEntry, NamespaceEntryKind,
AppMetadata, CompileOptions, ExportedObjectLiteralCapability, FpContractMode, ImportedClass,
ImportedObjectLiteral, ImportedObjectLiteralMethod, NamespaceEntry, NamespaceEntryKind,
};
pub(crate) use opts::{CrossModuleCtx, ImportedCtor};
pub(crate) use param_guard::scalar_descriptor_rep;
Expand Down Expand Up @@ -2219,6 +2220,28 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
})
.collect();

let imported_object_literals: std::collections::HashMap<String, ImportedObjectLiteral> = opts
.imported_classes
.iter()
.filter_map(|imported| {
imported
.object_literal
.as_ref()
.map(|object| (object.local_binding.clone(), object.clone()))
})
.collect();
let imported_object_producers: std::collections::BTreeSet<(String, u32)> =
imported_object_literals
.values()
.map(|object| (object.source_prefix.clone(), object.source_global_id))
.collect();
for (source_prefix, source_global_id) in imported_object_producers {
llmod.add_external_global(
&format!("perry_global_{source_prefix}__{source_global_id}"),
DOUBLE,
);
}

let mut cross_module = CrossModuleCtx {
namespace_imports: opts.namespace_imports.iter().cloned().collect(),
namespace_member_nested: opts.namespace_member_nested.iter().cloned().collect(),
Expand Down Expand Up @@ -2295,6 +2318,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
}
}),
imported_vars: opts.imported_vars,
imported_object_literals,
needs_stdlib: opts.needs_stdlib,
needs_geisterhand: opts.needs_geisterhand,
geisterhand_port: opts.geisterhand_port,
Expand Down
45 changes: 45 additions & 0 deletions crates/perry-codegen/src/codegen/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,48 @@ pub struct ImportedClass {
/// facts. Kept beside the class metadata so the proof and the field layout
/// it names enter the consumer atomically and share one object-cache key.
pub return_shape_imports: Vec<String>,
/// Producer-authored capability for an imported, immutable object-literal
/// binding. Such entries also carry the anonymous shape class above so the
/// consumer can validate its source class/ShapeId pair, but they are not
/// JavaScript class imports and are only consumed by the guarded own-method
/// lowering.
pub object_literal: Option<ImportedObjectLiteral>,
}

/// One concise own method published by an exported object-literal capability.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImportedObjectLiteralMethod {
pub name: String,
pub func_id: u32,
pub param_count: usize,
pub field_index: u32,
}

/// Consumer-resolved capability for one imported object-literal binding.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImportedObjectLiteral {
/// The identifier carried by `Expr::ExternFuncRef` in the consumer.
pub local_binding: String,
/// Public export name at the defining module, retained for diagnostics.
pub source_export_name: String,
pub source_prefix: String,
/// Consumer-local name of the imported anonymous shape stub.
pub receiver_class_name: String,
/// LocalId of the defining module's immutable global binding.
pub source_global_id: u32,
pub methods: Vec<ImportedObjectLiteralMethod>,
}

/// Defining-module fact harvested before parallel code generation. Absence is
/// authoritative: importers never infer an object capability from a getter or
/// from their own call-site observations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExportedObjectLiteralCapability {
pub class_name: String,
pub class_id: u32,
pub global_id: u32,
pub field_names: Vec<String>,
pub methods: Vec<ImportedObjectLiteralMethod>,
}

/// Constructor metadata for a class imported from another module.
Expand Down Expand Up @@ -793,6 +835,9 @@ pub(crate) struct CrossModuleCtx {
pub i18n: Option<crate::expr::I18nLowerCtx>,
/// Names of imports that are exported variables (not functions).
pub imported_vars: std::collections::HashSet<String>,
/// Producer-authored immutable imported object-literal capabilities,
/// keyed by the consumer's local import binding.
pub imported_object_literals: std::collections::HashMap<String, ImportedObjectLiteral>,
/// Whether perry-stdlib will be linked into the final binary. When
/// false, compile_module_entry skips the `js_stdlib_init_dispatch()`
/// call in main's prologue because only the runtime is linked and
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/collectors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mod loop_bounded_i32;
mod mutation;
mod not_bigint_locals;
mod number_by_construction;
mod object_literal_exports;
mod param_ranges;
mod pointer_locals;
mod proven_args;
Expand Down Expand Up @@ -85,6 +86,7 @@ pub(crate) use integer_locals::{
pub(crate) use local_refs::{expr_contains_local_get, mark_all_candidate_refs_in_expr};
pub(crate) use mutation::{body_contains_call, body_contains_closure, has_any_mutation};
pub(crate) use number_by_construction::collect_number_by_construction_locals;
pub(crate) use object_literal_exports::exported_object_literal_capabilities;
pub(crate) use param_ranges::{collect_param_int_ranges, ParamIntRanges};
pub(crate) use pointer_locals::collect_pointer_typed_locals;
pub(crate) use proven_args::{
Expand Down
Loading
Loading