diff options
Diffstat (limited to 'zjit/src')
| -rw-r--r-- | zjit/src/backend/lir.rs | 10 | ||||
| -rw-r--r-- | zjit/src/codegen.rs | 146 | ||||
| -rw-r--r-- | zjit/src/codegen_tests.rs | 1 | ||||
| -rw-r--r-- | zjit/src/cruby.rs | 10 | ||||
| -rw-r--r-- | zjit/src/cruby_bindings.inc.rs | 4 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 18 | ||||
| -rw-r--r-- | zjit/src/hir/opt_tests.rs | 69 | ||||
| -rw-r--r-- | zjit/src/hir_type/gen_hir_type.rb | 7 | ||||
| -rw-r--r-- | zjit/src/hir_type/hir_type.inc.rs | 8 | ||||
| -rw-r--r-- | zjit/src/hir_type/mod.rs | 4 | ||||
| -rw-r--r-- | zjit/src/profile.rs | 16 |
11 files changed, 162 insertions, 131 deletions
diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index b29f832000..25fa0cc151 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -203,7 +203,7 @@ pub use crate::backend::current::{ mem_base_reg, Reg, EC, CFP, SP, - NATIVE_STACK_PTR, NATIVE_BASE_PTR, + NATIVE_BASE_PTR, C_ARG_OPNDS, C_RET_OPND, }; @@ -1406,10 +1406,9 @@ pub struct Assembler { /// On `compile`, it also disables the backend's use of them. pub(super) accept_scratch_reg: bool, - /// The Assembler can use NATIVE_BASE_PTR + stack_base_idx as the - /// first stack slot in case it needs to allocate memory. This is - /// equal to the number of spilled basic block arguments. - pub(super) stack_base_idx: usize, + /// The maximum number of stack slots that have been reserved + /// by Assembler::alloc_stack(). + pub stack_base_idx: usize, /// If Some, the next ccall should verify its leafness leaf_ccall_stack_size: Option<usize>, @@ -3603,6 +3602,7 @@ pub(crate) use asm_ccall; mod tests { use super::*; use insta::assert_snapshot; + use crate::backend::current::NATIVE_STACK_PTR; fn scratch_reg() -> Opnd { Assembler::new_with_scratch_reg().1 diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index fbf62a5c81..7c893a72fe 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -19,13 +19,17 @@ use crate::state::ZJITState; use crate::stats::{CompileError, exit_counter_for_compile_error, exit_counter_for_unhandled_hir_insn, incr_counter, incr_counter_by, send_fallback_counter, send_fallback_counter_for_method_type, send_fallback_counter_for_super_method_type, send_fallback_counter_ptr_for_opcode, send_without_block_fallback_counter_for_method_type, send_without_block_fallback_counter_for_optimized_method_type}; use crate::stats::{counter_ptr, with_time_stat, trace_compile_phase, Counter, Counter::{compile_time_ns, exit_compile_error}}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; -use crate::backend::lir::{self, Assembler, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, NATIVE_STACK_PTR, Opnd, SP, SideExit, SideExitRecompile, Target, asm_ccall, asm_comment}; +use crate::backend::lir::{self, Assembler, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, Opnd, SP, SideExit, SideExitRecompile, Target, asm_ccall, asm_comment}; use crate::hir::{iseq_to_hir, BlockId, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; use crate::hir::{BlockHandler, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendFallbackReason}; use crate::hir_type::{types, Type}; use crate::options::{get_option, PerfMap}; use crate::cast::IntoUsize; +/// The number of stack slots used for JITFrame. gen_save_pc_for_gc() writes +/// JITFrame into this number of slots at the bottom of the native stack. +const JIT_FRAME_SIZE: usize = 1; + /// Default maximum number of compiled versions per ISEQ. const DEFAULT_MAX_VERSIONS: usize = 2; @@ -66,17 +70,22 @@ struct JITState { /// ISEQ calls that need to be compiled later iseq_calls: Vec<IseqCallRef>, + + /// The number of native stack slots reserved for JITFrame. + /// gen_save_pc_for_gc() writes JITFrame into the allocated space. + jit_frame_size: usize, } impl JITState { /// Create a new JITState instance - fn new(version: IseqVersionRef, num_insns: usize, num_blocks: usize) -> Self { + fn new(version: IseqVersionRef, num_insns: usize, num_blocks: usize, jit_frame_size: usize) -> Self { JITState { version, opnds: vec![None; num_insns], labels: vec![None; num_blocks], jit_entries: Vec::default(), iseq_calls: Vec::default(), + jit_frame_size, } } @@ -374,9 +383,8 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, mut version: IseqVersionRef, /// Compile a function fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, function: &Function) -> Result<(IseqCodePtrs, Vec<CodePtr>, Vec<IseqCallRef>), CompileError> { let (mut jit, asm) = trace_compile_phase("codegen", || { - let num_spilled_params = max_num_params(function).saturating_sub(ALLOC_REGS.len()); - let mut jit = JITState::new(version, function.num_insns(), function.num_blocks()); - let mut asm = Assembler::new_with_stack_slots(num_spilled_params + 1); // +1 for JITFrame + let mut jit = JITState::new(version, function.num_insns(), function.num_blocks(), JIT_FRAME_SIZE); + let mut asm = Assembler::new_with_stack_slots(JIT_FRAME_SIZE); // Mapping from HIR block IDs to LIR block IDs. // This is is a one-to-one mapping from HIR to LIR blocks used for finding @@ -583,7 +591,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio gen_const_uint32(val.0) } Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"), - Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)), + Insn::NewArray { elements, state } => gen_new_array(jit, asm, opnds!(elements), &function.frame_state(*state)), Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), Insn::NewRange { low, high, flag, state } => gen_new_range(jit, asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), @@ -1139,10 +1147,9 @@ fn gen_ccall_variadic( asm.mov(CFP, new_cfp); asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); - let argv_ptr = gen_push_opnds(asm, &args); + let argv_ptr = gen_push_opnds(jit, asm, &args); asm.count_call_to(&name.contents_lossy()); let result = asm.ccall(cfunc, vec![args.len().into(), argv_ptr, recv]); - gen_pop_opnds(asm, &args); asm_comment!(asm, "pop C frame"); let new_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); @@ -1699,7 +1706,7 @@ fn gen_invokeblock_ifunc( gen_prepare_non_leaf_call(jit, asm, state); // Push args to memory so we can pass argv pointer - let argv_ptr = gen_push_opnds(asm, &args); + let argv_ptr = gen_push_opnds(jit, asm, &args); // Untag the block handler to get the captured block pointer // captured = block_handler & ~0x3 @@ -1715,9 +1722,7 @@ fn gen_invokeblock_ifunc( argv: *const VALUE, ) -> VALUE; } - let result = asm_ccall!(asm, rb_vm_yield_with_cfunc, EC, captured, (args.len() as i64).into(), argv_ptr); - gen_pop_opnds(asm, &args); - result + asm_ccall!(asm, rb_vm_yield_with_cfunc, EC, captured, (args.len() as i64).into(), argv_ptr) } fn gen_invokeproc( @@ -1732,9 +1737,9 @@ fn gen_invokeproc( asm_comment!(asm, "call invokeproc"); - let argv_ptr = gen_push_opnds(asm, &args); + let argv_ptr = gen_push_opnds(jit, asm, &args); let kw_splat_opnd = Opnd::Imm(i64::from(kw_splat)); - let result = asm_ccall!( + asm_ccall!( asm, rb_optimized_call, recv, @@ -1743,10 +1748,7 @@ fn gen_invokeproc( argv_ptr, kw_splat_opnd, VM_BLOCK_HANDLER_NONE.into() - ); - gen_pop_opnds(asm, &args); - - result + ) } /// Compile a dynamic dispatch for `super` @@ -1820,6 +1822,7 @@ fn gen_array_dup( /// Compile a new array instruction fn gen_new_array( + jit: &JITState, asm: &mut Assembler, elements: Vec<Opnd>, state: &FrameState, @@ -1831,10 +1834,8 @@ fn gen_new_array( if elements.is_empty() { asm_ccall!(asm, rb_ec_ary_new_from_values, EC, 0i64.into(), Opnd::UImm(0)) } else { - let argv = gen_push_opnds(asm, &elements); - let new_array = asm_ccall!(asm, rb_ec_ary_new_from_values, EC, num.into(), argv); - gen_pop_opnds(asm, &elements); - new_array + let argv = gen_push_opnds(jit, asm, &elements); + asm_ccall!(asm, rb_ec_ary_new_from_values, EC, num.into(), argv) } } @@ -2117,10 +2118,8 @@ fn gen_new_hash( } else { gen_prepare_non_leaf_call(jit, asm, state); - let argv = gen_push_opnds(asm, &elements); - let hash = asm_ccall!(asm, rb_hash_new_with_bulk_insert, elements.len().into(), argv); - gen_pop_opnds(asm, &elements); - hash + let argv = gen_push_opnds(jit, asm, &elements); + asm_ccall!(asm, rb_hash_new_with_bulk_insert, elements.len().into(), argv) } } @@ -2552,7 +2551,7 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard asm.cmp(klass, Opnd::Value(expected_class)); asm.jne(jit, side_exit); - } else if guard_type.is_subtype(types::TypedTData) { + } else if guard_type.is_subtype(types::TData) { let side = side_exit(jit, state, GuardType(guard_type)); // Check special constant @@ -2563,11 +2562,11 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard asm.cmp(val, Qfalse.into()); asm.je(jit, side.clone()); - // Check the builtin type and RUBY_TYPED_FL_IS_TYPED_DATA with mask and compare + // Check the T_DATA builtin type. let val = asm.load_mem(val); let flags = asm.load(Opnd::mem(VALUE_BITS, val, RUBY_OFFSET_RBASIC_FLAGS)); - let mask = RUBY_T_MASK.to_usize() | RUBY_TYPED_FL_IS_TYPED_DATA.to_usize(); - let expected = RUBY_T_DATA.to_usize() | RUBY_TYPED_FL_IS_TYPED_DATA.to_usize(); + let mask = RUBY_T_MASK.to_usize(); + let expected = RUBY_T_DATA.to_usize(); let masked = asm.and(flags, mask.into()); asm.cmp(masked, expected.into()); asm.jne(jit, side); @@ -2986,20 +2985,6 @@ fn build_side_exit(jit: &JITState, state: &FrameState) -> SideExit { } } -/// Returne the maximum number of arguments for a block in a given function -fn max_num_params(function: &Function) -> usize { - let reverse_post_order = function.reverse_post_order(); - reverse_post_order - .iter() - .filter(|&&block_id| function.is_entry_block(block_id)) - .map(|&block_id| { - let block = function.block(block_id); - block.params().len() - }) - .max() - .unwrap_or(0) -} - #[cfg(target_arch = "x86_64")] macro_rules! c_callable { ($(#[$outer:meta])* @@ -3362,21 +3347,18 @@ pub fn gen_exit_trampoline_with_counter(cb: &mut CodeBlock, exit_trampoline: Cod }) } -fn gen_push_opnds(asm: &mut Assembler, opnds: &[Opnd]) -> lir::Opnd { - let n = opnds.len(); - let allocation_size = aligned_stack_bytes(n); - - // Bump the stack pointer to reserve the space for opnds - if n != 0 { - asm_comment!(asm, "allocate {} bytes on C stack for {} values", allocation_size, n); - asm.sub_into(NATIVE_STACK_PTR, allocation_size.into()); +/// Reserve native stack space and write operands into it. +fn gen_push_opnds(jit: &JITState, asm: &mut Assembler, opnds: &[Opnd]) -> lir::Opnd { + let argv = if opnds.len() > 0 { + // Make sure the Assembler will reserve a sufficient stack size for given opnds + asm_comment!(asm, "allocate space on C stack for {} values", opnds.len()); + asm.alloc_stack(jit, opnds.len()) } else { asm_comment!(asm, "no opnds to allocate"); - } + Opnd::UImm(0) + }; - // Load NATIVE_STACK_PTR to get the address of a returned array - // to allow the backend to move it for its own use. - let argv = asm.load(NATIVE_STACK_PTR); + // Write operands into stack slots allocated by asm.alloc_stack() for (idx, &opnd) in opnds.iter().enumerate() { asm.mov(Opnd::mem(VALUE_BITS, argv, idx as i32 * SIZEOF_VALUE_I32), opnd); } @@ -3384,35 +3366,18 @@ fn gen_push_opnds(asm: &mut Assembler, opnds: &[Opnd]) -> lir::Opnd { argv } -fn gen_pop_opnds(asm: &mut Assembler, opnds: &[Opnd]) { - if opnds.is_empty() { - asm_comment!(asm, "no opnds to restore"); - return - } - - asm_comment!(asm, "restore C stack pointer"); - let allocation_size = aligned_stack_bytes(opnds.len()); - asm.add_into(NATIVE_STACK_PTR, allocation_size.into()); -} - fn gen_toregexp(jit: &mut JITState, asm: &mut Assembler, opt: usize, values: Vec<Opnd>, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - let first_opnd_ptr = gen_push_opnds(asm, &values); - let result = asm_ccall!(asm, rb_reg_new_from_values, values.len().into(), first_opnd_ptr, opt.into()); - gen_pop_opnds(asm, &values); - - result + let first_opnd_ptr = gen_push_opnds(jit, asm, &values); + asm_ccall!(asm, rb_reg_new_from_values, values.len().into(), first_opnd_ptr, opt.into()) } fn gen_string_concat(jit: &mut JITState, asm: &mut Assembler, strings: Vec<Opnd>, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - let first_string_ptr = gen_push_opnds(asm, &strings); - let result = asm_ccall!(asm, rb_str_concat_literals, strings.len().into(), first_string_ptr); - gen_pop_opnds(asm, &strings); - - result + let first_string_ptr = gen_push_opnds(jit, asm, &strings); + asm_ccall!(asm, rb_str_concat_literals, strings.len().into(), first_string_ptr) } // Generate RSTRING_PTR @@ -3484,6 +3449,33 @@ fn aligned_stack_bytes(num_slots: usize) -> usize { } impl Assembler { + /// Allocate stack space on top of the stack slots reserved for JITFrame, + /// and return a pointer to the allocated space. + fn alloc_stack(&mut self, jit: &JITState, stack_size: usize) -> Opnd { + let total_stack_size = jit.jit_frame_size + stack_size; + self.stack_base_idx = self.stack_base_idx.max(total_stack_size); + // high addr + // +------------------------+ + // | return address | + // +------------------------+ + // | previous frame pointer | <- NATIVE_BASE_PTR == cfp->jit_return + // +------------------------+ + // | JITFrame pointer | <- jit.jit_frame_size, read by CFP_ZJIT_FRAME(cfp) + // +------------------------+ + // | opnds.last() | + // +------------------------+ + // | ... | + // +------------------------+ + // | opnds.first() | <- pointer returned by alloc_stack() + // +------------------------+ + // | register spill slots | if any + // +------------------------+ + // | FrameSetup align slot | if needed + // +------------------------+ + // low addr + self.sub(NATIVE_BASE_PTR, (SIZEOF_VALUE * total_stack_size).into()) + } + /// Emits a load for memory based operands and returns a vreg, /// otherwise returns recv. fn load_mem(&mut self, recv: Opnd) -> Opnd { diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index fd6367b7a3..b099371718 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -26,6 +26,7 @@ fn test_breakpoint_hir_codegen() { IseqVersion::new(iseq), function.num_insns(), function.num_blocks(), + 0, ); let mut asm = Assembler::new(); asm.new_block_without_id("test"); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 14db7c57d7..eb3241b0a2 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -608,15 +608,13 @@ impl VALUE { unsafe { rb_jit_class_fields_embedded_p(self) } } - /// Typed `T_DATA` made from `TypedData_Make_Struct()` (e.g. Thread, ARGF) - pub fn typed_data_p(self) -> bool { + pub fn data_p(self) -> bool { !self.special_const_p() && - self.builtin_type() == RUBY_T_DATA && - 0 != (self.builtin_flags() & RUBY_TYPED_FL_IS_TYPED_DATA.to_usize()) + self.builtin_type() == RUBY_T_DATA } - pub fn typed_data_fields_embedded_p(self) -> bool { - unsafe { rb_jit_typed_data_fields_embedded_p(self) } + pub fn data_fields_embedded_p(self) -> bool { + unsafe { rb_jit_data_fields_embedded_p(self) } } pub fn as_fixnum(self) -> i64 { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 21a2f6bbb4..dad29087be 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1929,7 +1929,7 @@ pub type zjit_struct_offsets = u32; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RCLASS_OFFSET_PRIME_FIELDS_OBJ: jit_bindgen_constants = 40; -pub const RTYPEDDATA_OFFSET_FIELDS_OBJ: jit_bindgen_constants = 16; +pub const TDATA_OFFSET_FIELDS_OBJ: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; pub const RB_SHAPE_FLAG_SHIFT: jit_bindgen_constants = 32; pub const RUBY_OFFSET_EC_CFP: jit_bindgen_constants = 16; @@ -2294,7 +2294,7 @@ unsafe extern "C" { pub fn rb_jit_shape_complex_p(shape_id: shape_id_t) -> bool; pub fn rb_jit_multi_ractor_p() -> bool; pub fn rb_jit_class_fields_embedded_p(klass: VALUE) -> bool; - pub fn rb_jit_typed_data_fields_embedded_p(obj: VALUE) -> bool; + pub fn rb_jit_data_fields_embedded_p(obj: VALUE) -> bool; pub fn rb_jit_vm_lock_then_barrier( recursive_lock_level: *mut ::std::os::raw::c_uint, file: *const ::std::os::raw::c_char, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 02ef9e0a1f..86078f4ac8 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4147,7 +4147,6 @@ impl Function { if let Some(replacement) = (props.inline)(self, tmp_block, recv, &args, state) { // Copy contents of tmp_block to block assert_ne!(block, tmp_block); - emit_super_call_guards(self, block, super_cme, current_cme, mid, state); let insns = std::mem::take(&mut self.blocks[tmp_block.0].insns); self.blocks[block.0].insns.extend(insns); self.count(block, Counter::inline_cfunc_optimized_send_count); @@ -4336,8 +4335,8 @@ impl Function { self.push_insn(block, Insn::GuardType { val: recv, guard_type: types::Class, state }) } else if recv_type.flags().is_t_module() { self.push_insn(block, Insn::GuardType { val: recv, guard_type: types::Module, state }) - } else if recv_type.flags().is_typed_data() { - self.push_insn(block, Insn::GuardType { val: recv, guard_type: types::TypedTData, state }) + } else if recv_type.flags().is_t_data() { + self.push_insn(block, Insn::GuardType { val: recv, guard_type: types::TData, state }) } else { // HeapBasicObject is wider than T_OBJECT, but shapes for T_OBJECTs are in a pool of // its own and are guaranteed to be different from shapes of any other T_* types. So @@ -4373,11 +4372,10 @@ impl Function { }); return self.load_ivar_from_fields(block, fields_obj, recv_type.flags().is_fields_embedded(), id, ivar_index); } - if recv_type.flags().is_typed_data() { - // Typed T_DATA: load from fields_obj at fixed offset in RTypedData + if recv_type.flags().is_t_data() { let fields_obj = self.push_insn(block, Insn::LoadField { recv: self_val, id: FieldName::fields_obj, - offset: RTYPEDDATA_OFFSET_FIELDS_OBJ as i32, + offset: TDATA_OFFSET_FIELDS_OBJ as i32, return_type: types::RubyValue, }); return self.load_ivar_from_fields(block, fields_obj, recv_type.flags().is_fields_embedded(), id, ivar_index); @@ -5318,6 +5316,14 @@ impl Function { Insn::Test { val } if self.type_of(val).is_known_truthy() => { self.new_insn(Insn::Const { val: Const::CBool(true) }) } + Insn::Test { val: test_val } => { + if let Insn::BoxBool { val: bool_val } = self.find(test_val) { + self.make_equal_to(insn_id, bool_val); + continue; + } else { + insn_id + } + } Insn::CondBranch { val, if_true, .. } if self.is_a(val, Type::from_cbool(true)) => { self.new_insn(Insn::Jump(if_true)) } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 4a83b40e38..74c0af710e 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -7912,8 +7912,8 @@ mod hir_opt_tests { } #[test] - fn test_optimize_getivar_on_typed_data() { - // Thread is typed T_DATA: uses LoadField chain via RTypedData fields_obj + fn test_optimize_getivar_on_t_data() { + // T_DATA uses fields_obj for instance variables. eval(" class C < Thread def test = @a @@ -7936,7 +7936,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v17:TypedTData = GuardType v6, TypedTData + v17:TData = GuardType v6, TData v18:CShape = LoadField v17, :shape_id@0x1000 v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) recompile v20:RubyValue = LoadField v17, :fields_obj@0x1002 @@ -7947,8 +7947,8 @@ mod hir_opt_tests { } #[test] - fn test_optimize_getivar_on_typed_data_complex_fields() { - // Typed T_DATA with enough ivars to force heap field storage + fn test_optimize_getivar_on_t_data_complex_fields() { + // T_DATA with enough ivars to force heap field storage eval(" class C < Thread def test = @var1000 @@ -8260,7 +8260,7 @@ mod hir_opt_tests { } #[test] - fn test_getivar_polymorphic_t_class_and_typed_data() { + fn test_getivar_polymorphic_t_class_and_t_data() { set_call_threshold(3); eval(r#" module Reader @@ -8295,7 +8295,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode v11:HeapBasicObject = GuardType v6, HeapBasicObject v12:CUInt64 = LoadField v11, :RBASIC_FLAGS@0x1000 - v14:CUInt64[0xffffffff0000005f] = Const CUInt64(0xffffffff0000005f) + v14:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f) v15:CPtr[CPtr(0x1001)] = Const CPtr(0x1001) v16 = RefineType v15, CUInt64 v17:CInt64 = IntAnd v12, v14 @@ -10010,11 +10010,6 @@ mod hir_opt_tests { v20:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v19, Value(VALUE(0x1040)) v21:RubyValue = LoadField v18, :VM_ENV_DATA_INDEX_SPECVAL@0x1048 v22:FalseClass = GuardBitEquals v21, Value(false) - v30:CPtr = GetEP 0 - v31:RubyValue = LoadField v30, :VM_ENV_DATA_INDEX_ME_CREF@0x1038 - v32:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v31, Value(VALUE(0x1040)) - v33:RubyValue = LoadField v30, :VM_ENV_DATA_INDEX_SPECVAL@0x1048 - v34:FalseClass = GuardBitEquals v33, Value(false) v23:Array = GuardType v6, Array v24:CUInt64 = LoadField v23, :RBASIC_FLAGS@0x1049 v25:CUInt64 = GuardNoBitsSet v24, RUBY_FL_FREEZE=CUInt64(2048) @@ -10054,11 +10049,6 @@ mod hir_opt_tests { v25:CallableMethodEntry[VALUE(0x1048)] = GuardBitEquals v24, Value(VALUE(0x1048)) v26:RubyValue = LoadField v23, :VM_ENV_DATA_INDEX_SPECVAL@0x1050 v27:FalseClass = GuardBitEquals v26, Value(false) - v38:CPtr = GetEP 0 - v39:RubyValue = LoadField v38, :VM_ENV_DATA_INDEX_ME_CREF@0x1040 - v40:CallableMethodEntry[VALUE(0x1048)] = GuardBitEquals v39, Value(VALUE(0x1048)) - v41:RubyValue = LoadField v38, :VM_ENV_DATA_INDEX_SPECVAL@0x1050 - v42:FalseClass = GuardBitEquals v41, Value(false) v28:Array = GuardType v9, Array v29:Fixnum = GuardType v10, Fixnum v30:CInt64 = UnboxFixnum v29 @@ -16604,4 +16594,49 @@ mod hir_opt_tests { Return v27 "); } + + #[test] + fn test_elide_test_of_box_bool() { + eval(r#" + def test(a, b) + if a == b + 3 + else + 4 + end + end + test(:a, :b) + "#); + assert_snapshot!(hir_string("test"), @" + fn test@<compiled>:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :a@0x1000 + v4:BasicObject = LoadField v2, :b@0x1001 + Jump bb3(v1, v3, v4) + bb2(): + EntryPoint JIT(0) + v7:BasicObject = LoadArg :self@0 + v8:BasicObject = LoadArg :a@1 + v9:BasicObject = LoadArg :b@2 + Jump bb3(v7, v8, v9) + bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1008, ==@0x1010, cme:0x1018) + v48:StaticSymbol = GuardType v12, StaticSymbol + v49:CBool = IsBitEqual v48, v13 + v50:BoolExact = BoxBool v49 + CheckInterrupts + CondBranch v49, bb5(), bb4(v11, v48, v13) + bb5(): + v29:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v29 + bb4(v34:BasicObject, v35:StaticSymbol, v36:BasicObject): + v40:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v40 + "); + } } diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 41f96a7a82..2eb5ca1932 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -134,10 +134,9 @@ nil_exact = final_type "NilClass", c_name: "rb_cNilClass" true_exact = final_type "TrueClass", c_name: "rb_cTrueClass" false_exact = final_type "FalseClass", c_name: "rb_cFalseClass" -# Typed T_DATA objects (RTYPEDDATA_P). These have a distinct memory layout -# for field access (fields_obj at a fixed offset in RTypedData). These -# don't have a common class ancestor below BasicObject. -basic_object.subtype "TypedTData" +# T_DATA objects have a distinct memory layout for field access and don't have a +# common class ancestor below BasicObject. +basic_object.subtype "TData" # Build the cvalue object universe. This is for C-level types that may be # passed around when calling into the Ruby VM or after some strength reduction diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index f4c3bf3489..1dae344b36 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -4,7 +4,7 @@ mod bits { pub const Array: u64 = ArrayExact | ArraySubclass; pub const ArrayExact: u64 = 1u64 << 0; pub const ArraySubclass: u64 = 1u64 << 1; - pub const BasicObject: u64 = BasicObjectExact | BasicObjectSubclass | Object | TypedTData; + pub const BasicObject: u64 = BasicObjectExact | BasicObjectSubclass | Object | TData; pub const BasicObjectExact: u64 = 1u64 << 2; pub const BasicObjectSubclass: u64 = 1u64 << 3; pub const Bignum: u64 = 1u64 << 4; @@ -76,7 +76,7 @@ mod bits { pub const Symbol: u64 = DynamicSymbol | StaticSymbol; pub const TrueClass: u64 = 1u64 << 45; pub const Truthy: u64 = BasicObject & !Falsy; - pub const TypedTData: u64 = 1u64 << 46; + pub const TData: u64 = 1u64 << 46; pub const Undef: u64 = 1u64 << 47; pub const AllBitPatterns: [(&str, u64); 78] = [ ("Any", Any), @@ -87,7 +87,7 @@ mod bits { ("NotNil", NotNil), ("Truthy", Truthy), ("HeapBasicObject", HeapBasicObject), - ("TypedTData", TypedTData), + ("TData", TData), ("Object", Object), ("BuiltinExact", BuiltinExact), ("BoolExact", BoolExact), @@ -238,7 +238,7 @@ pub mod types { pub const Symbol: Type = Type::from_bits(bits::Symbol); pub const TrueClass: Type = Type::from_bits(bits::TrueClass); pub const Truthy: Type = Type::from_bits(bits::Truthy); - pub const TypedTData: Type = Type::from_bits(bits::TypedTData); + pub const TData: Type = Type::from_bits(bits::TData); pub const Undef: Type = Type::from_bits(bits::Undef); pub const ExactBitsAndClass: [(u64, *const VALUE); 17] = [ (bits::ObjectExact, &raw const crate::cruby::rb_cObject), diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index cdfc15a935..6aa10dea55 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -214,7 +214,7 @@ impl Type { else if val.class_of() == unsafe { rb_cSymbol } { bits::DynamicSymbol } else if let Some(bits) = Self::bits_from_exact_class(val.class_of()) { bits } else if let Some(bits) = Self::bits_from_subclass(val.class_of()) { bits } - else if val.typed_data_p() { bits::TypedTData } + else if val.data_p() { bits::TData } else { unreachable!("Class {} is not a subclass of BasicObject! Don't know what to do.", get_class_name(val.class_of())) @@ -445,7 +445,7 @@ impl Type { } else if self.bit_equal(types::Hash) { Some(cruby::RUBY_T_HASH) } else { - // Note that types::TypedTData is narrower than T_DATA, so not here. + // T_DATA uses a specialized guard, so not here. None } } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 08803c4570..000c424da4 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -219,8 +219,8 @@ impl Flags { const IS_T_CLASS: u32 = 1 << 6; /// Object is a T_MODULE const IS_T_MODULE: u32 = 1 << 7; - /// Object is a typed T_DATA (RTYPEDDATA_P) - const IS_TYPED_DATA: u32 = 1 << 8; + /// Object is a T_DATA + const IS_T_DATA: u32 = 1 << 8; pub fn none() -> Self { Self(Self::NONE) } @@ -233,7 +233,7 @@ impl Flags { pub fn is_fields_embedded(self) -> bool { (self.0 & Self::IS_FIELDS_EMBEDDED) != 0 } pub fn is_t_class(self) -> bool { (self.0 & Self::IS_T_CLASS) != 0 } pub fn is_t_module(self) -> bool { (self.0 & Self::IS_T_MODULE) != 0 } - pub fn is_typed_data(self) -> bool { (self.0 & Self::IS_TYPED_DATA) != 0 } + pub fn is_t_data(self) -> bool { (self.0 & Self::IS_T_DATA) != 0 } } /// opt_send_without_block/opt_plus/... should store: @@ -322,9 +322,9 @@ impl ProfiledType { flags.0 |= Flags::IS_FIELDS_EMBEDDED; } } - if obj.typed_data_p() { - flags.0 |= Flags::IS_TYPED_DATA; - if obj.typed_data_fields_embedded_p() { + if obj.data_p() { + flags.0 |= Flags::IS_T_DATA; + if obj.data_fields_embedded_p() { flags.0 |= Flags::IS_FIELDS_EMBEDDED; } } @@ -365,8 +365,8 @@ impl ProfiledType { (RUBY_T_CLASS, RUBY_T_MASK) } else if self.flags().is_t_module() { (RUBY_T_MODULE, RUBY_T_MASK) - } else if self.flags().is_typed_data() { - (RUBY_T_DATA | RUBY_TYPED_FL_IS_TYPED_DATA, RUBY_T_MASK | RUBY_TYPED_FL_IS_TYPED_DATA) + } else if self.flags().is_t_data() { + (RUBY_T_DATA, RUBY_T_MASK) } else { (0, 0) }; |
