Skip to content

Commit 67a5275

Browse files
committed
initial sketch for polymorphic getivar. hangs on added test, not sure where
1 parent 1de6133 commit 67a5275

File tree

5 files changed

+150
-22
lines changed

5 files changed

+150
-22
lines changed

zjit/src/backend/arm64/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1717,7 +1717,6 @@ mod tests {
17171717

17181718
use super::*;
17191719
use insta::assert_snapshot;
1720-
use crate::hir;
17211720

17221721
static TEMP_REGS: [Reg; 5] = [X1_REG, X9_REG, X10_REG, X14_REG, X15_REG];
17231722

zjit/src/backend/tests.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use crate::backend::lir::*;
33
use crate::cruby::*;
44
use crate::codegen::c_callable;
55
use crate::options::rb_zjit_prepare_options;
6-
use crate::hir;
76

87
#[test]
98
fn test_add() {

zjit/src/distribution.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ pub struct DistributionSummary<T: Copy + PartialEq + Default + std::fmt::Debug,
7979
const SKEW_THRESHOLD: f64 = 0.75;
8080

8181
impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> DistributionSummary<T, N> {
82+
const N: usize = N; // TODO: better API
83+
8284
pub fn new(dist: &Distribution<T, N>) -> Self {
8385
#[cfg(debug_assertions)]
8486
{
@@ -134,6 +136,10 @@ impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> Distributi
134136
assert!(idx < N, "index {idx} out of bounds for buckets[{N}]");
135137
self.buckets[idx]
136138
}
139+
140+
pub fn buckets(&self) -> &[T] {
141+
&self.buckets
142+
}
137143
}
138144

139145
#[cfg(test)]

zjit/src/hir.rs

Lines changed: 113 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,6 +2638,22 @@ impl Function {
26382638
}
26392639
}
26402640

2641+
fn polymorphic_summary<'profile>(&self, profiles: &'profile ProfileOracle, recv: InsnId, insn_idx: usize) -> Option<&'profile TypeDistributionSummary> {
2642+
let Some(entries) = profiles.types.get(&insn_idx) else {
2643+
return None;
2644+
};
2645+
let recv = self.chase_insn(recv);
2646+
for (entry_insn, entry_type_summary) in entries {
2647+
if self.union_find.borrow().find_const(*entry_insn) == recv {
2648+
if entry_type_summary.is_polymorphic() {
2649+
return Some(entry_type_summary);
2650+
}
2651+
return None;
2652+
}
2653+
}
2654+
None
2655+
}
2656+
26412657
/// Prepare arguments for a direct send, handling keyword argument reordering and default synthesis.
26422658
/// Returns the (state, processed_args, kw_bits) to use for the SendWithoutBlockDirect instruction,
26432659
/// or Err with the fallback reason if direct send isn't possible.
@@ -5929,6 +5945,25 @@ fn invalidates_locals(opcode: u32, operands: *const VALUE) -> bool {
59295945
/// The index of the self parameter in the HIR function
59305946
pub const SELF_PARAM_IDX: usize = 0;
59315947

5948+
// TODO place this better
5949+
fn new_branch_block(
5950+
fun: &mut Function,
5951+
insn_idx: u32,
5952+
exit_state: &FrameState,
5953+
locals_count: usize,
5954+
stack_count: usize,
5955+
) -> (BlockId, InsnId, FrameState, InsnId) {
5956+
let block = fun.new_block(insn_idx);
5957+
let self_param = fun.push_insn(block, Insn::Param);
5958+
let mut state = exit_state.clone();
5959+
state.locals.clear();
5960+
state.stack.clear();
5961+
state.locals.extend((0..locals_count).map(|_| fun.push_insn(block, Insn::Param)));
5962+
state.stack.extend((0..stack_count).map(|_| fun.push_insn(block, Insn::Param)));
5963+
let snapshot = fun.push_insn(block, Insn::Snapshot { state: state.clone() });
5964+
(block, self_param, state, snapshot)
5965+
}
5966+
59325967
/// Compile ISEQ into High-level IR
59335968
pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
59345969
if !ZJITState::can_compile_iseq(iseq) {
@@ -6456,24 +6491,6 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
64566491
state.stack_push(fun.push_insn(block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) }));
64576492
}
64586493
YARVINSN_getblockparam => {
6459-
fn new_branch_block(
6460-
fun: &mut Function,
6461-
insn_idx: u32,
6462-
exit_state: &FrameState,
6463-
locals_count: usize,
6464-
stack_count: usize,
6465-
) -> (BlockId, InsnId, FrameState, InsnId) {
6466-
let block = fun.new_block(insn_idx);
6467-
let self_param = fun.push_insn(block, Insn::Param);
6468-
let mut state = exit_state.clone();
6469-
state.locals.clear();
6470-
state.stack.clear();
6471-
state.locals.extend((0..locals_count).map(|_| fun.push_insn(block, Insn::Param)));
6472-
state.stack.extend((0..stack_count).map(|_| fun.push_insn(block, Insn::Param)));
6473-
let snapshot = fun.push_insn(block, Insn::Snapshot { state: state.clone() });
6474-
(block, self_param, state, snapshot)
6475-
}
6476-
64776494
fn finish_getblockparam_branch(
64786495
fun: &mut Function,
64796496
block: BlockId,
@@ -6834,8 +6851,84 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
68346851
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode) });
68356852
break; // End the block
68366853
}
6837-
let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id });
6838-
state.stack_push(result);
6854+
6855+
let mut t_object_varying_shapes = true;
6856+
if let Some(summary) = fun.polymorphic_summary(&profiles, self_param, exit_state.insn_idx) {
6857+
// Only split in cases that vary in shape for now -- all T_OBJECT
6858+
for &profiled_type in summary.buckets() {
6859+
if profiled_type.is_empty() { break; }
6860+
if !profiled_type.flags().is_t_object() {
6861+
t_object_varying_shapes = false;
6862+
}
6863+
}
6864+
6865+
// Just a GetIvar if not splitting
6866+
if !t_object_varying_shapes {
6867+
let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id });
6868+
state.stack_push(result);
6869+
} else {
6870+
let self_val = fun.push_insn(block, Insn::GuardType { val: self_param, guard_type: types::HeapBasicObject, state: exit_id });
6871+
let shape = fun.load_shape(block, self_val);
6872+
let join_block = insn_idx_to_block.get(&insn_idx).copied().unwrap_or_else(|| fun.new_block(insn_idx));
6873+
6874+
// For each profiled shape, make a block that handle it
6875+
for &profiled_type in summary.buckets() {
6876+
if profiled_type.is_empty() { break; }
6877+
let mut ivar_index: u16 = 0;
6878+
if ! unsafe { rb_shape_get_iv_index(profiled_type.shape().0, id, &mut ivar_index) } {
6879+
// TODO revisit this, can just return nil.
6880+
// If there is no IVAR index, then the ivar was undefined when we
6881+
// entered the compiler. That means we can just return nil for this
6882+
// shape + iv name
6883+
continue;
6884+
}
6885+
let branch_insn_idx = exit_state.insn_idx as u32;
6886+
let locals_count = state.locals.len();
6887+
let stack_count = state.stack.len();
6888+
let (get_field_block, field_block_self, mut get_field_state, _) = new_branch_block(&mut fun, branch_insn_idx, &exit_state, locals_count, stack_count);
6889+
let expected_shape = fun.push_insn(block, Insn::Const { val: Const::CShape(profiled_type.shape()) });
6890+
let test_id = fun.push_insn(block, Insn::IsBitEqual { left: shape, right: expected_shape });
6891+
fun.push_insn(block, Insn::IfTrue {
6892+
val: test_id,
6893+
target: BranchEdge {
6894+
target: get_field_block,
6895+
args: state.as_args(self_param)
6896+
}
6897+
});
6898+
6899+
let ivar = if profiled_type.flags().is_embedded() {
6900+
// See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
6901+
let offset = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * ivar_index.to_usize()) as i32;
6902+
fun.push_insn(get_field_block, Insn::LoadField { recv: self_val, id, offset, return_type: types::BasicObject })
6903+
} else {
6904+
let as_heap = fun.push_insn(get_field_block, Insn::LoadField { recv: self_val, id: ID!(_as_heap), offset: ROBJECT_OFFSET_AS_HEAP_FIELDS as i32, return_type: types::CPtr });
6905+
6906+
let offset = SIZEOF_VALUE_I32 * ivar_index as i32;
6907+
fun.push_insn(get_field_block, Insn::LoadField { recv: as_heap, id, offset, return_type: types::BasicObject })
6908+
};
6909+
6910+
get_field_state.stack_push(ivar);
6911+
fun.push_insn(get_field_block, Insn::Jump(BranchEdge {
6912+
target: join_block,
6913+
args: get_field_state.as_args(field_block_self),
6914+
}));
6915+
}
6916+
6917+
// The fallback case, just getivar
6918+
let ivar = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id });
6919+
state.stack_push(ivar);
6920+
fun.push_insn(block, Insn::Jump(BranchEdge {
6921+
target: join_block,
6922+
args: state.as_args(self_param),
6923+
}));
6924+
queue.push_back((state, join_block, insn_idx, local_inval));
6925+
break; // End the block
6926+
}
6927+
} else {
6928+
// no profile
6929+
let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id });
6930+
state.stack_push(result);
6931+
}
68396932
}
68406933
YARVINSN_setinstancevariable => {
68416934
let id = ID(get_arg(pc, 0).as_u64());

zjit/src/hir/opt_tests.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3968,6 +3968,37 @@ mod hir_opt_tests {
39683968
");
39693969
}
39703970

3971+
// TODO better placement
3972+
#[test]
3973+
fn test_polymorphic_getinstancevariable() {
3974+
set_call_threshold(3);
3975+
eval("
3976+
module Tester
3977+
def test = @foo
3978+
end
3979+
3980+
class A
3981+
include Tester
3982+
def initialize
3983+
@a = 1
3984+
@foo = 50
3985+
end
3986+
end
3987+
3988+
class B
3989+
include Tester
3990+
def initialize = (@foo = 100)
3991+
end
3992+
3993+
a = A.new
3994+
b = B.new
3995+
a.test
3996+
b.test
3997+
a.test
3998+
");
3999+
assert_snapshot!(hir_string_proc("Tester.instance_method(:test)"), @r"");
4000+
}
4001+
39714002
#[test]
39724003
fn test_setinstancevariable() {
39734004
eval("

0 commit comments

Comments
 (0)