|
7 | 7 | #![allow(clippy::match_like_matches_macro)] |
8 | 8 | use crate::{ |
9 | 9 | backend::lir::C_ARG_OPNDS, |
10 | | - cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState, json::Json |
| 10 | + cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, invariants::has_singleton_class_of, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState, json::Json |
11 | 11 | }; |
12 | 12 | use std::{ |
13 | 13 | cell::RefCell, collections::{BTreeSet, HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter |
@@ -644,6 +644,9 @@ pub enum SendFallbackReason { |
644 | 644 | ComplexArgPass, |
645 | 645 | /// Caller has keyword arguments but callee doesn't expect them; need to convert to hash. |
646 | 646 | UnexpectedKeywordArgs, |
| 647 | + /// A singleton class has been seen for the receiver class, so we skip the optimization |
| 648 | + /// to avoid an invalidation loop. |
| 649 | + SingletonClassSeen, |
647 | 650 | /// Initial fallback reason for every instruction, which should be mutated to |
648 | 651 | /// a more actionable reason when an attempt to specialize the instruction fails. |
649 | 652 | Uncategorized(ruby_vminsn_type), |
@@ -680,6 +683,7 @@ impl Display for SendFallbackReason { |
680 | 683 | ArgcParamMismatch => write!(f, "Argument count does not match parameter count"), |
681 | 684 | ComplexArgPass => write!(f, "Complex argument passing"), |
682 | 685 | UnexpectedKeywordArgs => write!(f, "Unexpected Keyword Args"), |
| 686 | + SingletonClassSeen => write!(f, "Singleton class previously created for receiver class"), |
683 | 687 | Uncategorized(insn) => write!(f, "Uncategorized({})", insn_name(*insn as usize)), |
684 | 688 | } |
685 | 689 | } |
@@ -1891,6 +1895,24 @@ impl Function { |
1891 | 1895 | } |
1892 | 1896 | } |
1893 | 1897 |
|
| 1898 | + /// Assume that objects of a given class will have no singleton class. |
| 1899 | + /// Returns true if safe to assume so and emits a PatchPoint. |
| 1900 | + /// Returns false if we've already seen a singleton class for this class, |
| 1901 | + /// to avoid an invalidation loop. |
| 1902 | + pub fn assume_no_singleton_classes(&mut self, block: BlockId, klass: VALUE, state: InsnId) -> bool { |
| 1903 | + if !klass.instance_can_have_singleton_class() { |
| 1904 | + // This class can never have a singleton class, so no patchpoint needed. |
| 1905 | + return true; |
| 1906 | + } |
| 1907 | + if has_singleton_class_of(klass) { |
| 1908 | + // We've seen a singleton class for this klass. Disable the optimization |
| 1909 | + // to avoid an invalidation loop. |
| 1910 | + return false; |
| 1911 | + } |
| 1912 | + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); |
| 1913 | + true |
| 1914 | + } |
| 1915 | + |
1894 | 1916 | /// Return a copy of the instruction where the instruction and its operands have been read from |
1895 | 1917 | /// the union-find table (to find the current most-optimized version of this instruction). See |
1896 | 1918 | /// [`UnionFind`] for more. |
@@ -2531,8 +2553,8 @@ impl Function { |
2531 | 2553 | return false; |
2532 | 2554 | } |
2533 | 2555 | self.gen_patch_points_for_optimized_ccall(block, class, method_id, cme, state); |
2534 | | - if class.instance_can_have_singleton_class() { |
2535 | | - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: class }, state }); |
| 2556 | + if !self.assume_no_singleton_classes(block, class, state) { |
| 2557 | + return false; |
2536 | 2558 | } |
2537 | 2559 | true |
2538 | 2560 | } |
@@ -2702,10 +2724,12 @@ impl Function { |
2702 | 2724 | if !can_direct_send(self, block, iseq, insn_id, args.as_slice()) { |
2703 | 2725 | self.push_insn_id(block, insn_id); continue; |
2704 | 2726 | } |
2705 | | - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); |
2706 | | - if klass.instance_can_have_singleton_class() { |
2707 | | - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); |
| 2727 | + // Check singleton class assumption first, before emitting other patchpoints |
| 2728 | + if !self.assume_no_singleton_classes(block, klass, state) { |
| 2729 | + self.set_dynamic_send_reason(insn_id, SingletonClassSeen); |
| 2730 | + self.push_insn_id(block, insn_id); continue; |
2708 | 2731 | } |
| 2732 | + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); |
2709 | 2733 | if let Some(profiled_type) = profiled_type { |
2710 | 2734 | recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); |
2711 | 2735 | } |
@@ -2754,10 +2778,12 @@ impl Function { |
2754 | 2778 | // TODO(alan): Turn this into a ractor belonging guard to work better in multi ractor mode. |
2755 | 2779 | self.push_insn_id(block, insn_id); continue; |
2756 | 2780 | } |
2757 | | - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); |
2758 | | - if klass.instance_can_have_singleton_class() { |
2759 | | - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); |
| 2781 | + // Check singleton class assumption first, before emitting other patchpoints |
| 2782 | + if !self.assume_no_singleton_classes(block, klass, state) { |
| 2783 | + self.set_dynamic_send_reason(insn_id, SingletonClassSeen); |
| 2784 | + self.push_insn_id(block, insn_id); continue; |
2760 | 2785 | } |
| 2786 | + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); |
2761 | 2787 |
|
2762 | 2788 | if let Some(profiled_type) = profiled_type { |
2763 | 2789 | recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); |
@@ -2788,11 +2814,13 @@ impl Function { |
2788 | 2814 | if self.is_metaclass(klass) && !self.assume_single_ractor_mode(block, state) { |
2789 | 2815 | self.push_insn_id(block, insn_id); continue; |
2790 | 2816 | } |
| 2817 | + // Check singleton class assumption first, before emitting other patchpoints |
| 2818 | + if !self.assume_no_singleton_classes(block, klass, state) { |
| 2819 | + self.set_dynamic_send_reason(insn_id, SingletonClassSeen); |
| 2820 | + self.push_insn_id(block, insn_id); continue; |
| 2821 | + } |
2791 | 2822 |
|
2792 | 2823 | self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); |
2793 | | - if klass.instance_can_have_singleton_class() { |
2794 | | - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); |
2795 | | - } |
2796 | 2824 | if let Some(profiled_type) = profiled_type { |
2797 | 2825 | recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); |
2798 | 2826 | } |
@@ -2835,10 +2863,12 @@ impl Function { |
2835 | 2863 | // No (monomorphic/skewed polymorphic) profile info |
2836 | 2864 | self.push_insn_id(block, insn_id); continue; |
2837 | 2865 | }; |
2838 | | - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); |
2839 | | - if klass.instance_can_have_singleton_class() { |
2840 | | - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); |
| 2866 | + // Check singleton class assumption first, before emitting other patchpoints |
| 2867 | + if !self.assume_no_singleton_classes(block, klass, state) { |
| 2868 | + self.set_dynamic_send_reason(insn_id, SingletonClassSeen); |
| 2869 | + self.push_insn_id(block, insn_id); continue; |
2841 | 2870 | } |
| 2871 | + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); |
2842 | 2872 | if let Some(profiled_type) = profiled_type { |
2843 | 2873 | recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); |
2844 | 2874 | } |
@@ -3375,11 +3405,14 @@ impl Function { |
3375 | 3405 | return Err(()); |
3376 | 3406 | } |
3377 | 3407 |
|
| 3408 | + // Check singleton class assumption first, before emitting other patchpoints |
| 3409 | + if !fun.assume_no_singleton_classes(block, recv_class, state) { |
| 3410 | + fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen); |
| 3411 | + return Err(()); |
| 3412 | + } |
| 3413 | + |
3378 | 3414 | // Commit to the replacement. Put PatchPoint. |
3379 | 3415 | fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); |
3380 | | - if recv_class.instance_can_have_singleton_class() { |
3381 | | - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); |
3382 | | - } |
3383 | 3416 |
|
3384 | 3417 | if let Some(profiled_type) = profiled_type { |
3385 | 3418 | // Guard receiver class |
@@ -3410,11 +3443,15 @@ impl Function { |
3410 | 3443 | -1 => { |
3411 | 3444 | // The method gets a pointer to the first argument |
3412 | 3445 | // func(int argc, VALUE *argv, VALUE recv) |
3413 | | - fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); |
3414 | 3446 |
|
3415 | | - if recv_class.instance_can_have_singleton_class() { |
3416 | | - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); |
| 3447 | + // Check singleton class assumption first, before emitting other patchpoints |
| 3448 | + if !fun.assume_no_singleton_classes(block, recv_class, state) { |
| 3449 | + fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen); |
| 3450 | + return Err(()); |
3417 | 3451 | } |
| 3452 | + |
| 3453 | + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); |
| 3454 | + |
3418 | 3455 | if let Some(profiled_type) = profiled_type { |
3419 | 3456 | // Guard receiver class |
3420 | 3457 | recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); |
@@ -3522,11 +3559,14 @@ impl Function { |
3522 | 3559 | return Err(()); |
3523 | 3560 | } |
3524 | 3561 |
|
| 3562 | + // Check singleton class assumption first, before emitting other patchpoints |
| 3563 | + if !fun.assume_no_singleton_classes(block, recv_class, state) { |
| 3564 | + fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen); |
| 3565 | + return Err(()); |
| 3566 | + } |
| 3567 | + |
3525 | 3568 | // Commit to the replacement. Put PatchPoint. |
3526 | 3569 | fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); |
3527 | | - if recv_class.instance_can_have_singleton_class() { |
3528 | | - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); |
3529 | | - } |
3530 | 3570 |
|
3531 | 3571 | let props = ZJITState::get_method_annotations().get_cfunc_properties(cme); |
3532 | 3572 | if props.is_none() && get_option!(stats) { |
@@ -3599,11 +3639,14 @@ impl Function { |
3599 | 3639 | fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); |
3600 | 3640 | return Err(()); |
3601 | 3641 | } else { |
| 3642 | + // Check singleton class assumption first, before emitting other patchpoints |
| 3643 | + if !fun.assume_no_singleton_classes(block, recv_class, state) { |
| 3644 | + fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen); |
| 3645 | + return Err(()); |
| 3646 | + } |
| 3647 | + |
3602 | 3648 | fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); |
3603 | 3649 |
|
3604 | | - if recv_class.instance_can_have_singleton_class() { |
3605 | | - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); |
3606 | | - } |
3607 | 3650 | if let Some(profiled_type) = profiled_type { |
3608 | 3651 | // Guard receiver class |
3609 | 3652 | recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); |
|
0 commit comments