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
11 changes: 11 additions & 0 deletions changelog.d/8740-guarded-undefined-method-param.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Versioned eligible instance methods for an exact `undefined` optional argument.
The public boxed-ABI wrapper validates the live argument bits once and sends only
that value to a private specialized body; functions and every other falsey or
non-callable value retain the ordinary JavaScript path. Mutation, closure capture,
async/generator bodies, and oversized methods are conservatively excluded.

This removes the per-entity optional-filter truthiness and callback arm from
codehz/ecs's 10k-entity accumulation loop. On an Apple M1 Mac mini, 11
alternating process pairs measured 0.179437 ms versus 0.195817 ms on the exact
parent, an 8.376% median paired improvement with 11/11 wins and all output
oracles passing.
64 changes: 64 additions & 0 deletions crates/perry-codegen/src/codegen/artifact_context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! Borrowed inputs for the module artifact-emission phase.

use std::collections::{HashMap, HashSet};

use perry_hir::Module as HirModule;

use crate::module::LlModule;
use crate::strings::StringPool;

use super::opts::CrossModuleCtx;

/// Read-only view of the `CompileOptions` fields that artifact emission still
/// references after the pipeline has moved other fields into `CrossModuleCtx`.
pub(super) struct OptsView<'a> {
pub(super) import_function_prefixes: &'a HashMap<String, String>,
pub(super) imported_classes: &'a [super::opts::ImportedClass],
pub(super) is_entry_module: bool,
pub(super) non_entry_module_prefixes: &'a [String],
pub(super) output_type: &'a str,
}

/// Data computed by the `compile_module` prelude and borrowed by the artifact
/// tail. Keeping it together avoids a second oversized compiler entry module.
pub(super) struct ModuleArtifactsCtx<'a> {
pub progress: &'a super::CompileProgress,
pub llmod: &'a mut LlModule,
pub target_triple: &'a str,
pub strings: &'a mut StringPool,
pub hir: &'a HirModule,
pub import_function_prefixes: &'a HashMap<String, String>,
pub imported_classes: &'a [super::opts::ImportedClass],
pub is_entry_module: bool,
pub non_entry_module_prefixes: &'a [String],
pub output_type: &'a str,
pub module_prefix: &'a String,
pub class_table: &'a HashMap<String, &'a perry_hir::Class>,
pub class_ids: &'a HashMap<String, u32>,
pub enum_table: &'a HashMap<(String, String), perry_hir::EnumValue>,
pub module_globals: &'a HashMap<u32, String>,
pub module_global_types: &'a HashMap<u32, perry_hir::types::Type>,
pub static_field_globals: &'a HashMap<(String, String), String>,
pub method_names: &'a HashMap<(String, String), String>,
pub func_names: &'a HashMap<u32, String>,
pub func_signatures: &'a HashMap<u32, (usize, bool, bool, bool)>,
pub func_synthetic_arguments: &'a HashSet<u32>,
pub module_boxed_vars: &'a HashSet<u32>,
/// Typed-ABI capture oracle: module-wide local types minus boxed ids.
pub module_local_types: &'a HashMap<u32, perry_hir::types::Type>,
/// Source-type metadata for closure receivers; not a representation proof.
pub module_receiver_types: &'a HashMap<u32, perry_hir::types::Type>,
pub closure_rest_params: &'a HashMap<u32, usize>,
pub closure_synthetic_arguments: &'a HashSet<u32>,
pub closure_rest_and_arguments: &'a HashSet<u32>,
pub closure_arities: &'a HashMap<u32, u32>,
pub closure_lengths: &'a HashMap<u32, u32>,
pub closure_arrow_functions: &'a HashSet<u32>,
pub trusted_box_closures: &'a HashMap<u32, super::closure_collect::TrustedBoxClosure>,
pub closures: &'a [(perry_hir::types::FuncId, perry_hir::Expr)],
pub class_keys_init_data: &'a [(String, String, u32, Vec<u64>, Vec<u64>)],
/// Keys global to `(class id, packed GcHeader word)` for inline `new`.
pub class_header_image_inits: &'a HashMap<String, (u32, u64)>,
pub imported_class_stubs: &'a [perry_hir::Class],
pub cross_module: &'a CrossModuleCtx,
}
157 changes: 84 additions & 73 deletions crates/perry-codegen/src/codegen/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ use std::collections::{HashMap, HashSet};
use std::time::Instant;

use anyhow::{Context, Result};
use perry_hir::Module as HirModule;

use crate::module::LlModule;
use crate::strings::StringPool;
use crate::types::{LlvmType, DOUBLE, I64, VOID};

use super::artifact_context::{ModuleArtifactsCtx, OptsView};
use super::closure::{
compile_closure, compile_typed_f64_closure, compile_typed_i1_closure,
compile_typed_i32_closure, compile_typed_string_closure,
Expand All @@ -27,78 +25,9 @@ use super::method::{
compile_typed_string_method,
};
use super::native_namespace_exports::emit_native_namespace_reexport_getters;
use super::opts::CrossModuleCtx;
use super::spec_function_length;
use super::typed_abi::TypedFunctionTrampolineKind;

/// Read-only view of the `CompileOptions` fields that the artifact
/// emission step references via `opts.X`. Bundled into a struct so the
/// moved block (originally written against `let opts = …;` of type
/// `CompileOptions`) can keep its `opts.X` syntax without holding a
/// `&CompileOptions` borrow — that borrow is unavailable at the call
/// site, because `compile_module`'s prelude moves several `opts`
/// fields into `CrossModuleCtx` before invoking this function.
struct OptsView<'a> {
import_function_prefixes: &'a std::collections::HashMap<String, String>,
imported_classes: &'a [super::opts::ImportedClass],
is_entry_module: bool,
non_entry_module_prefixes: &'a [String],
output_type: &'a str,
}
use super::string_pool::emit_string_pool;

/// All the data computed by the prelude of `compile_module` that the
/// tail half (this file) needs. Bundled so the call from
/// `compile_module` stays a single line; field names mirror the
/// in-prelude local names so the moved block reads unchanged once
/// destructured.
pub(super) struct ModuleArtifactsCtx<'a> {
pub progress: &'a super::CompileProgress,
pub llmod: &'a mut LlModule,
pub target_triple: &'a str,
pub strings: &'a mut StringPool,
pub hir: &'a HirModule,
pub import_function_prefixes: &'a std::collections::HashMap<String, String>,
pub imported_classes: &'a [super::opts::ImportedClass],
pub is_entry_module: bool,
pub non_entry_module_prefixes: &'a [String],
pub output_type: &'a str,
pub module_prefix: &'a String,
pub class_table: &'a HashMap<String, &'a perry_hir::Class>,
pub class_ids: &'a HashMap<String, u32>,
pub enum_table: &'a HashMap<(String, String), perry_hir::EnumValue>,
pub module_globals: &'a HashMap<u32, String>,
pub module_global_types: &'a HashMap<u32, perry_hir::types::Type>,
pub static_field_globals: &'a HashMap<(String, String), String>,
pub method_names: &'a HashMap<(String, String), String>,
pub func_names: &'a HashMap<u32, String>,
pub func_signatures: &'a HashMap<u32, (usize, bool, bool, bool)>,
pub func_synthetic_arguments: &'a std::collections::HashSet<u32>,
pub module_boxed_vars: &'a std::collections::HashSet<u32>,
/// Typed-ABI capture-representation oracle: module-wide `Stmt::Let` types
/// MINUS boxed ids (#5869). Only the typed closure clones read this.
pub module_local_types: &'a HashMap<u32, perry_hir::types::Type>,
/// #6369: receiver-type oracle for closure bodies — the same module-wide
/// `Stmt::Let` types with no representation filtering, mirroring the
/// `module_global_types` seed that `compile_function` / `compile_method`
/// already use. Feeds `FnCtx.local_types` only.
pub module_receiver_types: &'a HashMap<u32, perry_hir::types::Type>,
pub closure_rest_params: &'a HashMap<u32, usize>,
pub closure_synthetic_arguments: &'a std::collections::HashSet<u32>,
pub closure_rest_and_arguments: &'a std::collections::HashSet<u32>,
pub closure_arities: &'a HashMap<u32, u32>,
pub closure_lengths: &'a HashMap<u32, u32>,
pub closure_arrow_functions: &'a std::collections::HashSet<u32>,
pub trusted_box_closures:
&'a std::collections::HashMap<u32, super::closure_collect::TrustedBoxClosure>,
pub closures: &'a [(perry_hir::types::FuncId, perry_hir::Expr)],
pub class_keys_init_data: &'a [(String, String, u32, Vec<u64>, Vec<u64>)],
/// #8122: keys global → (class id, packed GcHeader word) for the classes
/// whose inline-`new` header image module init must compose.
pub class_header_image_inits: &'a std::collections::HashMap<String, (u32, u64)>,
pub imported_class_stubs: &'a [perry_hir::Class],
pub cross_module: &'a CrossModuleCtx,
}
use super::typed_abi::TypedFunctionTrampolineKind;

/// Emit the artifact tail: bodies, wrappers, namespace globals, entry
/// function, string pool. Mirrors the in-prelude execution order of
Expand Down Expand Up @@ -393,6 +322,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
None,
Some(nonnegative_index_params),
false,
false,
)
.with_context(|| {
format!(
Expand Down Expand Up @@ -427,8 +357,46 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
None,
None,
false,
false,
)
.with_context(|| format!("lowering method '{}::{}'", class.name, method.name))?;
if cross_module
.guarded_undefined_method_params
.contains_key(&(class.name.clone(), method.name.clone()))
{
compile_method(
llmod,
class,
method,
func_names,
strings,
class_table,
method_names,
module_globals,
module_global_types,
opts.import_function_prefixes,
enum_table,
static_field_globals,
class_ids,
func_signatures,
func_synthetic_arguments,
module_boxed_vars,
closure_rest_params,
cross_module,
None,
false,
None,
None,
false,
true,
)
.with_context(|| {
format!(
"lowering exact-undefined clone of method '{}::{}'",
class.name, method.name
)
})?;
}
// Representation-selection Phase 5a: the additive `internal`
// proven-`this` clone. Same HIR, same ABI, same shadow-bound
// tagged-at-rest receiver slot — only `this.field` lowering
Expand Down Expand Up @@ -464,13 +432,51 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
Some(fact.clone()),
None,
false,
false,
)
.with_context(|| {
format!(
"lowering proven-`this` clone of method '{}::{}'",
class.name, method.name
)
})?;
if cross_module
.guarded_undefined_method_params
.contains_key(&(class.name.clone(), method.name.clone()))
{
compile_method(
llmod,
class,
method,
func_names,
strings,
class_table,
method_names,
module_globals,
module_global_types,
opts.import_function_prefixes,
enum_table,
static_field_globals,
class_ids,
func_signatures,
func_synthetic_arguments,
module_boxed_vars,
closure_rest_params,
cross_module,
None,
false,
Some(fact.clone()),
None,
false,
true,
)
.with_context(|| {
format!(
"lowering proven-`this` exact-undefined clone of method '{}::{}'",
class.name, method.name
)
})?;
}

// #8607: a second, stricter clone for the Phase 3b
// provenance+containment route. Its synthetic immutable
Expand Down Expand Up @@ -505,6 +511,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
Some(fact.clone()),
None,
true,
false,
)
.with_context(|| {
format!(
Expand Down Expand Up @@ -544,6 +551,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
None,
None,
false,
false,
)
.with_context(|| {
format!(
Expand Down Expand Up @@ -611,6 +619,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
None,
None,
false,
false,
)
.with_context(|| format!("lowering getter '{}::{}'", class.name, prop))?;
}
Expand Down Expand Up @@ -666,6 +675,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
None,
None,
false,
false,
)
.with_context(|| format!("lowering setter '{}::{}'", class.name, prop))?;
}
Expand Down Expand Up @@ -763,6 +773,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
None,
None,
false,
false,
)
.with_context(|| format!("lowering constructor for '{}'", class.name))?;
}
Expand Down
6 changes: 5 additions & 1 deletion crates/perry-codegen/src/codegen/closure_collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ fn count_stmt_nodes(stmt: &perry_hir::Stmt) -> usize {
count
}

pub(super) fn count_body_nodes(body: &[perry_hir::Stmt]) -> usize {
body.iter().map(count_stmt_nodes).sum()
}

pub(crate) fn select_trusted_box_closures(
closures: &[(perry_hir::types::FuncId, perry_hir::Expr)],
direct_call_closures: &std::collections::HashSet<u32>,
Expand Down Expand Up @@ -298,7 +302,7 @@ pub(crate) fn select_trusted_box_closures(
if boxed_capture_mask == 0 {
return None;
}
let cost = body.iter().map(count_stmt_nodes).sum::<usize>();
let cost = count_body_nodes(body);
(cost <= MAX_TRUSTED_BOX_CLONE_NODES).then_some((
cost,
*func_id,
Expand Down
Loading
Loading