@@ -3922,13 +3922,22 @@ impl Function {
39223922 }
39233923
39243924 self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state });
3925+ let id = unsafe { get_cme_def_body_attr_id(cme) };
39253926 if let Some(profiled_type) = profiled_type {
39263927 let argc = unsafe { vm_ci_argc(ci) } as i32;
3928+ // TODO: attr_writer SetIvar has a null inline cache and may target a receiver
3929+ // operand other than CFP self. Support it with a reprofile strategy that
3930+ // profiles the receiver operand even after the send insn has finished profiling.
3931+ let recompile = None;
39273932 recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state, recompile: Some(Recompile::ProfileSend{ argc }) });
3933+ self.try_emit_optimized_setivar(block, recv, id, val, profiled_type, state, recompile).unwrap_or_else(|counter| {
3934+ self.count(block, counter);
3935+ self.push_insn(block, Insn::SetIvar { self_val: recv, id, ic: std::ptr::null(), val, state });
3936+ });
3937+ } else {
3938+ // No shape information, just static class information
3939+ self.push_insn(block, Insn::SetIvar { self_val: recv, id, ic: std::ptr::null(), val, state });
39283940 }
3929- let id = unsafe { get_cme_def_body_attr_id(cme) };
3930-
3931- self.push_insn(block, Insn::SetIvar { self_val: recv, id, ic: std::ptr::null(), val, state });
39323941 self.make_equal_to(insn_id, val);
39333942 } else if !has_block && def_type == VM_METHOD_TYPE_OPTIMIZED {
39343943 let opt_type: OptimizedMethodType = unsafe { get_cme_def_body_optimized_type(cme) }.into();
@@ -4938,106 +4947,97 @@ impl Function {
49384947 Ok(self.load_ivar(block, self_val, profiled_type, id))
49394948 }
49404949
4941- fn optimize_getivar(&mut self) {
4942- for block in self.reverse_post_order() {
4943- let old_insns = std::mem::take(&mut self.blocks[block.0].insns);
4944- assert!(self.blocks[block.0].insns.is_empty());
4945- for insn_id in old_insns {
4946- match self.find(insn_id) {
4947- Insn::SetIvar { self_val, id, val, state, ic } => {
4948- let Some(recv_type) = self.profiled_type_of_at(self_val, state) else {
4949- // No (monomorphic/skewed polymorphic) profile info
4950- self.count(block, Counter::setivar_fallback_not_monomorphic);
4951- self.push_insn_id(block, insn_id); continue;
4952- };
4953- if recv_type.flags().is_immediate() {
4954- // Instance variable lookups on immediate values are always nil
4955- self.count(block, Counter::setivar_fallback_immediate);
4956- self.push_insn_id(block, insn_id); continue;
4957- }
4958- assert!(recv_type.shape().is_valid());
4959- if !recv_type.flags().is_t_object() {
4960- // Check if the receiver is a T_OBJECT
4961- self.count(block, Counter::setivar_fallback_not_t_object);
4962- self.push_insn_id(block, insn_id); continue;
4963- }
4964- if recv_type.shape().is_complex() {
4965- // too-complex shapes can't use index access
4966- self.count(block, Counter::setivar_fallback_complex);
4967- self.push_insn_id(block, insn_id); continue;
4968- }
4969- if recv_type.shape().is_frozen() {
4970- // Can't set ivars on frozen objects
4971- self.count(block, Counter::setivar_fallback_frozen);
4972- self.push_insn_id(block, insn_id); continue;
4973- }
4974- if self.policy.no_side_exits {
4975- // TODO: Support polymorphic SetIvar shape-specialized paths.
4976- // https://github.com/Shopify/ruby/issues/927
4977- // On the final version, keep the SetIvar fallback instead of another shape guard.
4978- self.push_insn_id(block, insn_id); continue;
4979- }
4980- let mut ivar_index: attr_index_t = 0;
4981- let mut next_shape_id = recv_type.shape();
4982- if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } {
4983- // Current shape does not contain this ivar; do a shape transition.
4984- let current_shape_id = recv_type.shape();
4985- let class = recv_type.class();
4986- // We're only looking at T_OBJECT so ignore all of the imemo stuff.
4987- assert!(recv_type.flags().is_t_object());
4988- next_shape_id = ShapeId(unsafe { rb_shape_transition_add_ivar_no_warnings(current_shape_id.0, id, class) });
4989- // If the VM ran out of shapes, or this class generated too many leaf,
4990- // it may be de-optimized into OBJ_COMPLEX_SHAPE (hash-table).
4991- let new_shape_complex = unsafe { rb_jit_shape_complex_p(next_shape_id.0) };
4992- // TODO(max): Is it OK to bail out here after making a shape transition?
4993- if new_shape_complex {
4994- self.count(block, Counter::setivar_fallback_new_shape_complex);
4995- self.push_insn_id(block, insn_id); continue;
4996- }
4997- let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) };
4998- assert!(ivar_result, "New shape must have the ivar index");
4999- let current_capacity = unsafe { rb_jit_shape_capacity(current_shape_id.0) };
5000- let next_capacity = unsafe { rb_jit_shape_capacity(next_shape_id.0) };
5001- // If the new shape has a different capacity, or is COMPLEX, we'll have to
5002- // reallocate it.
5003- let needs_extension = next_capacity != current_capacity;
5004- if needs_extension {
5005- self.count(block, Counter::setivar_fallback_new_shape_needs_extension);
5006- self.push_insn_id(block, insn_id); continue;
5007- }
5008- // Fall through to emitting the ivar write
5009- }
5010- let self_val = self.guard_heap(block, self_val, state);
5011- let shape = self.load_shape(block, self_val);
5012- // TODO: attr_writer SetIvar has a null inline cache and may target a receiver
5013- // operand other than CFP self. Support it with a reprofile strategy that
5014- // profiles the receiver operand even after the send insn has finished profiling.
5015- let recompile = if ic.is_null() { None } else { Some(Recompile::ProfileSelf) };
5016- self.guard_shape(block, shape, recv_type.shape(), state, recompile);
5017- // Current shape contains this ivar
5018- let (ivar_storage, offset) = if recv_type.flags().is_embedded() {
5019- // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
5020- let offset = ROBJECT_OFFSET_AS_ARY + (SIZEOF_VALUE * ivar_index.to_usize()) as i32;
5021- (self_val, offset)
5022- } else {
5023- let as_heap = self.push_insn(block, Insn::LoadField { recv: self_val, id: FieldName::as_heap, offset: ROBJECT_OFFSET_AS_HEAP_FIELDS, return_type: types::CPtr });
5024- let offset = SIZEOF_VALUE_I32 * ivar_index as i32;
5025- (as_heap, offset)
5026- };
5027- self.push_insn(block, Insn::StoreField { recv: ivar_storage, id: id.into(), offset, val });
5028- self.push_insn(block, Insn::WriteBarrier { recv: self_val, val });
5029- if next_shape_id != recv_type.shape() {
5030- // Write the new shape ID
5031- let shape_id = self.push_insn(block, Insn::Const { val: Const::CShape(next_shape_id) });
5032- let shape_id_offset = unsafe { rb_shape_id_offset() };
5033- self.push_insn(block, Insn::StoreField { recv: self_val, id: FieldName::shape_id, offset: shape_id_offset, val: shape_id });
5034- }
5035- }
5036- _ => { self.push_insn_id(block, insn_id); }
5037- }
5038- }
4950+ fn try_emit_optimized_setivar(&mut self, block: BlockId, self_val: InsnId, id: ID, val: InsnId, profiled_type: ProfiledType, state: InsnId, recompile: Option<Recompile>) -> Result<(), Counter> {
4951+ if profiled_type.flags().is_immediate() {
4952+ // Instance variable lookups on immediate values are always nil
4953+ return Err(Counter::setivar_fallback_immediate);
50394954 }
5040- crate::stats::trace_compile_phase("infer_types", || self.infer_types());
4955+ if !profiled_type.flags().is_t_object() {
4956+ // Check if the receiver is a T_OBJECT
4957+ return Err(Counter::setivar_fallback_not_t_object);
4958+ }
4959+ assert!(profiled_type.shape().is_valid());
4960+ if profiled_type.shape().is_frozen() {
4961+ // Can't set ivars on frozen objects
4962+ return Err(Counter::setivar_fallback_frozen);
4963+ }
4964+ if profiled_type.shape().is_complex() {
4965+ // too-complex shapes can't use index access
4966+ return Err(Counter::setivar_fallback_complex);
4967+ }
4968+ if self.policy.no_side_exits {
4969+ // On the final version, skip GetIvar shape specialization.
4970+ // iseq_to_hir already generates polymorphic branches with a
4971+ // GetIvar C call fallback for getinstancevariable, so we don't
4972+ // need to wrap it again here.
4973+ return Err(Counter::setivar_fallback_no_side_exits);
4974+ }
4975+
4976+ let mut ivar_index: attr_index_t = 0;
4977+ let mut next_shape_id = profiled_type.shape();
4978+ if !unsafe { rb_shape_get_iv_index(profiled_type.shape().0, id, &mut ivar_index) } {
4979+ // Current shape does not contain this ivar; do a shape transition.
4980+ let current_shape_id = profiled_type.shape();
4981+ let class = profiled_type.class();
4982+ // We're only looking at T_OBJECT so ignore all of the imemo stuff.
4983+ assert!(profiled_type.flags().is_t_object());
4984+ next_shape_id = ShapeId(unsafe { rb_shape_transition_add_ivar_no_warnings(current_shape_id.0, id, class) });
4985+ // If the VM ran out of shapes, or this class generated too many leaf,
4986+ // it may be de-optimized into OBJ_COMPLEX_SHAPE (hash-table).
4987+ let new_shape_complex = unsafe { rb_jit_shape_complex_p(next_shape_id.0) };
4988+ // TODO(max): Is it OK to bail out here after making a shape transition?
4989+ if new_shape_complex {
4990+ return Err(Counter::setivar_fallback_new_shape_complex);
4991+ }
4992+ let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) };
4993+ assert!(ivar_result, "New shape must have the ivar index");
4994+ let current_capacity = unsafe { rb_jit_shape_capacity(current_shape_id.0) };
4995+ let next_capacity = unsafe { rb_jit_shape_capacity(next_shape_id.0) };
4996+ // If the new shape has a different capacity, or is COMPLEX, we'll have to
4997+ // reallocate it.
4998+ let needs_extension = next_capacity != current_capacity;
4999+ if needs_extension {
5000+ return Err(Counter::setivar_fallback_new_shape_needs_extension);
5001+ }
5002+ // Fall through to emitting the ivar write
5003+ }
5004+ let self_val = self.guard_heap(block, self_val, state);
5005+ let shape = self.load_shape(block, self_val);
5006+ self.guard_shape(block, shape, profiled_type.shape(), state, recompile);
5007+ // Current shape contains this ivar
5008+ let (ivar_storage, offset) = if profiled_type.flags().is_embedded() {
5009+ // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
5010+ let offset = ROBJECT_OFFSET_AS_ARY + (SIZEOF_VALUE * ivar_index.to_usize()) as i32;
5011+ (self_val, offset)
5012+ } else {
5013+ let as_heap = self.push_insn(block, Insn::LoadField { recv: self_val, id: FieldName::as_heap, offset: ROBJECT_OFFSET_AS_HEAP_FIELDS, return_type: types::CPtr });
5014+ let offset = SIZEOF_VALUE_I32 * ivar_index as i32;
5015+ (as_heap, offset)
5016+ };
5017+ self.push_insn(block, Insn::StoreField { recv: ivar_storage, id: id.into(), offset, val });
5018+ self.push_insn(block, Insn::WriteBarrier { recv: self_val, val });
5019+ if next_shape_id != profiled_type.shape() {
5020+ // Write the new shape ID
5021+ let shape_id = self.push_insn(block, Insn::Const { val: Const::CShape(next_shape_id) });
5022+ let shape_id_offset = unsafe { rb_shape_id_offset() };
5023+ self.push_insn(block, Insn::StoreField { recv: self_val, id: FieldName::shape_id, offset: shape_id_offset, val: shape_id });
5024+ }
5025+ Ok(())
5026+ }
5027+
5028+ fn optimize_getivar(&mut self) {
5029+ // for block in self.reverse_post_order() {
5030+ // let old_insns = std::mem::take(&mut self.blocks[block.0].insns);
5031+ // assert!(self.blocks[block.0].insns.is_empty());
5032+ // for insn_id in old_insns {
5033+ // match self.find(insn_id) {
5034+ // Insn::SetIvar { self_val, id, val, state, ic } => {
5035+ // }
5036+ // _ => { self.push_insn_id(block, insn_id); }
5037+ // }
5038+ // }
5039+ // }
5040+ // crate::stats::trace_compile_phase("infer_types", || self.infer_types());
50415041 }
50425042
50435043 fn gen_patch_points_for_optimized_ccall(&mut self, block: BlockId, recv_class: VALUE, method_id: ID, cme: *const rb_callable_method_entry_struct, state: InsnId) {
@@ -9065,7 +9065,7 @@ fn add_iseq_to_hir(
90659065 }
90669066 YARVINSN_setinstancevariable => {
90679067 let id = ID(get_arg(pc, 0).as_u64());
9068- let ic = get_arg(pc, 1).as_ptr();
9068+ let ic: *const iseq_inline_iv_cache_entry = get_arg(pc, 1).as_ptr();
90699069 // Assume single-Ractor mode to omit gen_prepare_non_leaf_call on gen_setivar
90709070 // TODO: We only really need this if self_val is a class/module
90719071 if !fun.assume_single_ractor_mode(block, exit_id) {
@@ -9074,7 +9074,16 @@ fn add_iseq_to_hir(
90749074 break; // End the block
90759075 }
90769076 let val = state.stack_pop()?;
9077- fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id });
9077+ if let Some(profiled_type) = fun.monomorphic_summary(&profiles, self_param, exit_id) {
9078+ // TODO(max): Assert ic is never null
9079+ let recompile = if ic.is_null() { None } else { Some(Recompile::ProfileSelf) };
9080+ fun.try_emit_optimized_setivar(block, self_param, id, val, profiled_type, exit_id, recompile).unwrap_or_else(|counter| {
9081+ fun.count(block, counter);
9082+ fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id });
9083+ });
9084+ } else {
9085+ fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id });
9086+ }
90789087 // SetIvar will raise if self is an immediate. If it raises, we will have
90799088 // exited JIT code. So upgrade the type within JIT code to a heap object.
90809089 self_param = fun.push_insn(block, Insn::RefineType { val: self_param, new_type: types::HeapBasicObject });
0 commit comments