mirror of
https://github.com/ruby/ruby.git
synced 2026-01-26 20:19:19 +00:00
ZJIT: Optimize setivar with shape transition (#15375)
Since we do a decent job of pre-sizing objects, don't handle the case where we would need to re-size an object. Also don't handle too-complex shapes.
lobsters stats before:
```
Top-20 calls to C functions from JIT code (79.4% of total 90,051,140):
rb_vm_opt_send_without_block: 19,762,433 (21.9%)
rb_vm_setinstancevariable: 7,698,314 ( 8.5%)
rb_hash_aref: 6,767,461 ( 7.5%)
rb_vm_env_write: 5,373,080 ( 6.0%)
rb_vm_send: 5,049,229 ( 5.6%)
rb_vm_getinstancevariable: 4,535,259 ( 5.0%)
rb_obj_is_kind_of: 3,746,306 ( 4.2%)
rb_ivar_get_at_no_ractor_check: 3,745,237 ( 4.2%)
rb_vm_invokesuper: 3,037,467 ( 3.4%)
rb_ary_entry: 2,351,983 ( 2.6%)
rb_vm_opt_getconstant_path: 1,344,740 ( 1.5%)
rb_vm_invokeblock: 1,184,474 ( 1.3%)
Hash#[]=: 1,064,288 ( 1.2%)
rb_gc_writebarrier: 1,006,972 ( 1.1%)
rb_ec_ary_new_from_values: 902,687 ( 1.0%)
fetch: 898,667 ( 1.0%)
rb_str_buf_append: 833,787 ( 0.9%)
rb_class_allocate_instance: 822,024 ( 0.9%)
Hash#fetch: 699,580 ( 0.8%)
_bi20: 682,068 ( 0.8%)
Top-4 setivar fallback reasons (100.0% of total 7,732,326):
shape_transition: 6,032,109 (78.0%)
not_monomorphic: 1,469,300 (19.0%)
not_t_object: 172,636 ( 2.2%)
too_complex: 58,281 ( 0.8%)
```
lobsters stats after:
```
Top-20 calls to C functions from JIT code (79.0% of total 88,322,656):
rb_vm_opt_send_without_block: 19,777,880 (22.4%)
rb_hash_aref: 6,771,589 ( 7.7%)
rb_vm_env_write: 5,372,789 ( 6.1%)
rb_gc_writebarrier: 5,195,527 ( 5.9%)
rb_vm_send: 5,049,145 ( 5.7%)
rb_vm_getinstancevariable: 4,538,485 ( 5.1%)
rb_obj_is_kind_of: 3,746,241 ( 4.2%)
rb_ivar_get_at_no_ractor_check: 3,745,172 ( 4.2%)
rb_vm_invokesuper: 3,037,157 ( 3.4%)
rb_ary_entry: 2,351,968 ( 2.7%)
rb_vm_setinstancevariable: 1,703,337 ( 1.9%)
rb_vm_opt_getconstant_path: 1,344,730 ( 1.5%)
rb_vm_invokeblock: 1,184,290 ( 1.3%)
Hash#[]=: 1,061,868 ( 1.2%)
rb_ec_ary_new_from_values: 902,666 ( 1.0%)
fetch: 898,666 ( 1.0%)
rb_str_buf_append: 833,784 ( 0.9%)
rb_class_allocate_instance: 821,778 ( 0.9%)
Hash#fetch: 755,913 ( 0.9%)
Top-4 setivar fallback reasons (100.0% of total 1,703,337):
not_monomorphic: 1,472,405 (86.4%)
not_t_object: 172,629 (10.1%)
too_complex: 58,281 ( 3.4%)
new_shape_needs_extension: 22 ( 0.0%)
```
I also noticed that primitive printing in HIR was broken so I fixed that.
Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
This commit is contained in:
parent
3efd8c6764
commit
0af85a1fe2
Notes:
git
2025-12-04 02:28:24 +00:00
Merged-By: tekknolagi <donotemailthisaddress@bernsteinbear.com>
6
jit.c
6
jit.c
@ -767,3 +767,9 @@ rb_yarv_str_eql_internal(VALUE str1, VALUE str2)
|
||||
}
|
||||
|
||||
void rb_jit_str_concat_codepoint(VALUE str, VALUE codepoint);
|
||||
|
||||
attr_index_t
|
||||
rb_jit_shape_capacity(shape_id_t shape_id)
|
||||
{
|
||||
return RSHAPE_CAPACITY(shape_id);
|
||||
}
|
||||
|
||||
@ -100,6 +100,7 @@ fn main() {
|
||||
.allowlist_function("rb_shape_id_offset")
|
||||
.allowlist_function("rb_shape_get_iv_index")
|
||||
.allowlist_function("rb_shape_transition_add_ivar_no_warnings")
|
||||
.allowlist_function("rb_jit_shape_capacity")
|
||||
.allowlist_var("rb_invalid_shape_id")
|
||||
.allowlist_type("shape_id_fl_type")
|
||||
.allowlist_var("VM_KW_SPECIFIED_BITS_MAX")
|
||||
|
||||
@ -350,6 +350,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
|
||||
&Insn::Const { val: Const::CPtr(val) } => gen_const_cptr(val),
|
||||
&Insn::Const { val: Const::CInt64(val) } => gen_const_long(val),
|
||||
&Insn::Const { val: Const::CUInt16(val) } => gen_const_uint16(val),
|
||||
&Insn::Const { val: Const::CUInt32(val) } => gen_const_uint32(val),
|
||||
Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"),
|
||||
Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)),
|
||||
Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
|
||||
@ -475,7 +476,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
|
||||
Insn::LoadEC => gen_load_ec(),
|
||||
Insn::LoadSelf => gen_load_self(),
|
||||
&Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset),
|
||||
&Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val))),
|
||||
&Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val), function.type_of(val))),
|
||||
&Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))),
|
||||
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
|
||||
Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)),
|
||||
@ -1099,10 +1100,10 @@ fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32) -> Opnd
|
||||
asm.load(Opnd::mem(64, recv, offset))
|
||||
}
|
||||
|
||||
fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd) {
|
||||
fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd, val_type: Type) {
|
||||
asm_comment!(asm, "Store field id={} offset={}", id.contents_lossy(), offset);
|
||||
let recv = asm.load(recv);
|
||||
asm.store(Opnd::mem(64, recv, offset), val);
|
||||
asm.store(Opnd::mem(val_type.num_bits(), recv, offset), val);
|
||||
}
|
||||
|
||||
fn gen_write_barrier(asm: &mut Assembler, recv: Opnd, val: Opnd, val_type: Type) {
|
||||
@ -1159,6 +1160,10 @@ fn gen_const_uint16(val: u16) -> lir::Opnd {
|
||||
Opnd::UImm(val as u64)
|
||||
}
|
||||
|
||||
fn gen_const_uint32(val: u32) -> lir::Opnd {
|
||||
Opnd::UImm(val as u64)
|
||||
}
|
||||
|
||||
/// Compile a basic block argument
|
||||
fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd {
|
||||
// Allocate a register or a stack slot
|
||||
|
||||
@ -999,8 +999,9 @@ mod manual_defs {
|
||||
use super::*;
|
||||
|
||||
pub const SIZEOF_VALUE: usize = 8;
|
||||
pub const BITS_PER_BYTE: usize = 8;
|
||||
pub const SIZEOF_VALUE_I32: i32 = SIZEOF_VALUE as i32;
|
||||
pub const VALUE_BITS: u8 = 8 * SIZEOF_VALUE as u8;
|
||||
pub const VALUE_BITS: u8 = BITS_PER_BYTE as u8 * SIZEOF_VALUE as u8;
|
||||
|
||||
pub const RUBY_LONG_MIN: isize = std::os::raw::c_long::MIN as isize;
|
||||
pub const RUBY_LONG_MAX: isize = std::os::raw::c_long::MAX as isize;
|
||||
@ -1382,6 +1383,7 @@ pub(crate) mod ids {
|
||||
name: thread_ptr
|
||||
name: self_ content: b"self"
|
||||
name: rb_ivar_get_at_no_ractor_check
|
||||
name: _shape_id
|
||||
}
|
||||
|
||||
/// Get an CRuby `ID` to an interned string, e.g. a particular method name.
|
||||
|
||||
1
zjit/src/cruby_bindings.inc.rs
generated
1
zjit/src/cruby_bindings.inc.rs
generated
@ -2232,4 +2232,5 @@ unsafe extern "C" {
|
||||
);
|
||||
pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE;
|
||||
pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE);
|
||||
pub fn rb_jit_shape_capacity(shape_id: shape_id_t) -> attr_index_t;
|
||||
}
|
||||
|
||||
@ -2949,10 +2949,34 @@ impl Function {
|
||||
self.push_insn_id(block, insn_id); continue;
|
||||
}
|
||||
let mut ivar_index: u16 = 0;
|
||||
let mut next_shape_id = recv_type.shape();
|
||||
if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } {
|
||||
// TODO(max): Shape transition
|
||||
self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_shape_transition));
|
||||
self.push_insn_id(block, insn_id); continue;
|
||||
// Current shape does not contain this ivar; do a shape transition.
|
||||
let current_shape_id = recv_type.shape();
|
||||
let class = recv_type.class();
|
||||
// We're only looking at T_OBJECT so ignore all of the imemo stuff.
|
||||
assert!(recv_type.flags().is_t_object());
|
||||
next_shape_id = ShapeId(unsafe { rb_shape_transition_add_ivar_no_warnings(class, current_shape_id.0, id) });
|
||||
let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) };
|
||||
assert!(ivar_result, "New shape must have the ivar index");
|
||||
// If the VM ran out of shapes, or this class generated too many leaf,
|
||||
// it may be de-optimized into OBJ_TOO_COMPLEX_SHAPE (hash-table).
|
||||
let new_shape_too_complex = unsafe { rb_jit_shape_too_complex_p(next_shape_id.0) };
|
||||
// TODO(max): Is it OK to bail out here after making a shape transition?
|
||||
if new_shape_too_complex {
|
||||
self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_new_shape_too_complex));
|
||||
self.push_insn_id(block, insn_id); continue;
|
||||
}
|
||||
let current_capacity = unsafe { rb_jit_shape_capacity(current_shape_id.0) };
|
||||
let next_capacity = unsafe { rb_jit_shape_capacity(next_shape_id.0) };
|
||||
// If the new shape has a different capacity, or is TOO_COMPLEX, we'll have to
|
||||
// reallocate it.
|
||||
let needs_extension = next_capacity != current_capacity;
|
||||
if needs_extension {
|
||||
self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_new_shape_needs_extension));
|
||||
self.push_insn_id(block, insn_id); continue;
|
||||
}
|
||||
// Fall through to emitting the ivar write
|
||||
}
|
||||
let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state });
|
||||
let self_val = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state });
|
||||
@ -2968,6 +2992,13 @@ impl Function {
|
||||
};
|
||||
self.push_insn(block, Insn::StoreField { recv: ivar_storage, id, offset, val });
|
||||
self.push_insn(block, Insn::WriteBarrier { recv: self_val, val });
|
||||
if next_shape_id != recv_type.shape() {
|
||||
// Write the new shape ID
|
||||
assert_eq!(SHAPE_ID_NUM_BITS, 32);
|
||||
let shape_id = self.push_insn(block, Insn::Const { val: Const::CUInt32(next_shape_id.0) });
|
||||
let shape_id_offset = unsafe { rb_shape_id_offset() };
|
||||
self.push_insn(block, Insn::StoreField { recv: self_val, id: ID!(_shape_id), offset: shape_id_offset, val: shape_id });
|
||||
}
|
||||
}
|
||||
_ => { self.push_insn_id(block, insn_id); }
|
||||
}
|
||||
|
||||
@ -3484,6 +3484,39 @@ mod hir_opt_tests {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dont_specialize_complex_shape_definedivar() {
|
||||
eval(r#"
|
||||
class C
|
||||
def test = defined?(@a)
|
||||
end
|
||||
obj = C.new
|
||||
(0..1000).each do |i|
|
||||
obj.instance_variable_set(:"@v#{i}", i)
|
||||
end
|
||||
(0..1000).each do |i|
|
||||
obj.remove_instance_variable(:"@v#{i}")
|
||||
end
|
||||
obj.test
|
||||
TEST = C.instance_method(:test)
|
||||
"#);
|
||||
assert_snapshot!(hir_string_proc("TEST"), @r"
|
||||
fn test@<compiled>:3:
|
||||
bb0():
|
||||
EntryPoint interpreter
|
||||
v1:BasicObject = LoadSelf
|
||||
Jump bb2(v1)
|
||||
bb1(v4:BasicObject):
|
||||
EntryPoint JIT(0)
|
||||
Jump bb2(v4)
|
||||
bb2(v6:BasicObject):
|
||||
IncrCounter definedivar_fallback_too_complex
|
||||
v10:StringExact|NilClass = DefinedIvar v6, :@a
|
||||
CheckInterrupts
|
||||
Return v10
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_specialize_monomorphic_setivar_already_in_shape() {
|
||||
eval("
|
||||
@ -3513,7 +3546,7 @@ mod hir_opt_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dont_specialize_monomorphic_setivar_with_shape_transition() {
|
||||
fn test_specialize_monomorphic_setivar_with_shape_transition() {
|
||||
eval("
|
||||
def test = @foo = 5
|
||||
test
|
||||
@ -3530,13 +3563,57 @@ mod hir_opt_tests {
|
||||
bb2(v6:BasicObject):
|
||||
v10:Fixnum[5] = Const Value(5)
|
||||
PatchPoint SingleRactorMode
|
||||
IncrCounter setivar_fallback_shape_transition
|
||||
SetIvar v6, :@foo, v10
|
||||
v19:HeapBasicObject = GuardType v6, HeapBasicObject
|
||||
v20:HeapBasicObject = GuardShape v19, 0x1000
|
||||
StoreField v20, :@foo@0x1001, v10
|
||||
WriteBarrier v20, v10
|
||||
v23:CUInt32[4194311] = Const CUInt32(4194311)
|
||||
StoreField v20, :_shape_id@0x1002, v23
|
||||
CheckInterrupts
|
||||
Return v10
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_specialize_multiple_monomorphic_setivar_with_shape_transition() {
|
||||
eval("
|
||||
def test
|
||||
@foo = 1
|
||||
@bar = 2
|
||||
end
|
||||
test
|
||||
");
|
||||
assert_snapshot!(hir_string("test"), @r"
|
||||
fn test@<compiled>:3:
|
||||
bb0():
|
||||
EntryPoint interpreter
|
||||
v1:BasicObject = LoadSelf
|
||||
Jump bb2(v1)
|
||||
bb1(v4:BasicObject):
|
||||
EntryPoint JIT(0)
|
||||
Jump bb2(v4)
|
||||
bb2(v6:BasicObject):
|
||||
v10:Fixnum[1] = Const Value(1)
|
||||
PatchPoint SingleRactorMode
|
||||
v25:HeapBasicObject = GuardType v6, HeapBasicObject
|
||||
v26:HeapBasicObject = GuardShape v25, 0x1000
|
||||
StoreField v26, :@foo@0x1001, v10
|
||||
WriteBarrier v26, v10
|
||||
v29:CUInt32[4194311] = Const CUInt32(4194311)
|
||||
StoreField v26, :_shape_id@0x1002, v29
|
||||
v16:Fixnum[2] = Const Value(2)
|
||||
PatchPoint SingleRactorMode
|
||||
v31:HeapBasicObject = GuardType v6, HeapBasicObject
|
||||
v32:HeapBasicObject = GuardShape v31, 0x1003
|
||||
StoreField v32, :@bar@0x1004, v16
|
||||
WriteBarrier v32, v16
|
||||
v35:CUInt32[4194312] = Const CUInt32(4194312)
|
||||
StoreField v32, :_shape_id@0x1002, v35
|
||||
CheckInterrupts
|
||||
Return v16
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dont_specialize_setivar_with_t_data() {
|
||||
eval("
|
||||
@ -3611,6 +3688,9 @@ mod hir_opt_tests {
|
||||
(0..1000).each do |i|
|
||||
obj.instance_variable_set(:"@v#{i}", i)
|
||||
end
|
||||
(0..1000).each do |i|
|
||||
obj.remove_instance_variable(:"@v#{i}")
|
||||
end
|
||||
obj.test
|
||||
TEST = C.instance_method(:test)
|
||||
"#);
|
||||
@ -3626,7 +3706,7 @@ mod hir_opt_tests {
|
||||
bb2(v6:BasicObject):
|
||||
v10:Fixnum[5] = Const Value(5)
|
||||
PatchPoint SingleRactorMode
|
||||
IncrCounter setivar_fallback_shape_transition
|
||||
IncrCounter setivar_fallback_too_complex
|
||||
SetIvar v6, :@a, v10
|
||||
CheckInterrupts
|
||||
Return v10
|
||||
@ -5417,6 +5497,43 @@ mod hir_opt_tests {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dont_optimize_getivar_with_too_complex_shape() {
|
||||
eval(r#"
|
||||
class C
|
||||
attr_accessor :foo
|
||||
end
|
||||
obj = C.new
|
||||
(0..1000).each do |i|
|
||||
obj.instance_variable_set(:"@v#{i}", i)
|
||||
end
|
||||
(0..1000).each do |i|
|
||||
obj.remove_instance_variable(:"@v#{i}")
|
||||
end
|
||||
def test(o) = o.foo
|
||||
test obj
|
||||
"#);
|
||||
assert_snapshot!(hir_string("test"), @r"
|
||||
fn test@<compiled>:12:
|
||||
bb0():
|
||||
EntryPoint interpreter
|
||||
v1:BasicObject = LoadSelf
|
||||
v2:BasicObject = GetLocal l0, SP@4
|
||||
Jump bb2(v1, v2)
|
||||
bb1(v5:BasicObject, v6:BasicObject):
|
||||
EntryPoint JIT(0)
|
||||
Jump bb2(v5, v6)
|
||||
bb2(v8:BasicObject, v9:BasicObject):
|
||||
PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010)
|
||||
PatchPoint NoSingletonClass(C@0x1000)
|
||||
v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
|
||||
IncrCounter getivar_fallback_too_complex
|
||||
v22:BasicObject = GetIvar v21, :@foo
|
||||
CheckInterrupts
|
||||
Return v22
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optimize_send_with_block() {
|
||||
eval(r#"
|
||||
@ -5697,8 +5814,11 @@ mod hir_opt_tests {
|
||||
v16:Fixnum[5] = Const Value(5)
|
||||
PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010)
|
||||
v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
|
||||
IncrCounter setivar_fallback_shape_transition
|
||||
SetIvar v26, :@foo, v16
|
||||
v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038
|
||||
StoreField v29, :@foo@0x1039, v16
|
||||
WriteBarrier v29, v16
|
||||
v32:CUInt32[4194311] = Const CUInt32(4194311)
|
||||
StoreField v29, :_shape_id@0x103a, v32
|
||||
CheckInterrupts
|
||||
Return v16
|
||||
");
|
||||
@ -5729,8 +5849,11 @@ mod hir_opt_tests {
|
||||
v16:Fixnum[5] = Const Value(5)
|
||||
PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010)
|
||||
v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
|
||||
IncrCounter setivar_fallback_shape_transition
|
||||
SetIvar v26, :@foo, v16
|
||||
v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038
|
||||
StoreField v29, :@foo@0x1039, v16
|
||||
WriteBarrier v29, v16
|
||||
v32:CUInt32[4194311] = Const CUInt32(4194311)
|
||||
StoreField v29, :_shape_id@0x103a, v32
|
||||
CheckInterrupts
|
||||
Return v16
|
||||
");
|
||||
@ -9109,18 +9232,22 @@ mod hir_opt_tests {
|
||||
SetLocal l0, EP@3, v27
|
||||
v39:BasicObject = GetLocal l0, EP@3
|
||||
PatchPoint SingleRactorMode
|
||||
IncrCounter setivar_fallback_shape_transition
|
||||
SetIvar v14, :@formatted, v39
|
||||
v45:Class[VMFrozenCore] = Const Value(VALUE(0x1000))
|
||||
PatchPoint MethodRedefined(Class@0x1008, lambda@0x1010, cme:0x1018)
|
||||
PatchPoint NoSingletonClass(Class@0x1008)
|
||||
v60:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1040, block=0x1048
|
||||
v56:HeapBasicObject = GuardType v14, HeapBasicObject
|
||||
v57:HeapBasicObject = GuardShape v56, 0x1000
|
||||
StoreField v57, :@formatted@0x1001, v39
|
||||
WriteBarrier v57, v39
|
||||
v60:CUInt32[4194311] = Const CUInt32(4194311)
|
||||
StoreField v57, :_shape_id@0x1002, v60
|
||||
v45:Class[VMFrozenCore] = Const Value(VALUE(0x1008))
|
||||
PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020)
|
||||
PatchPoint NoSingletonClass(Class@0x1010)
|
||||
v65:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050
|
||||
v48:BasicObject = GetLocal l0, EP@6
|
||||
v49:BasicObject = GetLocal l0, EP@5
|
||||
v50:BasicObject = GetLocal l0, EP@4
|
||||
v51:BasicObject = GetLocal l0, EP@3
|
||||
CheckInterrupts
|
||||
Return v60
|
||||
Return v65
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,13 +89,13 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R
|
||||
Specialization::TypeExact(val) =>
|
||||
write!(f, "[class_exact:{}]", get_class_name(val)),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CBool) => write!(f, "[{}]", val != 0),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CInt8) => write!(f, "[{}]", (val as i64) >> 56),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CInt16) => write!(f, "[{}]", (val as i64) >> 48),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CInt32) => write!(f, "[{}]", (val as i64) >> 32),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CInt8) => write!(f, "[{}]", (val & u8::MAX as u64) as i8),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CInt16) => write!(f, "[{}]", (val & u16::MAX as u64) as i16),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CInt32) => write!(f, "[{}]", (val & u32::MAX as u64) as i32),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CInt64) => write!(f, "[{}]", val as i64),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CUInt8) => write!(f, "[{}]", val >> 56),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CUInt16) => write!(f, "[{}]", val >> 48),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CUInt32) => write!(f, "[{}]", val >> 32),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CUInt8) => write!(f, "[{}]", val & u8::MAX as u64),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CUInt16) => write!(f, "[{}]", val & u16::MAX as u64),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CUInt32) => write!(f, "[{}]", val & u32::MAX as u64),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CUInt64) => write!(f, "[{}]", val),
|
||||
Specialization::Int(val) if ty.is_subtype(types::CPtr) => write!(f, "[{}]", Const::CPtr(val as *const u8).print(printer.ptr_map)),
|
||||
Specialization::Int(val) => write!(f, "[{val}]"),
|
||||
@ -517,6 +517,18 @@ impl Type {
|
||||
pub fn print(self, ptr_map: &PtrPrintMap) -> TypePrinter<'_> {
|
||||
TypePrinter { inner: self, ptr_map }
|
||||
}
|
||||
|
||||
pub fn num_bits(&self) -> u8 {
|
||||
self.num_bytes() * crate::cruby::BITS_PER_BYTE as u8
|
||||
}
|
||||
|
||||
pub fn num_bytes(&self) -> u8 {
|
||||
if self.is_subtype(types::CUInt8) || self.is_subtype(types::CInt8) { return 1; }
|
||||
if self.is_subtype(types::CUInt16) || self.is_subtype(types::CInt16) { return 2; }
|
||||
if self.is_subtype(types::CUInt32) || self.is_subtype(types::CInt32) { return 4; }
|
||||
// CUInt64, CInt64, CPtr, CNull, CDouble, or anything else defaults to 8 bytes
|
||||
crate::cruby::SIZEOF_VALUE as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -574,6 +586,33 @@ mod tests {
|
||||
assert_subtype(types::Any, types::Any);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_const() {
|
||||
let cint32 = Type::from_const(Const::CInt32(12));
|
||||
assert_subtype(cint32, types::CInt32);
|
||||
assert_eq!(cint32.spec, Specialization::Int(12));
|
||||
assert_eq!(format!("{}", cint32), "CInt32[12]");
|
||||
|
||||
let cint32 = Type::from_const(Const::CInt32(-12));
|
||||
assert_subtype(cint32, types::CInt32);
|
||||
assert_eq!(cint32.spec, Specialization::Int((-12i64) as u64));
|
||||
assert_eq!(format!("{}", cint32), "CInt32[-12]");
|
||||
|
||||
let cuint32 = Type::from_const(Const::CInt32(12));
|
||||
assert_subtype(cuint32, types::CInt32);
|
||||
assert_eq!(cuint32.spec, Specialization::Int(12));
|
||||
|
||||
let cuint32 = Type::from_const(Const::CUInt32(0xffffffff));
|
||||
assert_subtype(cuint32, types::CUInt32);
|
||||
assert_eq!(cuint32.spec, Specialization::Int(0xffffffff));
|
||||
assert_eq!(format!("{}", cuint32), "CUInt32[4294967295]");
|
||||
|
||||
let cuint32 = Type::from_const(Const::CUInt32(0xc00087));
|
||||
assert_subtype(cuint32, types::CUInt32);
|
||||
assert_eq!(cuint32.spec, Specialization::Int(0xc00087));
|
||||
assert_eq!(format!("{}", cuint32), "CUInt32[12583047]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer() {
|
||||
assert_subtype(Type::fixnum(123), types::Fixnum);
|
||||
|
||||
@ -255,6 +255,8 @@ make_counters! {
|
||||
setivar_fallback_too_complex,
|
||||
setivar_fallback_frozen,
|
||||
setivar_fallback_shape_transition,
|
||||
setivar_fallback_new_shape_too_complex,
|
||||
setivar_fallback_new_shape_needs_extension,
|
||||
}
|
||||
|
||||
// Ivar fallback counters that are summed as dynamic_getivar_count
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user