Add compact Type lattice

This will be used for local type inference and potentially SCCP.
This commit is contained in:
Max Bernstein 2025-03-04 12:18:40 -05:00 committed by Takashi Kokubun
parent 0a543daf15
commit ec41dffd05
Notes: git 2025-04-18 13:48:27 +00:00
10 changed files with 989 additions and 25 deletions

View File

@ -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")

View File

@ -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)*)));
}
};
}

View File

@ -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;

View File

@ -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::*;

View File

@ -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
View 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 "}"

View File

@ -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
View 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)});
});
}
}

View 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);
}

View File

@ -4,6 +4,7 @@
mod state;
mod cruby;
mod hir;
mod hir_type;
mod codegen;
mod stats;
mod cast;