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
199 changes: 199 additions & 0 deletions PERF_RUN_LOG.md

Large diffs are not rendered by default.

28 changes: 25 additions & 3 deletions crates/perry-codegen/src/expr/index_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::types::{DOUBLE, I1, I16, I32, I64, I8, PTR};
use super::arrays_finds::lower_buffer_index_get_i32;
#[allow(unused_imports)]
use super::{
buffer_access_materialization_reason, buffer_alias_metadata_suffix,
buffer_access_materialization_reason, buffer_alias_metadata_suffix, can_lower_expr_as_i32,
emit_layout_note_slot_on_block, emit_shadow_slot_clear, emit_shadow_slot_update_for_expr,
emit_string_literal_global, emit_typed_feedback_register_site, emit_v8_export_call,
emit_v8_member_method_call, emit_write_barrier, emit_write_barrier_slot_on_block,
Expand Down Expand Up @@ -847,8 +847,30 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
}

let arr_box = lower_expr(ctx, object)?;
let idx_double = lower_expr(ctx, index)?;
let idx_i32 = ctx.block().fptosi(DOUBLE, &idx_double, I32);
let i32_slots = ctx.i32_counter_slots.clone();
let flat_const_arrays = ctx.flat_const_arrays.clone();
let array_row_aliases = ctx.array_row_aliases.clone();
let integer_locals = ctx.integer_locals.clone();
let use_i32_index = can_lower_expr_as_i32(
index,
&i32_slots,
&flat_const_arrays,
&array_row_aliases,
&integer_locals,
ctx.clamp3_functions,
ctx.clamp_u8_functions,
ctx.integer_returning_functions,
ctx.i32_identity_functions,
);
let (idx_double, idx_i32) = if use_i32_index {
let idx_i32 = lower_expr_as_i32(ctx, index)?;
let idx_double = ctx.block().sitofp(I32, &idx_i32, DOUBLE);
(idx_double, idx_i32)
} else {
let idx_double = lower_expr(ctx, index)?;
let idx_i32 = ctx.block().fptosi(DOUBLE, &idx_double, I32);
(idx_double, idx_i32)
};
if !require_numeric_layout
&& !matches!(index.as_ref(), Expr::Integer(_) | Expr::Number(_))
{
Expand Down
20 changes: 18 additions & 2 deletions crates/perry-codegen/src/stmt/loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ pub(crate) fn lower_for(
// site having done so already). Only the site that inserted should
// remove it at loop exit to avoid disturbing a pre-existing slot.
let local_bound_counter_i32_was_fresh: bool;
let local_bound_bound_i32_was_fresh: bool;
let i32_local_bound_slot: Option<String> =
if let Some((counter_id, bound_id, _op)) = local_bound_classification {
// Allocate a parallel i32 slot for the counter if not already
Expand All @@ -411,18 +412,28 @@ pub(crate) fn lower_for(
local_bound_counter_i32_was_fresh = fresh;
// Hoist `fptosi(n)` to a fresh i32 alloca before the cond block
// so LLVM sees a loop-invariant integer bound — critical for
// SCEV / LoopVectorizer to recognize the induction variable.
if let Some(bound_slot) = ctx.locals.get(&bound_id).cloned() {
// SCEV / LoopVectorizer to recognize the induction variable. Also
// expose that slot while lowering the loop body so integer index
// expressions like `i * n + k` can reuse the same trusted bound
// instead of rebuilding the index through double arithmetic.
if let Some(existing) = ctx.i32_counter_slots.get(&bound_id).cloned() {
local_bound_bound_i32_was_fresh = false;
Some(existing)
} else if let Some(bound_slot) = ctx.locals.get(&bound_id).cloned() {
let bound_dbl = ctx.block().load(DOUBLE, &bound_slot);
let bound_i32 = ctx.block().fptosi(DOUBLE, &bound_dbl, I32);
let slot = ctx.func.alloca_entry(I32);
ctx.block().store(I32, &bound_i32, &slot);
ctx.i32_counter_slots.insert(bound_id, slot.clone());
local_bound_bound_i32_was_fresh = true;
Some(slot)
} else {
local_bound_bound_i32_was_fresh = false;
None
}
} else {
local_bound_counter_i32_was_fresh = false;
local_bound_bound_i32_was_fresh = false;
None
};
// Issue #168 follow-up: when neither the `arr.length` hoist nor the static
Expand Down Expand Up @@ -718,6 +729,11 @@ pub(crate) fn lower_for(
ctx.i32_counter_slots.remove(&counter_id);
}
}
if local_bound_bound_i32_was_fresh {
if let Some((_, bound_id, _)) = local_bound_classification {
ctx.i32_counter_slots.remove(&bound_id);
}
}
let _ = i32_local_bound_slot;
// Same cleanup for the runtime-guarded `any`-bound path.
if let Some(dyn_bound) = dynamic_i32_bound {
Expand Down
53 changes: 52 additions & 1 deletion crates/perry-codegen/tests/typed_feedback.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use perry_codegen::{compile_module, AppMetadata, CompileOptions};
use perry_hir::{BinaryOp, Class, ClassField, Expr, Function, Module, ModuleInitKind, Param, Stmt};
use perry_hir::{
BinaryOp, Class, ClassField, CompareOp, Expr, Function, Module, ModuleInitKind, Param, Stmt,
UpdateOp,
};
use perry_types::{FunctionType, Type};

/// Serializes env-mutating tests so a concurrent test never observes a
Expand Down Expand Up @@ -547,3 +550,51 @@ fn typed_feedback_guards_computed_numeric_array_index_hot_path() {
assert!(!ir.contains("call double @js_array_numeric_get_f64_unboxed"));
assert!(ir.contains("load double"));
}

#[test]
fn typed_feedback_guards_computed_numeric_array_index_uses_i32_loop_bound() {
let array_ty = Type::Array(Box::new(Type::Number));
let ir = ir_for(module(
"typed_feedback_loop_bound_computed_array.ts",
vec![param(1, "xs", array_ty), param(2, "size", Type::Number)],
Type::Number,
vec![Stmt::For {
init: Some(Box::new(Stmt::Let {
id: 3,
name: "i".to_string(),
ty: Type::Number,
mutable: true,
init: Some(Expr::Integer(0)),
})),
condition: Some(Expr::Compare {
op: CompareOp::Lt,
left: Box::new(Expr::LocalGet(3)),
right: Box::new(Expr::LocalGet(2)),
}),
update: Some(Expr::Update {
id: 3,
op: UpdateOp::Increment,
prefix: false,
}),
body: vec![Stmt::Return(Some(Expr::IndexGet {
object: Box::new(Expr::LocalGet(1)),
index: Box::new(Expr::Binary {
op: BinaryOp::Add,
left: Box::new(Expr::Binary {
op: BinaryOp::Mul,
left: Box::new(Expr::LocalGet(3)),
right: Box::new(Expr::LocalGet(2)),
}),
right: Box::new(Expr::Integer(1)),
}),
}))],
}],
));

assert!(ir.contains("call i32 @js_typed_feedback_numeric_array_index_get_guard"));
assert!(ir.contains("call double @js_typed_feedback_array_index_get_fallback_boxed"));
assert!(ir.contains("mul i32"), "{ir}");
assert!(ir.contains("add i32"), "{ir}");
assert!(!ir.contains("fmul double"), "{ir}");
assert!(!ir.contains("call double @js_array_numeric_get_f64_unboxed"));
}
Loading