Skip to content

Commit

Permalink
fix: implement niches instead of fragmented bits.
Browse files Browse the repository at this point in the history
  • Loading branch information
rzvxa committed Jul 29, 2024
1 parent 5f47351 commit 0ebd4eb
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 101 deletions.
119 changes: 58 additions & 61 deletions tasks/ast_codegen/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use lazy_static::lazy_static;
use quote::ToTokens;
use syn::Type;

use crate::util::{Bits, FromBits, ToBits};
use crate::{
schema::{REnum, RStruct, RType},
util::{NormalizeError, TypeAnalyzeResult, TypeExt, TypeWrapper},
Expand All @@ -23,8 +22,8 @@ pub enum Layout {
}

impl Layout {
const fn known(size: usize, align: usize, trailing_pad: usize, fragmented_bits: Bits) -> Self {
Self::Layout(KnownLayout { size, align, trailing_pad, fragmented_bits })
const fn known(size: usize, align: usize, trailing_pad: usize, niches: u128) -> Self {
Self::Layout(KnownLayout { size, align, trailing_pad, niches })
}

pub fn layout(self) -> Option<KnownLayout> {
Expand All @@ -48,9 +47,8 @@ pub struct KnownLayout {
align: usize,
/// padding at the end of type to align it
trailing_pad: usize,
/// number of unused bits scatered throughout layout, this doesn't contain the `trailing_pad`
/// bits.
fragmented_bits: Bits,
/// number of available niches
niches: u128,
}

impl KnownLayout {
Expand All @@ -73,31 +71,31 @@ impl KnownLayout {
/// number of unused bits scatered throughout layout, this doesn't contain the `trailing_pad`
/// bits.
#[inline]
pub fn fragmented_bits(&self) -> Bits {
self.fragmented_bits
pub fn niches(&self) -> u128 {
self.niches
}

/// Panics
/// if doesn't have enough viable space and `can_resize` is false
pub fn consume_trailing_pad(&mut self, size: usize, can_resize: bool) {
self.consume_bits(size.to_bits(), false, can_resize);
pub fn consume_trailing_pad(&mut self, size: usize) {
self.trailing_pad -= size;
}

/// Panics
/// if doesn't have enough viable space and `can_resize` is false
pub fn consume_bits(&mut self, bits: Bits, can_be_fragmented: bool, can_resize: bool) {
if can_be_fragmented && self.fragmented_bits() >= bits {
self.fragmented_bits -= 1;
} else if self.trailing_pad() > 0 {
pub fn consume_niches(&mut self, n: u128, can_eat_pad: bool, can_resize: bool) {
if self.niches() >= n {
self.niches -= 1;
} else if can_eat_pad && self.trailing_pad() > 0 {
// consume one byte of padding at the end
self.trailing_pad -= 1;
// consume one bit of that byte and add the remaining unused bits as fragmented space.
self.fragmented_bits = 1.to_bits() - 1;
// consume one and add the remaining values of one byte as niches.
self.niches = (u8::MAX - 1) as u128;
} else if can_resize {
let align = self.align();
self.size += align;
self.trailing_pad += align;
self.consume_bits(bits, can_be_fragmented, can_resize);
self.consume_niches(n, can_eat_pad, can_resize);
} else {
panic!("`consume_bit` called on a layout without enough space.");
}
Expand All @@ -114,43 +112,44 @@ impl Layout {
/// If alignment of `T` is higher than one byte.
const fn of<T>() -> Self {
// TODO: find a better way of calculating this.
struct Bit1<T>(Option<T>);
struct Bit2<T>(Bit1<Bit1<T>>);
struct Bit3<T>(Bit1<Bit2<T>>);
struct Bit4<T>(Bit1<Bit3<T>>);
struct Bit5<T>(Bit1<Bit4<T>>);
struct Bit6<T>(Bit1<Bit5<T>>);
struct Bit7<T>(Bit1<Bit6<T>>);
struct Bit8<T>(Bit1<Bit7<T>>);
struct N1<T>(Option<T>);
struct N2<T>(N1<N1<T>>);
struct N3<T>(N1<N2<T>>);
struct N4<T>(N1<N3<T>>);
struct N5<T>(N1<N4<T>>);
struct N6<T>(N1<N5<T>>);
struct N7<T>(N1<N6<T>>);
struct N8<T>(N1<N7<T>>);

let size = size_of::<T>();
let align = align_of::<T>();
let fragmented_bits = if size_of::<Bit1<T>>() > size {
let niches = if size_of::<N1<T>>() > size {
0
} else if size_of::<Bit2<T>>() > size {
} else if size_of::<N2<T>>() > size {
1
} else if size_of::<Bit3<T>>() > size {
} else if size_of::<N3<T>>() > size {
2
} else if size_of::<Bit4<T>>() > size {
} else if size_of::<N4<T>>() > size {
3
} else if size_of::<Bit5<T>>() > size {
} else if size_of::<N5<T>>() > size {
4
} else if size_of::<Bit6<T>>() > size {
} else if size_of::<N6<T>>() > size {
5
} else if size_of::<Bit7<T>>() > size {
} else if size_of::<N7<T>>() > size {
6
} else if size_of::<Bit8<T>>() > size {
} else if size_of::<N8<T>>() > size {
7
} else if size_of::<Bit8<T>>() == size {
} else if size_of::<N8<T>>() == size {
8
} else {
panic!(
"Alignment of `T` is bigger than what this method can calculate the headroom for."
);
};
// NOTE: some or all of `fragmented_bits` might be `trailing_pad` but we don't need to
// NOTE: some or all of `niches` might be `trailing_pad` but we don't need to
// distinguish between them. This method is only used to get layout info of simple types.
Self::known(size, align, 0, fragmented_bits)
// most of them are builtin primitives.
Self::known(size, align, 0, niches)
}

const fn zero() -> Self {
Expand Down Expand Up @@ -336,13 +335,9 @@ fn calc_enum_layout(ty: &mut REnum, ctx: &CodegenCtx) -> Result<PlatformLayout>
// all unit variants?
if ty.item.variants.iter().all(|var| var.fields.is_empty()) {
// all AST enums are `repr(C)` so it would have a 4 byte layout/alignment,
const TAG_SIZE: usize = 4;
const TAG_ALIGN: usize = 4;
let var_len = ty.item.variants.len();
// We use one bit per each variant to identify it, The rest are unused bits.
let unused_bits = 4.to_bits() - var_len as Bits;
let (padding_bytes, fragmented_bits) = usize::from_bits(unused_bits);
let layout = Layout::known(TAG_SIZE, TAG_ALIGN, padding_bytes, fragmented_bits);
let mut layout = KnownLayout { size: 0, align: 4, trailing_pad: 0, niches: 0 };
layout.consume_niches(ty.item.variants.len() as u128, true, true);
let layout = Layout::Layout(layout);
Ok(vec![PlatformLayout(layout, layout)])
} else {
ty.item
Expand Down Expand Up @@ -370,17 +365,17 @@ fn calc_enum_layout(ty: &mut REnum, ctx: &CodegenCtx) -> Result<PlatformLayout>
if layout.trailing_pad() < acc.trailing_pad() {
acc.trailing_pad = layout.trailing_pad();
}
// min fragmented_bits
if layout.fragmented_bits() < acc.fragmented_bits() {
acc.fragmented_bits = layout.fragmented_bits();
// min niches
if layout.niches() < acc.niches() {
acc.niches = layout.niches();
}
acc
}

fn with_tag(mut acc: KnownLayout) -> KnownLayout {
acc.consume_bits(size_of::<u8>().to_bits(), false, true);
let with_tag = |mut acc: KnownLayout| -> KnownLayout {
acc.consume_niches(ty.item.variants.len() as u128, true, true);
acc
}
};

let layouts = collect_variant_layouts(ty, ctx)?;
let (layouts_x64, layouts_x32): (Vec<KnownLayout>, Vec<KnownLayout>) = layouts
Expand All @@ -391,14 +386,16 @@ fn calc_enum_layout(ty: &mut REnum, ctx: &CodegenCtx) -> Result<PlatformLayout>
.collect::<Option<_>>()
.expect("already checked.");

let x32 = with_tag(layouts_x32.into_iter().fold(
KnownLayout { size: 0, align: 0, trailing_pad: usize::MAX, fragmented_bits: 0 },
fold_layout,
));
let x64 = with_tag(layouts_x64.into_iter().fold(
KnownLayout { size: 0, align: 0, trailing_pad: usize::MAX, fragmented_bits: 0 },
fold_layout,
));
let x32 =
with_tag(layouts_x32.into_iter().fold(
KnownLayout { size: 0, align: 0, trailing_pad: usize::MAX, niches: 0 },
fold_layout,
));
let x64 =
with_tag(layouts_x64.into_iter().fold(
KnownLayout { size: 0, align: 0, trailing_pad: usize::MAX, niches: 0 },
fold_layout,
));
Ok(PlatformLayout(Layout::from(x64), Layout::from(x32)))
}

Expand All @@ -424,13 +421,13 @@ fn calc_struct_layout(ty: &mut RStruct, ctx: &CodegenCtx) -> Result<PlatformLayo
// TODO: store `offsets` in the layout
let mut offsets = Vec::new();
let mut output = std::alloc::Layout::from_size_align(0, 1)?;
let mut fragmented_bits = 0;
let mut niches = 0;
for layout in layouts {
let (new_layout, offset) =
output.extend(std::alloc::Layout::from_size_align(layout.size, layout.align)?)?;
output = new_layout;
offsets.push(offset);
fragmented_bits += layout.fragmented_bits();
niches += layout.niches();
}
// TODO: use `std::alloc::Layout::padding_needed_for` when it stabilized
let (pad, output) = {
Expand All @@ -442,7 +439,7 @@ fn calc_struct_layout(ty: &mut RStruct, ctx: &CodegenCtx) -> Result<PlatformLayo
size: output.size(),
align: output.align(),
trailing_pad: pad + layouts.last().map(KnownLayout::trailing_pad).unwrap_or_default(),
fragmented_bits,
niches,
})
}

Expand Down Expand Up @@ -479,7 +476,7 @@ fn calc_type_layout(ty: &TypeAnalyzeResult, ctx: &CodegenCtx) -> Result<Platform
fn try_fold_option(layout: Layout) -> Layout {
let Layout::Layout(mut known) = layout else { return layout };
// option needs only one bit to store its tag and it can be in a fragmented offset
known.consume_bits(1, true, true);
known.consume_niches(1, true, true);
Layout::Layout(known)
}

Expand Down
40 changes: 0 additions & 40 deletions tasks/ast_codegen/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,6 @@ pub trait StrExt: AsRef<str> {
fn to_plural(self) -> String;
}

pub type Bits = u128;

pub trait ToBits {
/// Returns number of bits in the given bytes, basiaclly `self * 8`
fn to_bits(&self) -> Bits;
}

pub trait FromBits {
/// Returns number of bytes that can be constructed from the given bits and the leftover
/// fragmented bits.
fn from_bits(bits: Bits) -> (usize, /* fragments */ Bits);
}

#[derive(Debug)]
pub enum TypeIdentResult<'a> {
/// We bailed on detecting wrapper
Expand Down Expand Up @@ -298,30 +285,3 @@ pub fn write_all_to<S: AsRef<str>>(data: &[u8], path: S) -> std::io::Result<()>
file.write_all(data)?;
Ok(())
}

impl ToBits for usize {
#[inline]
fn to_bits(&self) -> Bits {
bits_from_bytes(*self)
}
}

impl FromBits for usize {
#[inline]
fn from_bits(bits: Bits) -> (Self, Bits) {
bits_to_bytes(bits)
}
}

/// Returns number of bits in the given bytes, basiaclly `bytes * 8`
#[inline]
pub const fn bits_from_bytes(bytes: usize) -> Bits {
bytes as Bits * 8
}

#[inline]
pub fn bits_to_bytes(bits: Bits) -> (usize, /* fragments */ Bits) {
let frag = bits % 8;
let bits = bits - frag;
((bits / 8) as usize, frag)
}

0 comments on commit 0ebd4eb

Please sign in to comment.