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
1 change: 1 addition & 0 deletions changelog.d/9773-typed-feedback-profile-replay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add opt-in typed-feedback profile replay for guarded numeric array reads, with versioned capture catalogs, exact freshness checks, deterministic selection and rejection diagnostics, native-region verifier checks, and explain-lowering evidence. Profiles remain advisory and retain the runtime guard and boxed fallback.
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3662,6 +3662,8 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
progress.phase(1, "lowering complete; finalizing generated IR");
crate::root_reload::apply_to_module(&mut llmod);

crate::typed_feedback_profile::finish_module(&mut llmod.native_rep_records);

let verify_native_regions = opts.verify_native_regions
|| std::env::var("PERRY_VERIFY_NATIVE_REGIONS").ok().as_deref() == Some("1");
if verify_native_regions {
Expand Down
52 changes: 42 additions & 10 deletions crates/perry-codegen/src/expr/index_get/guarded_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@ pub(super) fn lower_guarded_array_index_get(
coerce_numeric_fallback: bool,
receiver_slot: Option<&str>,
) -> Result<String> {
let site_id = ctx.typed_feedback_site_id(ctx.ic_site_counter);
crate::typed_feedback_profile::register_site(
site_id,
&ctx.func.name,
"array_element",
"array[index]",
);
let replay_fact =
crate::typed_feedback_profile::select_numeric_array(site_id, require_numeric_layout);
// Preserve the original consumer's coercion contract. A replay hint can
// select representation handling, but cannot turn a JS-value read into
// a numeric-context read.
let coerce_numeric_fallback = require_numeric_layout && coerce_numeric_fallback;
let require_numeric_layout = require_numeric_layout || replay_fact.is_some();
let contract = if require_numeric_layout {
TypedFeedbackContract::numeric_array_get_index()
} else {
Expand All @@ -201,6 +215,10 @@ pub(super) fn lower_guarded_array_index_get(
"array[index]",
contract,
);
// Replay selects the existing numeric tier, including its full inline
// receiver/layout/bounds checks and cold runtime guard. The observation
// itself never admits a load or suppresses a check.
let inline_guard = !typed_feedback_emission_enabled();
let fast_idx = ctx.new_block(&format!("{}.fast", block_prefix));
let fallback_idx = ctx.new_block(&format!("{}.fallback", block_prefix));
// A non-negative ordinary-array index at or above `length` has no own
Expand All @@ -210,7 +228,7 @@ pub(super) fn lower_guarded_array_index_get(
// properties, that result is `undefined` without consulting the generic
// polymorphic getter. Sparse-set membership tests hit exactly this arm for
// absent ids, so keep it separate from the in-bounds raw-load block.
let inline_oob_idx = if !typed_feedback_emission_enabled() {
let inline_oob_idx = if inline_guard {
Some(ctx.new_block(&format!("{}.guard.oob", block_prefix)))
} else {
None
Expand All @@ -226,7 +244,7 @@ pub(super) fn lower_guarded_array_index_get(
let mut inline_fast_handle: Option<(String, String)> = None;
let mut runtime_fast_handle: Option<(String, String)> = None;

if !typed_feedback_emission_enabled() {
if inline_guard {
// Normal builds do not collect feedback. Inline the plain-array
// structural guard instead of paying an out-of-line call merely to
// rediscover the same header facts before the direct slot load below.
Expand Down Expand Up @@ -535,7 +553,10 @@ pub(super) fn lower_guarded_array_index_get(
],
false,
false,
Vec::new(),
replay_fact
.as_ref()
.map(|fact| vec![format!("typed_feedback_replay_fallback={}", fact.fact_id)])
.unwrap_or_default(),
);
}

Expand Down Expand Up @@ -617,16 +638,27 @@ pub(super) fn lower_guarded_array_index_get(
None,
None,
None,
vec![raw_f64_layout_fact(
None,
"consumed",
"numeric_array_index_get_guard",
None,
)],
{
let mut facts = vec![raw_f64_layout_fact(
None,
"consumed",
"numeric_array_index_get_guard",
None,
)];
if let Some(fact) = &replay_fact {
facts.push(fact.clone());
}
facts
},
Vec::new(),
false,
false,
Vec::new(),
replay_fact
.as_ref()
.map(|_| {
vec!["typed_feedback_replay_selected=fresh_numeric_array_observation".into()]
})
.unwrap_or_default(),
);
}

Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/expr/typed_feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ pub(crate) fn emit_typed_feedback_register_site(
let local_site_id = ctx.ic_site_counter;
ctx.ic_site_counter += 1;
let site_id = ctx.typed_feedback_site_id(local_site_id);
crate::typed_feedback_profile::register_site(site_id, &ctx.func.name, kind.label(), operation);
// Default build: skip the no-op registration call (and its byte globals)
// but keep the site-id stable for the guard call.
if !typed_feedback_emission_enabled() {
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub(crate) mod type_analysis;
pub(crate) mod type_analysis_class_fields;
pub(crate) mod type_analysis_facts;
pub(crate) mod type_analysis_net;
pub mod typed_feedback_profile;
pub(crate) mod typed_shape;
pub mod types;

Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/native_value/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use raw_f64::{

pub(crate) fn verify_native_rep_records(records: &[NativeRepRecord]) -> Result<()> {
let mut errors = Vec::new();
crate::typed_feedback_profile::verify_records(records, &mut errors);
for record in records {
if let Some(expected_ty) = expected_llvm_type(&record.native_rep) {
if record.llvm_ty != expected_ty {
Expand Down
Loading
Loading