Add compact Type lattice
This will be used for local type inference and potentially SCCP.
This commit is contained in:
parent
0a543daf15
commit
ec41dffd05
Notes:
git
2025-04-18 13:48:27 +00:00
@ -116,6 +116,7 @@ fn main() {
|
||||
// From ruby/internal/intern/object.h
|
||||
.allowlist_function("rb_obj_is_kind_of")
|
||||
.allowlist_function("rb_obj_frozen_p")
|
||||
.allowlist_function("rb_class_inherited_p")
|
||||
|
||||
// From ruby/internal/encoding/encoding.h
|
||||
.allowlist_type("ruby_encoding_consts")
|
||||
@ -156,6 +157,7 @@ fn main() {
|
||||
// From include/ruby/internal/intern/class.h
|
||||
.allowlist_function("rb_class_attached_object")
|
||||
.allowlist_function("rb_singleton_class")
|
||||
.allowlist_function("rb_define_class")
|
||||
|
||||
// From include/ruby/internal/core/rclass.h
|
||||
.allowlist_function("rb_class_get_superclass")
|
||||
@ -169,6 +171,7 @@ fn main() {
|
||||
// VALUE variables for Ruby class objects
|
||||
// From include/ruby/internal/globals.h
|
||||
.allowlist_var("rb_cBasicObject")
|
||||
.allowlist_var("rb_cObject")
|
||||
.allowlist_var("rb_cModule")
|
||||
.allowlist_var("rb_cNilClass")
|
||||
.allowlist_var("rb_cTrueClass")
|
||||
|
@ -2144,7 +2144,7 @@ impl Assembler {
|
||||
macro_rules! asm_comment {
|
||||
($asm:expr, $($fmt:tt)*) => {
|
||||
if $crate::options::get_option!(dump_disasm) {
|
||||
$asm.push_insn(Insn::Comment(format!($($fmt)*)));
|
||||
$asm.push_insn(crate::backend::lir::Insn::Comment(format!($($fmt)*)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
asm::CodeBlock,
|
||||
backend::lir::*,
|
||||
backend::lir::{EC, CFP, SP, C_ARG_OPNDS, Assembler, Opnd, asm_comment},
|
||||
cruby::*,
|
||||
debug,
|
||||
hir::{Function, Insn::*, InsnId},
|
||||
hir::{Function, InsnId, Insn, Const},
|
||||
virtualmem::CodePtr
|
||||
};
|
||||
#[cfg(feature = "disasm")]
|
||||
@ -37,13 +37,13 @@ pub fn gen_function(cb: &mut CodeBlock, function: &Function, iseq: IseqPtr) -> O
|
||||
// Compile each instruction in the IR
|
||||
for (insn_idx, insn) in function.insns.iter().enumerate() {
|
||||
let insn_id = InsnId(insn_idx);
|
||||
if !matches!(*insn, Snapshot { .. }) {
|
||||
if !matches!(*insn, Insn::Snapshot { .. }) {
|
||||
asm_comment!(asm, "Insn: {:04} {:?}", insn_idx, insn);
|
||||
}
|
||||
match *insn {
|
||||
Const { val } => gen_const(&mut jit, insn_id, val),
|
||||
Return { val } => gen_return(&jit, &mut asm, val)?,
|
||||
Snapshot { .. } => {}, // we don't need to do anything for this instruction at the moment
|
||||
Insn::Const { val: Const::Value(val) } => gen_const(&mut jit, insn_id, val),
|
||||
Insn::Return { val } => gen_return(&jit, &mut asm, val)?,
|
||||
Insn::Snapshot { .. } => {}, // we don't need to do anything for this instruction at the moment
|
||||
_ => {
|
||||
debug!("ZJIT: gen_function: unexpected insn {:?}", insn);
|
||||
return None;
|
||||
|
@ -328,6 +328,13 @@ pub struct rb_cref_t {
|
||||
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum ClassRelationship {
|
||||
Subclass,
|
||||
Superclass,
|
||||
NoRelation,
|
||||
}
|
||||
|
||||
impl VALUE {
|
||||
/// Dump info about the value to the console similarly to rp(VALUE)
|
||||
pub fn dump_info(self) {
|
||||
@ -432,6 +439,22 @@ impl VALUE {
|
||||
unsafe { rb_yarv_class_of(self) }
|
||||
}
|
||||
|
||||
/// Check if `self` is a subclass of `other`. Assumes both `self` and `other` are class
|
||||
/// objects. Returns [`ClassRelationship::Subclass`] if `self <= other`,
|
||||
/// [`ClassRelationship::Superclass`] if `other < self`, and [`ClassRelationship::NoRelation`]
|
||||
/// otherwise.
|
||||
pub fn is_subclass_of(self, other: VALUE) -> ClassRelationship {
|
||||
assert!(unsafe { RB_TYPE_P(self, RUBY_T_CLASS) });
|
||||
assert!(unsafe { RB_TYPE_P(other, RUBY_T_CLASS) });
|
||||
match unsafe { rb_class_inherited_p(self, other) } {
|
||||
Qtrue => ClassRelationship::Subclass,
|
||||
Qfalse => ClassRelationship::Superclass,
|
||||
Qnil => ClassRelationship::NoRelation,
|
||||
// The API specifies that it will return Qnil in this case
|
||||
_ => panic!("Unexpected return value from rb_class_inherited_p"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_frozen(self) -> bool {
|
||||
unsafe { rb_obj_frozen_p(self) != VALUE(0) }
|
||||
}
|
||||
@ -544,7 +567,7 @@ impl VALUE {
|
||||
ptr
|
||||
}
|
||||
|
||||
pub fn fixnum_from_usize(item: usize) -> Self {
|
||||
pub const fn fixnum_from_usize(item: usize) -> Self {
|
||||
assert!(item <= (RUBY_FIXNUM_MAX as usize)); // An unsigned will always be greater than RUBY_FIXNUM_MIN
|
||||
let k: usize = item.wrapping_add(item.wrapping_add(1));
|
||||
VALUE(k)
|
||||
@ -871,6 +894,11 @@ pub mod test_utils {
|
||||
rb_iseqw_to_iseq(wrapped_iseq)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn define_class(name: &str, superclass: VALUE) -> VALUE {
|
||||
let name = CString::new(name).unwrap();
|
||||
unsafe { rb_define_class(name.as_ptr(), superclass) }
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub use test_utils::*;
|
||||
|
@ -747,6 +747,7 @@ unsafe extern "C" {
|
||||
pub fn rb_gc_location(obj: VALUE) -> VALUE;
|
||||
pub fn rb_gc_writebarrier(old: VALUE, young: VALUE);
|
||||
pub fn rb_class_get_superclass(klass: VALUE) -> VALUE;
|
||||
pub static mut rb_cObject: VALUE;
|
||||
pub fn rb_funcallv(
|
||||
recv: VALUE,
|
||||
mid: ID,
|
||||
@ -771,6 +772,7 @@ unsafe extern "C" {
|
||||
pub static mut rb_cTrueClass: VALUE;
|
||||
pub fn ruby_init();
|
||||
pub fn ruby_init_stack(addr: *mut ::std::os::raw::c_void);
|
||||
pub fn rb_define_class(name: *const ::std::os::raw::c_char, super_: VALUE) -> VALUE;
|
||||
pub fn rb_obj_class(obj: VALUE) -> VALUE;
|
||||
pub fn rb_ary_new_capa(capa: ::std::os::raw::c_long) -> VALUE;
|
||||
pub fn rb_ary_store(ary: VALUE, key: ::std::os::raw::c_long, val: VALUE);
|
||||
@ -797,6 +799,7 @@ unsafe extern "C" {
|
||||
pub fn rb_class2name(klass: VALUE) -> *const ::std::os::raw::c_char;
|
||||
pub fn rb_obj_is_kind_of(obj: VALUE, klass: VALUE) -> VALUE;
|
||||
pub fn rb_obj_frozen_p(obj: VALUE) -> VALUE;
|
||||
pub fn rb_class_inherited_p(scion: VALUE, ascendant: VALUE) -> VALUE;
|
||||
pub fn rb_backref_get() -> VALUE;
|
||||
pub fn rb_range_new(beg: VALUE, end: VALUE, excl: ::std::os::raw::c_int) -> VALUE;
|
||||
pub fn rb_reg_nth_match(n: ::std::os::raw::c_int, md: VALUE) -> VALUE;
|
||||
|
138
zjit/src/gen_hir_type.rb
Normal file
138
zjit/src/gen_hir_type.rb
Normal file
@ -0,0 +1,138 @@
|
||||
# Generate src/hir_type_generated.rs. To do this, we build up a DAG that
|
||||
# represents a slice of the Ruby type hierarchy that we care about optimizing.
|
||||
# This also includes primitive values such as C booleans, int32, and so on.
|
||||
|
||||
require 'set'
|
||||
|
||||
# Type represents not just a Ruby class but a named union of other types.
|
||||
class Type
|
||||
attr_accessor :name, :subtypes
|
||||
|
||||
def initialize name, subtypes=nil
|
||||
@name = name
|
||||
@subtypes = subtypes || []
|
||||
end
|
||||
|
||||
def all_subtypes
|
||||
subtypes + subtypes.flat_map { |subtype| subtype.all_subtypes }
|
||||
end
|
||||
|
||||
def subtype name
|
||||
result = Type.new name
|
||||
@subtypes << result
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
# Helper to generate graphviz.
|
||||
def to_graphviz_rec type
|
||||
type.subtypes.each {|subtype|
|
||||
puts type.name + "->" + subtype.name + ";"
|
||||
}
|
||||
type.subtypes.each {|subtype|
|
||||
to_graphviz_rec subtype
|
||||
}
|
||||
end
|
||||
|
||||
# Generate graphviz.
|
||||
def to_graphviz type
|
||||
puts "digraph G {"
|
||||
to_graphviz_rec type
|
||||
puts "}"
|
||||
end
|
||||
|
||||
# ===== Start generating the type DAG =====
|
||||
|
||||
# Start at Top. All types are subtypes of Top.
|
||||
top = Type.new "Top"
|
||||
# Build the Ruby object universe.
|
||||
object = top.subtype "Object"
|
||||
object.subtype "ObjectExact"
|
||||
$object_user = object.subtype "ObjectUser"
|
||||
$user = top.subtype "User"
|
||||
$builtin_exact = object.subtype "BuiltinExact"
|
||||
|
||||
# Define a new type that can be subclassed (most of them).
|
||||
def base_type name
|
||||
type = $object_user.subtype name
|
||||
exact = type.subtype(name+"Exact")
|
||||
user = type.subtype(name+"User")
|
||||
$builtin_exact.subtypes << exact
|
||||
$user.subtypes << user
|
||||
[type, exact]
|
||||
end
|
||||
|
||||
base_type "String"
|
||||
base_type "Array"
|
||||
base_type "Hash"
|
||||
|
||||
(integer, integer_exact) = base_type "Integer"
|
||||
# CRuby partitions Integer into immediate and non-immediate variants.
|
||||
integer_exact.subtype "Fixnum"
|
||||
integer_exact.subtype "Bignum"
|
||||
|
||||
(float, float_exact) = base_type "Float"
|
||||
# CRuby partitions Float into immediate and non-immediate variants.
|
||||
float_exact.subtype "Flonum"
|
||||
float_exact.subtype "HeapFloat"
|
||||
|
||||
(symbol, symbol_exact) = base_type "Symbol"
|
||||
# CRuby partitions Symbol into immediate and non-immediate variants.
|
||||
symbol_exact.subtype "StaticSymbol"
|
||||
symbol_exact.subtype "DynamicSymbol"
|
||||
|
||||
base_type "NilClass"
|
||||
base_type "TrueClass"
|
||||
base_type "FalseClass"
|
||||
|
||||
# Build the primitive object universe.
|
||||
primitive = top.subtype "Primitive"
|
||||
primitive.subtype "CBool"
|
||||
primitive.subtype "CPtr"
|
||||
primitive.subtype "CDouble"
|
||||
primitive.subtype "CNull"
|
||||
primitive_int = primitive.subtype "CInt"
|
||||
signed = primitive_int.subtype "CSigned"
|
||||
unsigned = primitive_int.subtype "CUnsigned"
|
||||
[8, 16, 32, 64].each {|width|
|
||||
signed.subtype "CInt#{width}"
|
||||
unsigned.subtype "CUInt#{width}"
|
||||
}
|
||||
|
||||
# Assign individual bits to type leaves and union bit patterns to nodes with subtypes
|
||||
num_bits = 0
|
||||
bits = {"Bottom" => ["0u64"]}
|
||||
Set[top, *top.all_subtypes].sort_by(&:name).each {|type|
|
||||
subtypes = type.subtypes
|
||||
if subtypes.empty?
|
||||
# Assign bits for leaves
|
||||
bits[type.name] = ["1u64 << #{num_bits}"]
|
||||
num_bits += 1
|
||||
else
|
||||
# Assign bits for unions
|
||||
bits[type.name] = subtypes.map(&:name).sort
|
||||
end
|
||||
}
|
||||
|
||||
# ===== Finished generating the DAG; write Rust code =====
|
||||
|
||||
puts "// This file is @generated by src/gen_hir_type.rb."
|
||||
puts "mod bits {"
|
||||
bits.keys.sort.map {|type_name|
|
||||
subtypes = bits[type_name].join(" | ")
|
||||
puts " pub const #{type_name}: u64 = #{subtypes};"
|
||||
}
|
||||
puts " pub const AllBitPatterns: [(&'static str, u64); #{bits.size}] = ["
|
||||
bits.keys.sort.map {|type_name|
|
||||
puts " (\"#{type_name}\", #{type_name}),"
|
||||
}
|
||||
puts " ];"
|
||||
puts " pub const NumTypeBits: u64 = #{num_bits};
|
||||
}"
|
||||
|
||||
puts "pub mod types {
|
||||
use super::*;"
|
||||
bits.keys.sort.map {|type_name|
|
||||
puts " pub const #{type_name}: Type = Type::from_bits(bits::#{type_name});"
|
||||
}
|
||||
puts "}"
|
@ -45,7 +45,7 @@ fn write_vec<T: std::fmt::Display>(f: &mut std::fmt::Formatter, objs: &Vec<T>) -
|
||||
impl std::fmt::Display for VALUE {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
val if val.fixnum_p() => write!(f, "Fixnum({})", val.as_fixnum()),
|
||||
val if val.fixnum_p() => write!(f, "{}", val.as_fixnum()),
|
||||
&Qnil => write!(f, "nil"),
|
||||
&Qtrue => write!(f, "true"),
|
||||
&Qfalse => write!(f, "false"),
|
||||
@ -89,11 +89,34 @@ pub enum Invariant {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Const {
|
||||
Value(VALUE),
|
||||
CInt8(i8),
|
||||
CInt16(i16),
|
||||
CInt32(i32),
|
||||
CInt64(i64),
|
||||
CUInt8(u8),
|
||||
CUInt16(u16),
|
||||
CUInt32(u32),
|
||||
CUInt64(u64),
|
||||
CPtr(*mut u8),
|
||||
CDouble(f64),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Const {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Const::Value(val) => write!(f, "Value({val})"),
|
||||
_ => write!(f, "{self:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Insn {
|
||||
PutSelf,
|
||||
// TODO(max): We probably want to make this an enum so we are not limited to Ruby heap objects
|
||||
Const { val: VALUE },
|
||||
Const { val: Const },
|
||||
// SSA block parameter. Also used for function parameters in the function's entry block.
|
||||
Param { idx: usize },
|
||||
|
||||
@ -530,7 +553,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
|
||||
if idx < num_lead_params(iseq) {
|
||||
entry_state.locals.push(fun.push_insn(fun.entry_block, Insn::Param { idx }));
|
||||
} else {
|
||||
entry_state.locals.push(fun.push_insn(fun.entry_block, Insn::Const { val: Qnil }));
|
||||
entry_state.locals.push(fun.push_insn(fun.entry_block, Insn::Const { val: Const::Value(Qnil) }));
|
||||
}
|
||||
}
|
||||
queue.push_back((entry_state, fun.entry_block, /*insn_idx=*/0 as u32));
|
||||
@ -570,11 +593,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
|
||||
|
||||
match opcode {
|
||||
YARVINSN_nop => {},
|
||||
YARVINSN_putnil => { state.push(fun.push_insn(block, Insn::Const { val: Qnil })); },
|
||||
YARVINSN_putobject => { state.push(fun.push_insn(block, Insn::Const { val: get_arg(pc, 0) })); },
|
||||
YARVINSN_putnil => { state.push(fun.push_insn(block, Insn::Const { val: Const::Value(Qnil) })); },
|
||||
YARVINSN_putobject => { state.push(fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) })); },
|
||||
YARVINSN_putstring | YARVINSN_putchilledstring => {
|
||||
// TODO(max): Do something different for chilled string
|
||||
let val = fun.push_insn(block, Insn::Const { val: get_arg(pc, 0) });
|
||||
let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
|
||||
let insn_id = fun.push_insn(block, Insn::StringCopy { val });
|
||||
state.push(insn_id);
|
||||
}
|
||||
@ -593,15 +616,15 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
|
||||
state.push(insn_id);
|
||||
}
|
||||
YARVINSN_duparray => {
|
||||
let val = fun.push_insn(block, Insn::Const { val: get_arg(pc, 0) });
|
||||
let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
|
||||
let insn_id = fun.push_insn(block, Insn::ArrayDup { val });
|
||||
state.push(insn_id);
|
||||
}
|
||||
YARVINSN_putobject_INT2FIX_0_ => {
|
||||
state.push(fun.push_insn(block, Insn::Const { val: VALUE::fixnum_from_usize(0) }));
|
||||
state.push(fun.push_insn(block, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(0)) }));
|
||||
}
|
||||
YARVINSN_putobject_INT2FIX_1_ => {
|
||||
state.push(fun.push_insn(block, Insn::Const { val: VALUE::fixnum_from_usize(1) }));
|
||||
state.push(fun.push_insn(block, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(1)) }));
|
||||
}
|
||||
YARVINSN_defined => {
|
||||
let op_type = get_arg(pc, 0).as_usize();
|
||||
@ -829,13 +852,33 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_matches_value(insn: Option<&Insn>, val: VALUE) {
|
||||
match insn {
|
||||
Some(Insn::Const { val: Const::Value(spec) }) => {
|
||||
assert_eq!(*spec, val);
|
||||
}
|
||||
_ => assert!(false, "Expected Const {val}, found {insn:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_matches_const(insn: Option<&Insn>, expected: Const) {
|
||||
match insn {
|
||||
Some(Insn::Const { val }) => {
|
||||
assert_eq!(*val, expected, "{val:?} does not match {expected:?}");
|
||||
}
|
||||
_ => assert!(false, "Expected Const {expected:?}, found {insn:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_putobject() {
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let program = "123";
|
||||
let iseq = compile_to_iseq(program);
|
||||
let function = iseq_to_hir(iseq).unwrap();
|
||||
assert_matches!(function.insns.get(1), Some(Insn::Const { val: VALUE(247) }));
|
||||
assert_matches_value(function.insns.get(1), VALUE::fixnum_from_usize(123));
|
||||
assert_matches!(function.insns.get(3), Some(Insn::Return { val: InsnId(1) }));
|
||||
});
|
||||
}
|
||||
@ -848,8 +891,8 @@ mod tests {
|
||||
let function = iseq_to_hir(iseq).unwrap();
|
||||
// TODO(max): Figure out a clean way to match against String
|
||||
// TODO(max): Figure out a clean way to match against args vec
|
||||
assert_matches!(function.insns.get(1), Some(Insn::Const { val: VALUE(3) }));
|
||||
assert_matches!(function.insns.get(3), Some(Insn::Const { val: VALUE(5) }));
|
||||
assert_matches_value(function.insns.get(1), VALUE::fixnum_from_usize(1));
|
||||
assert_matches_value(function.insns.get(3), VALUE::fixnum_from_usize(2));
|
||||
assert_matches!(function.insns.get(5), Some(Insn::Send { self_val: InsnId(1), .. }));
|
||||
});
|
||||
}
|
||||
@ -860,7 +903,7 @@ mod tests {
|
||||
let program = "a = 1; a";
|
||||
let iseq = compile_to_iseq(program);
|
||||
let function = iseq_to_hir(iseq).unwrap();
|
||||
assert_matches!(function.insns.get(2), Some(Insn::Const { val: VALUE(3) }));
|
||||
assert_matches_value(function.insns.get(2), VALUE::fixnum_from_usize(1));
|
||||
assert_matches!(function.insns.get(6), Some(Insn::Return { val: InsnId(2) }));
|
||||
});
|
||||
}
|
||||
@ -871,12 +914,12 @@ mod tests {
|
||||
let program = "cond = true; if cond; 3; else; 4; end";
|
||||
let iseq = compile_to_iseq(program);
|
||||
let function = iseq_to_hir(iseq).unwrap();
|
||||
assert_matches!(function.insns.get(2), Some(Insn::Const { val: Qtrue }));
|
||||
assert_matches_const(function.insns.get(2), Const::Value(Qtrue));
|
||||
assert_matches!(function.insns.get(6), Some(Insn::Test { val: InsnId(2) }));
|
||||
assert_matches!(function.insns.get(7), Some(Insn::IfFalse { val: InsnId(6), target: BranchEdge { target: BlockId(1), .. } }));
|
||||
assert_matches!(function.insns.get(9), Some(Insn::Const { val: VALUE(7) }));
|
||||
assert_matches_const(function.insns.get(9), Const::Value(VALUE::fixnum_from_usize(3)));
|
||||
assert_matches!(function.insns.get(11), Some(Insn::Return { val: InsnId(9) }));
|
||||
assert_matches!(function.insns.get(14), Some(Insn::Const { val: VALUE(9) }));
|
||||
assert_matches_const(function.insns.get(14), Const::Value(VALUE::fixnum_from_usize(4)));
|
||||
assert_matches!(function.insns.get(16), Some(Insn::Return { val: InsnId(14) }));
|
||||
});
|
||||
}
|
||||
|
571
zjit/src/hir_type.rs
Normal file
571
zjit/src/hir_type.rs
Normal file
@ -0,0 +1,571 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE};
|
||||
use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass};
|
||||
use crate::cruby::ClassRelationship;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
/// Specialization of the type. If we know additional information about the object, we put it here.
|
||||
/// This includes information about its value as a primitive. For Ruby objects, type specialization
|
||||
/// is split into three sub-cases:
|
||||
///
|
||||
/// * Object, where we know exactly what object (pointer) the Type corresponds to
|
||||
/// * Type exact, where we know exactly what class the Type represents (which could be because we
|
||||
/// have an instance of it; includes Object specialization)
|
||||
/// * Type, where we know that the Type could represent the given class or any of its subclasses
|
||||
///
|
||||
/// It is also a lattice but a much shallower one. It is not meant to be used directly, just by
|
||||
/// Type internals.
|
||||
pub enum Specialization {
|
||||
/// We know nothing about the specialization of this Type.
|
||||
Top,
|
||||
/// We know that this Type is an instance of the given Ruby class in the VALUE or any of its subclasses.
|
||||
Type(VALUE),
|
||||
/// We know that this Type is an instance of exactly the Ruby class in the VALUE.
|
||||
TypeExact(VALUE),
|
||||
/// We know that this Type is exactly the Ruby object in the VALUE.
|
||||
Object(VALUE),
|
||||
/// We know that this Type is exactly the given primitive/C integer value (use the type bits to
|
||||
/// inform how we should interpret the u64, e.g. as CBool or CInt32).
|
||||
Int(u64),
|
||||
/// We know that this Type is exactly the given primitive/C double.
|
||||
Double(f64),
|
||||
/// We know that the Type is [`types::Bottom`] and therefore the instruction that produces this
|
||||
/// value never returns.
|
||||
Bottom,
|
||||
}
|
||||
|
||||
// NOTE: Type very intentionally does not support Eq or PartialEq; we almost never want to check
|
||||
// bit equality of types in the compiler but instead check subtyping, intersection, union, etc.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
/// The main work horse of intraprocedural type inference and specialization. The main interfaces
|
||||
/// will look like:
|
||||
///
|
||||
/// * is type A a subset of type B
|
||||
/// * union/meet type A and type B
|
||||
///
|
||||
/// Most questions can be rewritten in terms of these operations.
|
||||
pub struct Type {
|
||||
/// A bitset representing type information about the object. Specific bits are assigned for
|
||||
/// leaf types (for example, static symbols) and union-ing bitsets together represents
|
||||
/// union-ing sets of types. These sets form a lattice (with Top as "could be anything" and
|
||||
/// Bottom as "can be nothing").
|
||||
///
|
||||
/// Capable of also representing primitive types (bool, i32, etc).
|
||||
///
|
||||
/// This field should not be directly read or written except by internal `Type` APIs.
|
||||
bits: u64,
|
||||
/// Specialization of the type. See [`Specialization`].
|
||||
///
|
||||
/// This field should not be directly read or written except by internal `Type` APIs.
|
||||
spec: Specialization
|
||||
}
|
||||
|
||||
// TODO(max): Figure out how to silence the non-upper-case globals warning
|
||||
include!("hir_type_generated.rs");
|
||||
|
||||
/// Get class name from a class pointer.
|
||||
fn get_class_name(class: Option<VALUE>) -> String {
|
||||
use crate::{RB_TYPE_P, RUBY_T_MODULE, RUBY_T_CLASS};
|
||||
use crate::{cstr_to_rust_string, rb_class2name};
|
||||
class.filter(|&class| {
|
||||
// type checks for rb_class2name()
|
||||
unsafe { RB_TYPE_P(class, RUBY_T_MODULE) || RB_TYPE_P(class, RUBY_T_CLASS) }
|
||||
}).and_then(|class| unsafe {
|
||||
cstr_to_rust_string(rb_class2name(class))
|
||||
}).unwrap_or_else(|| "Unknown".to_string())
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let mut bit_patterns = Vec::from_iter(bits::AllBitPatterns);
|
||||
bit_patterns.sort_by(|(_, left), (_, right)| left.partial_cmp(right).unwrap());
|
||||
for (name, pattern) in bit_patterns {
|
||||
if self.bits == pattern {
|
||||
write!(f, "{name}")?;
|
||||
return match self.spec {
|
||||
Specialization::Top | Specialization::Bottom => { Ok(()) },
|
||||
Specialization::Object(val) => write!(f, "[{val}]"),
|
||||
Specialization::Type(val) => write!(f, "[class:{}]", get_class_name(Some(val))),
|
||||
Specialization::TypeExact(val) => write!(f, "[class_exact:{}]", get_class_name(Some(val))),
|
||||
Specialization::Int(val) if self.is_subtype(types::CBool) => write!(f, "[{}]", val != 0),
|
||||
Specialization::Int(val) if self.is_subtype(types::CInt8) => write!(f, "[{}]", (val as i64) >> 56),
|
||||
Specialization::Int(val) if self.is_subtype(types::CInt16) => write!(f, "[{}]", (val as i64) >> 48),
|
||||
Specialization::Int(val) if self.is_subtype(types::CInt32) => write!(f, "[{}]", (val as i64) >> 32),
|
||||
Specialization::Int(val) if self.is_subtype(types::CInt64) => write!(f, "[{}]", val as i64),
|
||||
Specialization::Int(val) if self.is_subtype(types::CUInt8) => write!(f, "[{}]", val >> 56),
|
||||
Specialization::Int(val) if self.is_subtype(types::CUInt16) => write!(f, "[{}]", val >> 48),
|
||||
Specialization::Int(val) if self.is_subtype(types::CUInt32) => write!(f, "[{}]", val >> 32),
|
||||
Specialization::Int(val) if self.is_subtype(types::CUInt64) => write!(f, "[{}]", val),
|
||||
Specialization::Int(val) => write!(f, "[{val}]"),
|
||||
Specialization::Double(val) => write!(f, "[{val}]"),
|
||||
}
|
||||
};
|
||||
}
|
||||
// TODO(max): Add a prettier string representation
|
||||
write!(f, "{:x?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Type {
|
||||
/// Create a `Type` from the given integer.
|
||||
pub const fn fixnum(val: i64) -> Type {
|
||||
Type {
|
||||
bits: bits::Fixnum,
|
||||
spec: Specialization::Object(VALUE::fixnum_from_usize(val as usize)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Type` from a Ruby `VALUE`. The type is not guaranteed to have object
|
||||
/// specialization in its `specialization` field (for example, `Qnil` will just be
|
||||
/// `types::NilClassExact`), but will be available via `ruby_object()`.
|
||||
pub fn from_value(val: VALUE) -> Type {
|
||||
if val.fixnum_p() {
|
||||
Type { bits: bits::Fixnum, spec: Specialization::Object(val) }
|
||||
}
|
||||
else if val.flonum_p() {
|
||||
Type { bits: bits::Flonum, spec: Specialization::Object(val) }
|
||||
}
|
||||
else if val.static_sym_p() {
|
||||
Type { bits: bits::StaticSymbol, spec: Specialization::Object(val) }
|
||||
}
|
||||
// Singleton objects; don't specialize
|
||||
else if val == Qnil { types::NilClassExact }
|
||||
else if val == Qtrue { types::TrueClassExact }
|
||||
else if val == Qfalse { types::FalseClassExact }
|
||||
else if val.class_of() == unsafe { rb_cInteger } {
|
||||
Type { bits: bits::Bignum, spec: Specialization::Object(val) }
|
||||
}
|
||||
else if val.class_of() == unsafe { rb_cFloat } {
|
||||
Type { bits: bits::HeapFloat, spec: Specialization::Object(val) }
|
||||
}
|
||||
else if val.class_of() == unsafe { rb_cSymbol } {
|
||||
Type { bits: bits::DynamicSymbol, spec: Specialization::Object(val) }
|
||||
}
|
||||
else if val.class_of() == unsafe { rb_cArray } {
|
||||
Type { bits: bits::ArrayExact, spec: Specialization::Object(val) }
|
||||
}
|
||||
else if val.class_of() == unsafe { rb_cHash } {
|
||||
Type { bits: bits::HashExact, spec: Specialization::Object(val) }
|
||||
}
|
||||
else if val.class_of() == unsafe { rb_cString } {
|
||||
Type { bits: bits::StringExact, spec: Specialization::Object(val) }
|
||||
}
|
||||
else if val.class_of() == unsafe { rb_cObject } {
|
||||
Type { bits: bits::ObjectExact, spec: Specialization::Object(val) }
|
||||
}
|
||||
else {
|
||||
// TODO(max): Add more cases for inferring type bits from built-in types
|
||||
Type { bits: bits::Object, spec: Specialization::Object(val) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Private. Only for creating type globals.
|
||||
const fn from_bits(bits: u64) -> Type {
|
||||
Type {
|
||||
bits,
|
||||
spec: if bits == bits::Bottom {
|
||||
Specialization::Bottom
|
||||
} else {
|
||||
Specialization::Top
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Type` from a primitive integer. Use the `ty` given to specify what size the
|
||||
/// `specialization` represents. For example, `Type::from_cint(types::CBool, 1)` or
|
||||
/// `Type::from_cint(types::CUInt16, 12)`.
|
||||
pub fn from_cint(ty: Type, val: i64) -> Type {
|
||||
assert_eq!(ty.spec, Specialization::Top);
|
||||
assert!((ty.is_subtype(types::CUnsigned) || ty.is_subtype(types::CSigned)) &&
|
||||
ty.bits != types::CUnsigned.bits && ty.bits != types::CSigned.bits,
|
||||
"ty must be a specific int size");
|
||||
Type { bits: ty.bits, spec: Specialization::Int(val as u64) }
|
||||
}
|
||||
|
||||
/// Create a `Type` from a primitive boolean.
|
||||
pub fn from_cbool(val: bool) -> Type {
|
||||
Type { bits: bits::CBool, spec: Specialization::Int(val as u64) }
|
||||
}
|
||||
|
||||
/// Return the object specialization, if any.
|
||||
pub fn ruby_object(&self) -> Option<VALUE> {
|
||||
match self.spec {
|
||||
Specialization::Object(val) => Some(val),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if the Type has object specialization and false otherwise.
|
||||
pub fn ruby_object_known(&self) -> bool {
|
||||
matches!(self.spec, Specialization::Object(_))
|
||||
}
|
||||
|
||||
fn is_builtin(class: VALUE) -> bool {
|
||||
if class == unsafe { rb_cArray } { return true; }
|
||||
if class == unsafe { rb_cFalseClass } { return true; }
|
||||
if class == unsafe { rb_cFloat } { return true; }
|
||||
if class == unsafe { rb_cHash } { return true; }
|
||||
if class == unsafe { rb_cInteger } { return true; }
|
||||
if class == unsafe { rb_cNilClass } { return true; }
|
||||
if class == unsafe { rb_cObject } { return true; }
|
||||
if class == unsafe { rb_cString } { return true; }
|
||||
if class == unsafe { rb_cSymbol } { return true; }
|
||||
if class == unsafe { rb_cTrueClass } { return true; }
|
||||
false
|
||||
}
|
||||
|
||||
pub fn union(&self, other: Type) -> Type {
|
||||
// Easy cases first
|
||||
if self.is_subtype(other) { return other; }
|
||||
if other.is_subtype(*self) { return *self; }
|
||||
let bits = self.bits | other.bits;
|
||||
let result = Type::from_bits(bits);
|
||||
// If one type isn't type specialized, we can't return a specialized Type
|
||||
if !self.type_known() || !other.type_known() { return result; }
|
||||
let self_class = self.inexact_ruby_class().unwrap();
|
||||
let other_class = other.inexact_ruby_class().unwrap();
|
||||
// Pick one of self/other as the least upper bound. This is not the most specific (there
|
||||
// could be intermediate classes in the inheritance hierarchy) but it is fast to compute.
|
||||
let super_class = match self_class.is_subclass_of(other_class) {
|
||||
ClassRelationship::Subclass => other_class,
|
||||
ClassRelationship::Superclass => self_class,
|
||||
ClassRelationship::NoRelation => return result,
|
||||
};
|
||||
// Don't specialize built-in types; we can represent them perfectly with type bits.
|
||||
if Type::is_builtin(super_class) { return result; }
|
||||
// Supertype specialization can be exact only if the exact type specializations are identical
|
||||
if let Some(self_class) = self.exact_ruby_class() {
|
||||
if let Some(other_class) = other.exact_ruby_class() {
|
||||
if self_class == other_class {
|
||||
return Type { bits, spec: Specialization::TypeExact(self_class) };
|
||||
}
|
||||
}
|
||||
}
|
||||
Type { bits, spec: Specialization::Type(super_class) }
|
||||
}
|
||||
|
||||
/// Check if the type field of `self` is a subtype of the type field of `other` and also check
|
||||
/// if the specialization of `self` is a subtype of the specialization of `other`.
|
||||
pub fn is_subtype(&self, other: Type) -> bool {
|
||||
(self.bits & other.bits) == self.bits && self.spec_is_subtype_of(other)
|
||||
}
|
||||
|
||||
/// Return the type specialization, if any. Type specialization asks if we know the Ruby type
|
||||
/// (including potentially its subclasses) corresponding to a `Type`, including knowing exactly
|
||||
/// what object is is.
|
||||
pub fn type_known(&self) -> bool {
|
||||
matches!(self.spec, Specialization::TypeExact(_) | Specialization::Type(_) | Specialization::Object(_))
|
||||
}
|
||||
|
||||
/// Return the exact type specialization, if any. Type specialization asks if we know the
|
||||
/// *exact* Ruby type corresponding to a `Type`, including knowing exactly what object is is.
|
||||
pub fn exact_class_known(&self) -> bool {
|
||||
matches!(self.spec, Specialization::TypeExact(_) | Specialization::Object(_))
|
||||
}
|
||||
|
||||
/// Return the exact type specialization, if any. Type specialization asks if we know the exact
|
||||
/// Ruby type corresponding to a `Type` (no subclasses), including knowing exactly what object
|
||||
/// it is.
|
||||
pub fn exact_ruby_class(&self) -> Option<VALUE> {
|
||||
match self.spec {
|
||||
// If we're looking at a precise object, we can pull out its class.
|
||||
Specialization::Object(val) => Some(val.class_of()),
|
||||
Specialization::TypeExact(val) => Some(val),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the type specialization, if any. Type specialization asks if we know the inexact
|
||||
/// Ruby type corresponding to a `Type`, including knowing exactly what object is is.
|
||||
pub fn inexact_ruby_class(&self) -> Option<VALUE> {
|
||||
match self.spec {
|
||||
// If we're looking at a precise object, we can pull out its class.
|
||||
Specialization::Object(val) => Some(val.class_of()),
|
||||
Specialization::TypeExact(val) | Specialization::Type(val) => Some(val),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check *only* if `self`'s specialization is a subtype of `other`'s specialization. Private.
|
||||
/// You probably want [`Type::is_subtype`] instead.
|
||||
fn spec_is_subtype_of(&self, other: Type) -> bool {
|
||||
match (self.spec, other.spec) {
|
||||
// Bottom is a subtype of everything; Top is a supertype of everything
|
||||
(Specialization::Bottom, _) | (_, Specialization::Top) => true,
|
||||
// Other is not Top from the previous case, so Top is definitely not a subtype
|
||||
(Specialization::Top, _) | (_, Specialization::Bottom) => false,
|
||||
// Int and double specialization requires exact equality
|
||||
(Specialization::Int(_), _) | (_, Specialization::Int(_)) |
|
||||
(Specialization::Double(_), _) | (_, Specialization::Double(_)) =>
|
||||
self.bits == other.bits && self.spec == other.spec,
|
||||
// Check other's specialization type in decreasing order of specificity
|
||||
(_, Specialization::Object(_)) =>
|
||||
self.ruby_object_known() && self.ruby_object() == other.ruby_object(),
|
||||
(_, Specialization::TypeExact(_)) =>
|
||||
self.exact_class_known() && self.inexact_ruby_class() == other.inexact_ruby_class(),
|
||||
(_, Specialization::Type(other_class)) =>
|
||||
self.inexact_ruby_class().unwrap().is_subclass_of(other_class) == ClassRelationship::Subclass,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::rust_str_to_ruby;
|
||||
use crate::rust_str_to_sym;
|
||||
use crate::rb_ary_new_capa;
|
||||
use crate::rb_hash_new;
|
||||
use crate::rb_float_new;
|
||||
use crate::cruby::define_class;
|
||||
|
||||
#[track_caller]
|
||||
fn assert_bit_equal(left: Type, right: Type) {
|
||||
assert_eq!(left.bits, right.bits, "{left} bits are not equal to {right} bits");
|
||||
assert_eq!(left.spec, right.spec, "{left} spec is not equal to {right} spec");
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_subtype(left: Type, right: Type) {
|
||||
assert!(left.is_subtype(right), "{left} is not a subtype of {right}");
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_not_subtype(left: Type, right: Type) {
|
||||
assert!(!left.is_subtype(right), "{left} is a subtype of {right}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bottom_is_subtype_of_everything() {
|
||||
// Spot check a few cases
|
||||
assert_subtype(types::Bottom, types::NilClassExact);
|
||||
assert_subtype(types::Bottom, types::Array);
|
||||
assert_subtype(types::Bottom, types::Object);
|
||||
assert_subtype(types::Bottom, types::CUInt16);
|
||||
assert_subtype(types::Bottom, Type::from_cint(types::CInt32, 10));
|
||||
assert_subtype(types::Bottom, types::Top);
|
||||
assert_subtype(types::Bottom, types::Bottom);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything_is_a_subtype_of_top() {
|
||||
// Spot check a few cases
|
||||
assert_subtype(types::NilClassExact, types::Top);
|
||||
assert_subtype(types::Array, types::Top);
|
||||
assert_subtype(types::Object, types::Top);
|
||||
assert_subtype(types::CUInt16, types::Top);
|
||||
assert_subtype(Type::from_cint(types::CInt32, 10), types::Top);
|
||||
assert_subtype(types::Bottom, types::Top);
|
||||
assert_subtype(types::Top, types::Top);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer() {
|
||||
assert_subtype(Type::fixnum(123), types::Fixnum);
|
||||
assert_subtype(Type::fixnum(123), Type::fixnum(123));
|
||||
assert_not_subtype(Type::fixnum(123), Type::fixnum(200));
|
||||
assert_subtype(Type::from_value(VALUE::fixnum_from_usize(123)), types::Fixnum);
|
||||
assert_subtype(types::Fixnum, types::IntegerExact);
|
||||
assert_subtype(types::Bignum, types::IntegerExact);
|
||||
assert_subtype(types::IntegerExact, types::Integer);
|
||||
assert_subtype(types::IntegerUser, types::Integer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float() {
|
||||
assert_subtype(types::Flonum, types::FloatExact);
|
||||
assert_subtype(types::HeapFloat, types::FloatExact);
|
||||
assert_subtype(types::FloatExact, types::Float);
|
||||
assert_subtype(types::FloatUser, types::Float);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol() {
|
||||
assert_subtype(types::StaticSymbol, types::SymbolExact);
|
||||
assert_subtype(types::DynamicSymbol, types::SymbolExact);
|
||||
assert_subtype(types::SymbolExact, types::Symbol);
|
||||
assert_subtype(types::SymbolUser, types::Symbol);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixnum_has_ruby_object() {
|
||||
assert_eq!(Type::fixnum(3).ruby_object(), Some(VALUE::fixnum_from_usize(3)));
|
||||
assert_eq!(types::Fixnum.ruby_object(), None);
|
||||
assert_eq!(types::IntegerExact.ruby_object(), None);
|
||||
assert_eq!(types::Integer.ruby_object(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn singletons_do_not_have_ruby_object() {
|
||||
assert_eq!(Type::from_value(Qnil).ruby_object(), None);
|
||||
assert_eq!(types::NilClassExact.ruby_object(), None);
|
||||
assert_eq!(Type::from_value(Qtrue).ruby_object(), None);
|
||||
assert_eq!(types::TrueClassExact.ruby_object(), None);
|
||||
assert_eq!(Type::from_value(Qfalse).ruby_object(), None);
|
||||
assert_eq!(types::FalseClassExact.ruby_object(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer_has_exact_ruby_class() {
|
||||
assert_eq!(Type::fixnum(3).exact_ruby_class(), Some(unsafe { rb_cInteger }.into()));
|
||||
assert_eq!(types::Fixnum.exact_ruby_class(), None);
|
||||
assert_eq!(types::IntegerExact.exact_ruby_class(), None);
|
||||
assert_eq!(types::Integer.exact_ruby_class(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn singletons_do_not_have_exact_ruby_class() {
|
||||
assert_eq!(Type::from_value(Qnil).exact_ruby_class(), None);
|
||||
assert_eq!(types::NilClassExact.exact_ruby_class(), None);
|
||||
assert_eq!(Type::from_value(Qtrue).exact_ruby_class(), None);
|
||||
assert_eq!(types::TrueClassExact.exact_ruby_class(), None);
|
||||
assert_eq!(Type::from_value(Qfalse).exact_ruby_class(), None);
|
||||
assert_eq!(types::FalseClassExact.exact_ruby_class(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn singletons_do_not_have_ruby_class() {
|
||||
assert_eq!(Type::from_value(Qnil).inexact_ruby_class(), None);
|
||||
assert_eq!(types::NilClassExact.inexact_ruby_class(), None);
|
||||
assert_eq!(Type::from_value(Qtrue).inexact_ruby_class(), None);
|
||||
assert_eq!(types::TrueClassExact.inexact_ruby_class(), None);
|
||||
assert_eq!(Type::from_value(Qfalse).inexact_ruby_class(), None);
|
||||
assert_eq!(types::FalseClassExact.inexact_ruby_class(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer_has_ruby_class() {
|
||||
assert_eq!(Type::fixnum(3).inexact_ruby_class(), Some(unsafe { rb_cInteger }.into()));
|
||||
assert_eq!(types::Fixnum.inexact_ruby_class(), None);
|
||||
assert_eq!(types::IntegerExact.inexact_ruby_class(), None);
|
||||
assert_eq!(types::Integer.inexact_ruby_class(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_exact_bits_match() {
|
||||
assert_eq!(format!("{}", Type::fixnum(4)), "Fixnum[4]");
|
||||
assert_eq!(format!("{}", Type::from_cint(types::CInt8, -1)), "CInt8[-1]");
|
||||
assert_eq!(format!("{}", Type::from_cint(types::CUInt8, -1)), "CUInt8[255]");
|
||||
assert_eq!(format!("{}", Type::from_cint(types::CInt16, -1)), "CInt16[-1]");
|
||||
assert_eq!(format!("{}", Type::from_cint(types::CUInt16, -1)), "CUInt16[65535]");
|
||||
assert_eq!(format!("{}", Type::from_cint(types::CInt32, -1)), "CInt32[-1]");
|
||||
assert_eq!(format!("{}", Type::from_cint(types::CUInt32, -1)), "CUInt32[4294967295]");
|
||||
assert_eq!(format!("{}", Type::from_cint(types::CInt64, -1)), "CInt64[-1]");
|
||||
assert_eq!(format!("{}", Type::from_cint(types::CUInt64, -1)), "CUInt64[18446744073709551615]");
|
||||
assert_eq!(format!("{}", Type::from_cbool(true)), "CBool[true]");
|
||||
assert_eq!(format!("{}", Type::from_cbool(false)), "CBool[false]");
|
||||
assert_eq!(format!("{}", types::Fixnum), "Fixnum");
|
||||
assert_eq!(format!("{}", types::Integer), "Integer");
|
||||
assert_eq!(format!("{}", types::IntegerExact), "IntegerExact");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_equal() {
|
||||
assert_bit_equal(types::Fixnum.union(types::Fixnum), types::Fixnum);
|
||||
assert_bit_equal(Type::fixnum(3).union(Type::fixnum(3)), Type::fixnum(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_bits_subtype() {
|
||||
assert_bit_equal(types::Fixnum.union(types::IntegerExact), types::IntegerExact);
|
||||
assert_bit_equal(types::Fixnum.union(types::Integer), types::Integer);
|
||||
assert_bit_equal(types::Fixnum.union(types::Object), types::Object);
|
||||
assert_bit_equal(Type::fixnum(3).union(types::Fixnum), types::Fixnum);
|
||||
|
||||
assert_bit_equal(types::IntegerExact.union(types::Fixnum), types::IntegerExact);
|
||||
assert_bit_equal(types::Integer.union(types::Fixnum), types::Integer);
|
||||
assert_bit_equal(types::Object.union(types::Fixnum), types::Object);
|
||||
assert_bit_equal(types::Fixnum.union(Type::fixnum(3)), types::Fixnum);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_bits_unions_bits() {
|
||||
assert_bit_equal(types::Fixnum.union(types::StaticSymbol), Type { bits: bits::Fixnum | bits::StaticSymbol, spec: Specialization::Top });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_int_specialized() {
|
||||
assert_bit_equal(Type::from_cbool(true).union(Type::from_cbool(true)), Type::from_cbool(true));
|
||||
assert_bit_equal(Type::from_cbool(true).union(Type::from_cbool(false)), types::CBool);
|
||||
assert_bit_equal(Type::from_cbool(true).union(types::CBool), types::CBool);
|
||||
|
||||
assert_bit_equal(Type::from_cbool(false).union(Type::from_cbool(true)), types::CBool);
|
||||
assert_bit_equal(types::CBool.union(Type::from_cbool(true)), types::CBool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_one_type_specialized_returns_unspecialized() {
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let specialized = Type::from_value(unsafe { rb_ary_new_capa(0) });
|
||||
let unspecialized = types::StringExact;
|
||||
assert_bit_equal(specialized.union(unspecialized), Type { bits: bits::ArrayExact | bits::StringExact, spec: Specialization::Top });
|
||||
assert_bit_equal(unspecialized.union(specialized), Type { bits: bits::ArrayExact | bits::StringExact, spec: Specialization::Top });
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_specialized_builtin_subtype_returns_unspecialized() {
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let hello = Type::from_value(rust_str_to_ruby("hello"));
|
||||
let world = Type::from_value(rust_str_to_ruby("world"));
|
||||
assert_bit_equal(hello.union(world), types::StringExact);
|
||||
});
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let hello = Type::from_value(rust_str_to_sym("hello"));
|
||||
let world = Type::from_value(rust_str_to_sym("world"));
|
||||
assert_bit_equal(hello.union(world), types::StaticSymbol);
|
||||
});
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let left = Type::from_value(rust_str_to_ruby("hello"));
|
||||
let right = Type::from_value(rust_str_to_ruby("hello"));
|
||||
assert_bit_equal(left.union(right), types::StringExact);
|
||||
});
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let left = Type::from_value(rust_str_to_sym("hello"));
|
||||
let right = Type::from_value(rust_str_to_sym("hello"));
|
||||
assert_bit_equal(left.union(right), left);
|
||||
});
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let left = Type::from_value(unsafe { rb_ary_new_capa(0) });
|
||||
let right = Type::from_value(unsafe { rb_ary_new_capa(0) });
|
||||
assert_bit_equal(left.union(right), types::ArrayExact);
|
||||
});
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let left = Type::from_value(unsafe { rb_hash_new() });
|
||||
let right = Type::from_value(unsafe { rb_hash_new() });
|
||||
assert_bit_equal(left.union(right), types::HashExact);
|
||||
});
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let left = Type::from_value(unsafe { rb_float_new(1.0) });
|
||||
let right = Type::from_value(unsafe { rb_float_new(2.0) });
|
||||
assert_bit_equal(left.union(right), types::Flonum);
|
||||
});
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let left = Type::from_value(unsafe { rb_float_new(1.7976931348623157e+308) });
|
||||
let right = Type::from_value(unsafe { rb_float_new(1.7976931348623157e+308) });
|
||||
assert_bit_equal(left.union(right), types::HeapFloat);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_specialized_with_no_relation_returns_unspecialized() {
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let string = Type::from_value(rust_str_to_ruby("hello"));
|
||||
let array = Type::from_value(unsafe { rb_ary_new_capa(0) });
|
||||
assert_bit_equal(string.union(array), Type { bits: bits::ArrayExact | bits::StringExact, spec: Specialization::Top });
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_specialized_with_subclass_relationship_returns_superclass() {
|
||||
crate::cruby::with_rubyvm(|| {
|
||||
let c_class = define_class("C", unsafe { rb_cObject });
|
||||
let d_class = define_class("D", c_class);
|
||||
let c_instance = Type { bits: bits::ObjectUser, spec: Specialization::TypeExact(c_class) };
|
||||
let d_instance = Type { bits: bits::ObjectUser, spec: Specialization::TypeExact(d_class) };
|
||||
assert_bit_equal(c_instance.union(c_instance), Type { bits: bits::ObjectUser, spec: Specialization::TypeExact(c_class)});
|
||||
assert_bit_equal(c_instance.union(d_instance), Type { bits: bits::ObjectUser, spec: Specialization::Type(c_class)});
|
||||
assert_bit_equal(d_instance.union(c_instance), Type { bits: bits::ObjectUser, spec: Specialization::Type(c_class)});
|
||||
});
|
||||
}
|
||||
}
|
177
zjit/src/hir_type_generated.rs
Normal file
177
zjit/src/hir_type_generated.rs
Normal file
@ -0,0 +1,177 @@
|
||||
// This file is @generated by src/gen_hir_type.rb.
|
||||
mod bits {
|
||||
pub const Array: u64 = ArrayExact | ArrayUser;
|
||||
pub const ArrayExact: u64 = 1u64 << 0;
|
||||
pub const ArrayUser: u64 = 1u64 << 1;
|
||||
pub const Bignum: u64 = 1u64 << 2;
|
||||
pub const Bottom: u64 = 0u64;
|
||||
pub const BuiltinExact: u64 = ArrayExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | StringExact | SymbolExact | TrueClassExact;
|
||||
pub const CBool: u64 = 1u64 << 3;
|
||||
pub const CDouble: u64 = 1u64 << 4;
|
||||
pub const CInt: u64 = CSigned | CUnsigned;
|
||||
pub const CInt16: u64 = 1u64 << 5;
|
||||
pub const CInt32: u64 = 1u64 << 6;
|
||||
pub const CInt64: u64 = 1u64 << 7;
|
||||
pub const CInt8: u64 = 1u64 << 8;
|
||||
pub const CNull: u64 = 1u64 << 9;
|
||||
pub const CPtr: u64 = 1u64 << 10;
|
||||
pub const CSigned: u64 = CInt16 | CInt32 | CInt64 | CInt8;
|
||||
pub const CUInt16: u64 = 1u64 << 11;
|
||||
pub const CUInt32: u64 = 1u64 << 12;
|
||||
pub const CUInt64: u64 = 1u64 << 13;
|
||||
pub const CUInt8: u64 = 1u64 << 14;
|
||||
pub const CUnsigned: u64 = CUInt16 | CUInt32 | CUInt64 | CUInt8;
|
||||
pub const DynamicSymbol: u64 = 1u64 << 15;
|
||||
pub const FalseClass: u64 = FalseClassExact | FalseClassUser;
|
||||
pub const FalseClassExact: u64 = 1u64 << 16;
|
||||
pub const FalseClassUser: u64 = 1u64 << 17;
|
||||
pub const Fixnum: u64 = 1u64 << 18;
|
||||
pub const Float: u64 = FloatExact | FloatUser;
|
||||
pub const FloatExact: u64 = Flonum | HeapFloat;
|
||||
pub const FloatUser: u64 = 1u64 << 19;
|
||||
pub const Flonum: u64 = 1u64 << 20;
|
||||
pub const Hash: u64 = HashExact | HashUser;
|
||||
pub const HashExact: u64 = 1u64 << 21;
|
||||
pub const HashUser: u64 = 1u64 << 22;
|
||||
pub const HeapFloat: u64 = 1u64 << 23;
|
||||
pub const Integer: u64 = IntegerExact | IntegerUser;
|
||||
pub const IntegerExact: u64 = Bignum | Fixnum;
|
||||
pub const IntegerUser: u64 = 1u64 << 24;
|
||||
pub const NilClass: u64 = NilClassExact | NilClassUser;
|
||||
pub const NilClassExact: u64 = 1u64 << 25;
|
||||
pub const NilClassUser: u64 = 1u64 << 26;
|
||||
pub const Object: u64 = BuiltinExact | ObjectExact | ObjectUser;
|
||||
pub const ObjectExact: u64 = 1u64 << 27;
|
||||
pub const ObjectUser: u64 = Array | FalseClass | Float | Hash | Integer | NilClass | String | Symbol | TrueClass;
|
||||
pub const Primitive: u64 = CBool | CDouble | CInt | CNull | CPtr;
|
||||
pub const StaticSymbol: u64 = 1u64 << 28;
|
||||
pub const String: u64 = StringExact | StringUser;
|
||||
pub const StringExact: u64 = 1u64 << 29;
|
||||
pub const StringUser: u64 = 1u64 << 30;
|
||||
pub const Symbol: u64 = SymbolExact | SymbolUser;
|
||||
pub const SymbolExact: u64 = DynamicSymbol | StaticSymbol;
|
||||
pub const SymbolUser: u64 = 1u64 << 31;
|
||||
pub const Top: u64 = Object | Primitive | User;
|
||||
pub const TrueClass: u64 = TrueClassExact | TrueClassUser;
|
||||
pub const TrueClassExact: u64 = 1u64 << 32;
|
||||
pub const TrueClassUser: u64 = 1u64 << 33;
|
||||
pub const User: u64 = ArrayUser | FalseClassUser | FloatUser | HashUser | IntegerUser | NilClassUser | StringUser | SymbolUser | TrueClassUser;
|
||||
pub const AllBitPatterns: [(&'static str, u64); 56] = [
|
||||
("Array", Array),
|
||||
("ArrayExact", ArrayExact),
|
||||
("ArrayUser", ArrayUser),
|
||||
("Bignum", Bignum),
|
||||
("Bottom", Bottom),
|
||||
("BuiltinExact", BuiltinExact),
|
||||
("CBool", CBool),
|
||||
("CDouble", CDouble),
|
||||
("CInt", CInt),
|
||||
("CInt16", CInt16),
|
||||
("CInt32", CInt32),
|
||||
("CInt64", CInt64),
|
||||
("CInt8", CInt8),
|
||||
("CNull", CNull),
|
||||
("CPtr", CPtr),
|
||||
("CSigned", CSigned),
|
||||
("CUInt16", CUInt16),
|
||||
("CUInt32", CUInt32),
|
||||
("CUInt64", CUInt64),
|
||||
("CUInt8", CUInt8),
|
||||
("CUnsigned", CUnsigned),
|
||||
("DynamicSymbol", DynamicSymbol),
|
||||
("FalseClass", FalseClass),
|
||||
("FalseClassExact", FalseClassExact),
|
||||
("FalseClassUser", FalseClassUser),
|
||||
("Fixnum", Fixnum),
|
||||
("Float", Float),
|
||||
("FloatExact", FloatExact),
|
||||
("FloatUser", FloatUser),
|
||||
("Flonum", Flonum),
|
||||
("Hash", Hash),
|
||||
("HashExact", HashExact),
|
||||
("HashUser", HashUser),
|
||||
("HeapFloat", HeapFloat),
|
||||
("Integer", Integer),
|
||||
("IntegerExact", IntegerExact),
|
||||
("IntegerUser", IntegerUser),
|
||||
("NilClass", NilClass),
|
||||
("NilClassExact", NilClassExact),
|
||||
("NilClassUser", NilClassUser),
|
||||
("Object", Object),
|
||||
("ObjectExact", ObjectExact),
|
||||
("ObjectUser", ObjectUser),
|
||||
("Primitive", Primitive),
|
||||
("StaticSymbol", StaticSymbol),
|
||||
("String", String),
|
||||
("StringExact", StringExact),
|
||||
("StringUser", StringUser),
|
||||
("Symbol", Symbol),
|
||||
("SymbolExact", SymbolExact),
|
||||
("SymbolUser", SymbolUser),
|
||||
("Top", Top),
|
||||
("TrueClass", TrueClass),
|
||||
("TrueClassExact", TrueClassExact),
|
||||
("TrueClassUser", TrueClassUser),
|
||||
("User", User),
|
||||
];
|
||||
pub const NumTypeBits: u64 = 34;
|
||||
}
|
||||
pub mod types {
|
||||
use super::*;
|
||||
pub const Array: Type = Type::from_bits(bits::Array);
|
||||
pub const ArrayExact: Type = Type::from_bits(bits::ArrayExact);
|
||||
pub const ArrayUser: Type = Type::from_bits(bits::ArrayUser);
|
||||
pub const Bignum: Type = Type::from_bits(bits::Bignum);
|
||||
pub const Bottom: Type = Type::from_bits(bits::Bottom);
|
||||
pub const BuiltinExact: Type = Type::from_bits(bits::BuiltinExact);
|
||||
pub const CBool: Type = Type::from_bits(bits::CBool);
|
||||
pub const CDouble: Type = Type::from_bits(bits::CDouble);
|
||||
pub const CInt: Type = Type::from_bits(bits::CInt);
|
||||
pub const CInt16: Type = Type::from_bits(bits::CInt16);
|
||||
pub const CInt32: Type = Type::from_bits(bits::CInt32);
|
||||
pub const CInt64: Type = Type::from_bits(bits::CInt64);
|
||||
pub const CInt8: Type = Type::from_bits(bits::CInt8);
|
||||
pub const CNull: Type = Type::from_bits(bits::CNull);
|
||||
pub const CPtr: Type = Type::from_bits(bits::CPtr);
|
||||
pub const CSigned: Type = Type::from_bits(bits::CSigned);
|
||||
pub const CUInt16: Type = Type::from_bits(bits::CUInt16);
|
||||
pub const CUInt32: Type = Type::from_bits(bits::CUInt32);
|
||||
pub const CUInt64: Type = Type::from_bits(bits::CUInt64);
|
||||
pub const CUInt8: Type = Type::from_bits(bits::CUInt8);
|
||||
pub const CUnsigned: Type = Type::from_bits(bits::CUnsigned);
|
||||
pub const DynamicSymbol: Type = Type::from_bits(bits::DynamicSymbol);
|
||||
pub const FalseClass: Type = Type::from_bits(bits::FalseClass);
|
||||
pub const FalseClassExact: Type = Type::from_bits(bits::FalseClassExact);
|
||||
pub const FalseClassUser: Type = Type::from_bits(bits::FalseClassUser);
|
||||
pub const Fixnum: Type = Type::from_bits(bits::Fixnum);
|
||||
pub const Float: Type = Type::from_bits(bits::Float);
|
||||
pub const FloatExact: Type = Type::from_bits(bits::FloatExact);
|
||||
pub const FloatUser: Type = Type::from_bits(bits::FloatUser);
|
||||
pub const Flonum: Type = Type::from_bits(bits::Flonum);
|
||||
pub const Hash: Type = Type::from_bits(bits::Hash);
|
||||
pub const HashExact: Type = Type::from_bits(bits::HashExact);
|
||||
pub const HashUser: Type = Type::from_bits(bits::HashUser);
|
||||
pub const HeapFloat: Type = Type::from_bits(bits::HeapFloat);
|
||||
pub const Integer: Type = Type::from_bits(bits::Integer);
|
||||
pub const IntegerExact: Type = Type::from_bits(bits::IntegerExact);
|
||||
pub const IntegerUser: Type = Type::from_bits(bits::IntegerUser);
|
||||
pub const NilClass: Type = Type::from_bits(bits::NilClass);
|
||||
pub const NilClassExact: Type = Type::from_bits(bits::NilClassExact);
|
||||
pub const NilClassUser: Type = Type::from_bits(bits::NilClassUser);
|
||||
pub const Object: Type = Type::from_bits(bits::Object);
|
||||
pub const ObjectExact: Type = Type::from_bits(bits::ObjectExact);
|
||||
pub const ObjectUser: Type = Type::from_bits(bits::ObjectUser);
|
||||
pub const Primitive: Type = Type::from_bits(bits::Primitive);
|
||||
pub const StaticSymbol: Type = Type::from_bits(bits::StaticSymbol);
|
||||
pub const String: Type = Type::from_bits(bits::String);
|
||||
pub const StringExact: Type = Type::from_bits(bits::StringExact);
|
||||
pub const StringUser: Type = Type::from_bits(bits::StringUser);
|
||||
pub const Symbol: Type = Type::from_bits(bits::Symbol);
|
||||
pub const SymbolExact: Type = Type::from_bits(bits::SymbolExact);
|
||||
pub const SymbolUser: Type = Type::from_bits(bits::SymbolUser);
|
||||
pub const Top: Type = Type::from_bits(bits::Top);
|
||||
pub const TrueClass: Type = Type::from_bits(bits::TrueClass);
|
||||
pub const TrueClassExact: Type = Type::from_bits(bits::TrueClassExact);
|
||||
pub const TrueClassUser: Type = Type::from_bits(bits::TrueClassUser);
|
||||
pub const User: Type = Type::from_bits(bits::User);
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
mod state;
|
||||
mod cruby;
|
||||
mod hir;
|
||||
mod hir_type;
|
||||
mod codegen;
|
||||
mod stats;
|
||||
mod cast;
|
||||
|
Loading…
x
Reference in New Issue
Block a user