YJIT: shink local types from 8 to 4 bytes (#8597)

* Shink local types from 8 to 4 bytes, context from 21 to 17 bytes

Use repr(packed)

* Add comment about Type being limited to 4 bits
This commit is contained in:
Maxime Chevalier-Boisvert 2023-10-05 17:17:33 -04:00 committed by GitHub
parent 7d1abd5d31
commit 13bde94a9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -42,7 +42,7 @@ pub type IseqIdx = u16;
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
#[repr(u8)] #[repr(u8)]
pub enum Type { pub enum Type {
Unknown, Unknown = 0,
UnknownImm, UnknownImm,
UnknownHeap, UnknownHeap,
Nil, Nil,
@ -64,6 +64,10 @@ pub enum Type {
BlockParamProxy, // A special sentinel value indicating the block parameter should be read from BlockParamProxy, // A special sentinel value indicating the block parameter should be read from
// the current surrounding cfp // the current surrounding cfp
// The context currently relies on types taking at most 4 bits (max value 15)
// to encode, so if we add any more, we will need to refactor the context,
// or we could remove HeapSymbol, which is currently unused.
} }
// Default initialization // Default initialization
@ -438,7 +442,8 @@ impl RegTemps {
/// Code generation context /// Code generation context
/// Contains information we can use to specialize/optimize code /// Contains information we can use to specialize/optimize code
/// There are a lot of context objects so we try to keep the size small. /// There are a lot of context objects so we try to keep the size small.
#[derive(Clone, Default, Eq, Hash, PartialEq, Debug)] #[derive(Copy, Clone, Default, Eq, Hash, PartialEq, Debug)]
#[repr(packed)]
pub struct Context { pub struct Context {
// Number of values currently on the temporary stack // Number of values currently on the temporary stack
stack_size: u8, stack_size: u8,
@ -457,7 +462,8 @@ pub struct Context {
chain_depth_return_landing: u8, chain_depth_return_landing: u8,
// Local variable types we keep track of // Local variable types we keep track of
local_types: [Type; MAX_LOCAL_TYPES], // We store 8 local types, requiring 4 bits each, for a total of 32 bits
local_types: u32,
// Type we track for self // Type we track for self
self_type: Type, self_type: Type,
@ -1711,7 +1717,7 @@ impl Context {
MapToLocal => { MapToLocal => {
let idx = mapping.get_local_idx(); let idx = mapping.get_local_idx();
assert!((idx as usize) < MAX_LOCAL_TYPES); assert!((idx as usize) < MAX_LOCAL_TYPES);
return self.local_types[idx as usize]; return self.get_local_type(idx.into());
} }
} }
} }
@ -1719,8 +1725,14 @@ impl Context {
} }
/// Get the currently tracked type for a local variable /// Get the currently tracked type for a local variable
pub fn get_local_type(&self, idx: usize) -> Type { pub fn get_local_type(&self, local_idx: usize) -> Type {
*self.local_types.get(idx).unwrap_or(&Type::Unknown) if local_idx >= MAX_LOCAL_TYPES {
return Type::Unknown
} else {
// Each type is stored in 4 bits
let type_bits = (self.local_types >> (4 * local_idx)) & 0b1111;
unsafe { transmute::<u8, Type>(type_bits as u8) }
}
} }
/// Upgrade (or "learn") the type of an instruction operand /// Upgrade (or "learn") the type of an instruction operand
@ -1756,7 +1768,9 @@ impl Context {
MapToLocal => { MapToLocal => {
let idx = mapping.get_local_idx() as usize; let idx = mapping.get_local_idx() as usize;
assert!(idx < MAX_LOCAL_TYPES); assert!(idx < MAX_LOCAL_TYPES);
self.local_types[idx].upgrade(opnd_type); let mut new_type = self.get_local_type(idx);
new_type.upgrade(opnd_type);
self.set_local_type(idx, new_type);
} }
} }
} }
@ -1814,26 +1828,26 @@ impl Context {
/// Set the type of a local variable /// Set the type of a local variable
pub fn set_local_type(&mut self, local_idx: usize, local_type: Type) { pub fn set_local_type(&mut self, local_idx: usize, local_type: Type) {
let ctx = self;
// If type propagation is disabled, store no types // If type propagation is disabled, store no types
if get_option!(no_type_prop) { if get_option!(no_type_prop) {
return; return;
} }
if local_idx >= MAX_LOCAL_TYPES { if local_idx >= MAX_LOCAL_TYPES {
return; return
} }
// If any values on the stack map to this local we must detach them // If any values on the stack map to this local we must detach them
for mapping in ctx.temp_mapping.iter_mut() { for mapping_idx in 0..self.temp_mapping.len() {
*mapping = match mapping.get_kind() { let mapping = self.temp_mapping[mapping_idx];
MapToStack => *mapping, self.temp_mapping[mapping_idx] = match mapping.get_kind() {
MapToSelf => *mapping, MapToStack => mapping,
MapToSelf => mapping,
MapToLocal => { MapToLocal => {
let idx = mapping.get_local_idx(); let idx = mapping.get_local_idx();
if idx as usize == local_idx { if idx as usize == local_idx {
TempMapping::map_to_stack(ctx.local_types[idx as usize]) let local_type = self.get_local_type(local_idx.into());
TempMapping::map_to_stack(local_type)
} else { } else {
TempMapping::map_to_local(idx) TempMapping::map_to_local(idx)
} }
@ -1841,7 +1855,12 @@ impl Context {
} }
} }
ctx.local_types[local_idx] = local_type; // Update the type bits
let type_bits = local_type as u32;
assert!(type_bits <= 0b1111);
let mask_bits = (0b1111 as u32) << (4 * local_idx);
let shifted_bits = type_bits << (4 * local_idx);
self.local_types = (self.local_types & !mask_bits) | shifted_bits;
} }
/// Erase local variable type information /// Erase local variable type information
@ -1849,15 +1868,17 @@ impl Context {
pub fn clear_local_types(&mut self) { pub fn clear_local_types(&mut self) {
// When clearing local types we must detach any stack mappings to those // When clearing local types we must detach any stack mappings to those
// locals. Even if local values may have changed, stack values will not. // locals. Even if local values may have changed, stack values will not.
for mapping in self.temp_mapping.iter_mut() {
for mapping_idx in 0..self.temp_mapping.len() {
let mapping = self.temp_mapping[mapping_idx];
if mapping.get_kind() == MapToLocal { if mapping.get_kind() == MapToLocal {
let idx = mapping.get_local_idx(); let local_idx = mapping.get_local_idx() as usize;
*mapping = TempMapping::map_to_stack(self.local_types[idx as usize]); self.temp_mapping[mapping_idx] = TempMapping::map_to_stack(self.get_local_type(local_idx));
} }
} }
// Clear the local types // Clear the local types
self.local_types = [Type::default(); MAX_LOCAL_TYPES]; self.local_types = 0;
} }
/// Compute a difference score for two context objects /// Compute a difference score for two context objects
@ -1902,9 +1923,9 @@ impl Context {
}; };
// For each local type we track // For each local type we track
for i in 0..src.local_types.len() { for i in 0.. MAX_LOCAL_TYPES {
let t_src = src.local_types[i]; let t_src = src.get_local_type(i);
let t_dst = dst.local_types[i]; let t_dst = dst.get_local_type(i);
diff += match t_src.diff(t_dst) { diff += match t_src.diff(t_dst) {
TypeDiff::Compatible(diff) => diff, TypeDiff::Compatible(diff) => diff,
TypeDiff::Incompatible => return TypeDiff::Incompatible, TypeDiff::Incompatible => return TypeDiff::Incompatible,
@ -3237,11 +3258,45 @@ impl<T> RefUnchecked for Cell<T> {
mod tests { mod tests {
use crate::core::*; use crate::core::*;
#[test]
fn type_size() {
// Check that we can store types in 4 bits,
// and all local types in 32 bits
assert_eq!(mem::size_of::<Type>(), 1);
assert!(Type::BlockParamProxy as usize <= 0b1111);
assert!(MAX_LOCAL_TYPES * 4 <= 32);
}
#[test] #[test]
fn tempmapping_size() { fn tempmapping_size() {
assert_eq!(mem::size_of::<TempMapping>(), 1); assert_eq!(mem::size_of::<TempMapping>(), 1);
} }
#[test]
fn local_types() {
let mut ctx = Context::default();
for i in 0..MAX_LOCAL_TYPES {
ctx.set_local_type(i, Type::Fixnum);
assert_eq!(ctx.get_local_type(i), Type::Fixnum);
ctx.set_local_type(i, Type::BlockParamProxy);
assert_eq!(ctx.get_local_type(i), Type::BlockParamProxy);
}
ctx.set_local_type(0, Type::Fixnum);
ctx.clear_local_types();
assert!(ctx.get_local_type(0) == Type::Unknown);
// Make sure we don't accidentally set bits incorrectly
let mut ctx = Context::default();
ctx.set_local_type(0, Type::Fixnum);
assert_eq!(ctx.get_local_type(0), Type::Fixnum);
ctx.set_local_type(2, Type::Fixnum);
ctx.set_local_type(1, Type::BlockParamProxy);
assert_eq!(ctx.get_local_type(0), Type::Fixnum);
assert_eq!(ctx.get_local_type(2), Type::Fixnum);
}
#[test] #[test]
fn tempmapping() { fn tempmapping() {
let t = TempMapping::map_to_stack(Type::Unknown); let t = TempMapping::map_to_stack(Type::Unknown);
@ -3259,7 +3314,7 @@ mod tests {
#[test] #[test]
fn context_size() { fn context_size() {
assert_eq!(mem::size_of::<Context>(), 21); assert_eq!(mem::size_of::<Context>(), 17);
} }
#[test] #[test]