use { crate::{ ctx_map, lexer::TokenKind, parser::{self, CommentOr, Expr, ExprRef, Pos}, utils::{self, Ent, EntSlice, EntVec}, Ident, }, alloc::{string::String, vec::Vec}, core::{ cell::Cell, num::NonZeroU32, ops::{Deref, DerefMut, Range}, }, hashbrown::hash_map, }; macro_rules! impl_deref { ($for:ty { $name:ident: $base:ty }) => { impl Deref for $for { type Target = $base; fn deref(&self) -> &Self::Target { &self.$name } } impl DerefMut for $for { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.$name } } }; } pub type ArrayLen = u32; pub type Offset = u32; pub type Size = u32; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, PartialOrd, Ord)] pub struct Tuple(pub u32); impl Tuple { const LEN_BITS: u32 = 5; const LEN_MASK: usize = Self::MAX_LEN - 1; const MAX_LEN: usize = 1 << Self::LEN_BITS; pub fn new(pos: usize, len: usize) -> Option { if len >= Self::MAX_LEN { return None; } Some(Self((pos << Self::LEN_BITS | len) as u32)) } pub fn range(self) -> Range { let start = self.0 as usize >> Self::LEN_BITS; start..start + self.len() } pub fn len(self) -> usize { self.0 as usize & Self::LEN_MASK } pub fn is_empty(self) -> bool { self.len() == 0 } pub fn empty() -> Self { Self(0) } pub fn args(self) -> ArgIter { ArgIter(self.range()) } } pub struct ArgIter(Range); pub enum Arg { Type(Id), Value(Id), } impl ArgIter { pub(crate) fn next(&mut self, tys: &Types) -> Option { let ty = tys.ins.args[self.0.next()?]; if ty == Id::TYPE { return Some(Arg::Type(tys.ins.args[self.0.next().unwrap()])); } Some(Arg::Value(ty)) } pub(crate) fn next_value(&mut self, tys: &Types) -> Option { loop { match self.next(tys)? { Arg::Type(_) => continue, Arg::Value(id) => break Some(id), } } } } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Id(NonZeroU32); impl From for i64 { fn from(value: Id) -> Self { value.0.get() as _ } } impl crate::ctx_map::CtxEntry for Id { type Ctx = TypeIns; type Key<'a> = SymKey<'a>; fn key<'a>(&self, ctx: &'a Self::Ctx) -> Self::Key<'a> { match self.expand() { Kind::Struct(s) => { let st = &ctx.structs[s]; debug_assert_ne!(st.pos, Pos::MAX); SymKey::Type(st.parent, st.pos, st.captured) } Kind::Enum(e) => { let en = &ctx.enums[e]; debug_assert_ne!(en.pos, Pos::MAX); SymKey::Type(en.parent, en.pos, en.captured) } Kind::Union(e) => { let en = &ctx.unions[e]; debug_assert_ne!(en.pos, Pos::MAX); SymKey::Type(en.parent, en.pos, en.captured) } Kind::Ptr(p) => SymKey::Pointer(&ctx.ptrs[p]), Kind::Opt(p) => SymKey::Optional(&ctx.opts[p]), Kind::Func(f) => { let fc = &ctx.funcs[f]; if fc.is_generic { SymKey::Type(fc.parent, fc.pos, fc.sig.args) } else { SymKey::Decl(fc.parent, fc.name) } } Kind::Template(t) => { let tc = &ctx.templates[t]; SymKey::Decl(tc.parent, tc.name) } Kind::Global(g) => { let gb = &ctx.globals[g]; SymKey::Decl(gb.file.into(), gb.name) } Kind::Slice(s) => SymKey::Array(&ctx.slices[s]), Kind::Module(_) | Kind::Builtin(_) => { SymKey::Decl(Module::default().into(), Ident::INVALID) } Kind::Const(c) => SymKey::Constant(&ctx.consts[c]), } } } impl Default for Id { fn default() -> Self { Self(unsafe { NonZeroU32::new_unchecked(UNDECLARED) }) } } impl Id { pub const DINT: Self = Self::UINT; pub fn bin_ret(self, op: TokenKind) -> Id { if op.is_compatison() { Self::BOOL } else { self } } pub fn is_float(self) -> bool { matches!(self.repr(), F32 | F64) || self.is_never() } pub fn is_signed(self) -> bool { matches!(self.repr(), I8..=INT) || self.is_never() } pub fn is_unsigned(self) -> bool { matches!(self.repr(), U8..=UINT) || self.is_never() } pub fn is_integer(self) -> bool { matches!(self.repr(), U8..=INT) || self.is_never() } pub fn is_never(self) -> bool { self == Self::NEVER } pub fn strip_pointer(self) -> Self { match self.expand() { Kind::Ptr(_) => Id::UINT, _ => self, } } pub fn is_pointer(self) -> bool { matches!(self.expand(), Kind::Ptr(_)) || self.is_never() } pub fn is_optional(self) -> bool { matches!(self.expand(), Kind::Opt(_)) || self.is_never() } pub fn try_upcast(self, ob: Self) -> Option { self.try_upcast_low(ob, false) } pub fn try_upcast_low(self, ob: Self, coerce_pointer: bool) -> Option { let (oa, ob) = (Self(self.0.min(ob.0)), Self(self.0.max(ob.0))); let (a, b) = (oa.strip_pointer(), ob.strip_pointer()); Some(match () { _ if oa == Id::NEVER => ob, _ if ob == Id::NEVER => oa, _ if oa == ob => oa, _ if ob.is_optional() => ob, _ if oa.is_pointer() && ob.is_pointer() => return None, _ if oa == Id::BOOL && ob.is_integer() => ob, _ if a.is_signed() && b.is_signed() || a.is_unsigned() && b.is_unsigned() => ob, _ if a.is_unsigned() && b.is_signed() && a.repr() - U8 < b.repr() - I8 => ob, _ if a.is_unsigned() && b.is_signed() && a.repr() - U8 > b.repr() - I8 => oa, _ if oa.is_integer() && ob.is_pointer() && coerce_pointer => ob, _ => return None, }) } pub fn expand(self) -> Kind { Kind::from_ty(self) } pub const fn repr(self) -> u32 { self.0.get() } pub(crate) fn simple_size(&self) -> Option { Some(match self.expand() { Kind::Ptr(_) => 8, Kind::Builtin(Builtin(VOID)) => 0, Kind::Builtin(Builtin(NEVER)) => 0, Kind::Builtin(Builtin(INT | UINT | F64)) => 8, Kind::Builtin(Builtin(I32 | U32 | TYPE | F32)) => 4, Kind::Builtin(Builtin(I16 | U16)) => 2, Kind::Builtin(Builtin(I8 | U8 | BOOL)) => 1, _ => return None, }) } pub(crate) fn extend(self) -> Self { if self.is_signed() { Self::INT } else if self.is_pointer() { self } else { Self::UINT } } pub(crate) fn loc(&self, tys: &Types) -> Loc { match self.expand() { Kind::Opt(o) if let ty = tys.ins.opts[o].base && ty.loc(tys) == Loc::Reg && (ty.is_pointer() || tys.size_of(ty) < 8) => { Loc::Reg } Kind::Ptr(_) | Kind::Enum(_) | Kind::Builtin(_) => Loc::Reg, Kind::Struct(_) | Kind::Union(_) if tys.size_of(*self) == 0 => Loc::Reg, Kind::Struct(_) | Kind::Union(_) | Kind::Slice(_) | Kind::Opt(_) => Loc::Stack, c @ (Kind::Func(_) | Kind::Global(_) | Kind::Module(_) | Kind::Const(_) | Kind::Template(_)) => { unreachable!("{c:?}") } } } pub(crate) fn has_pointers(&self, tys: &Types) -> bool { match self.expand() { Kind::Struct(s) => tys.struct_fields(s).iter().any(|f| f.ty.has_pointers(tys)), Kind::Ptr(_) => true, Kind::Slice(s) => tys.ins.slices[s].len == ArrayLen::MAX, _ => false, } } } #[derive(PartialEq, Eq, Clone, Copy)] pub enum Loc { Reg, Stack, } impl From for Id { fn from(id: u64) -> Self { Self(unsafe { NonZeroU32::new_unchecked(id as _) }) } } const fn array_to_lower_case(array: [u8; N]) -> [u8; N] { let mut result = [0; N]; let mut i = 0; while i < N { result[i] = array[i].to_ascii_lowercase(); i += 1; } result } // const string to lower case macro_rules! builtin_type { ($($name:ident;)*) => { $(const $name: u32 = ${index(0)} + 1;)* mod __lc_names { use super::*; $(pub const $name: &str = unsafe { const LCL: &[u8] = unsafe { &array_to_lower_case( *(stringify!($name).as_ptr() as *const [u8; stringify!($name).len()]) ) }; core::str::from_utf8_unchecked(LCL) };)* } impl Builtin { $(pub const $name: Self = Builtin($name);)* } impl Id { $(pub const $name: Self = Kind::Builtin(Builtin($name)).compress();)* } impl Kind { $(pub const $name: Self = Kind::Builtin(Builtin($name));)* } pub fn from_str(name: &str) -> Option { match name { $(__lc_names::$name => Some(Builtin($name)),)* _ => None, } } pub fn to_str(ty: Builtin) -> &'static str { match ty.0 { $($name => __lc_names::$name,)* v => unreachable!("invalid type: {}", v), } } }; } builtin_type! { UNDECLARED; LEFT_UNREACHABLE; RIGHT_UNREACHABLE; NEVER; VOID; TYPE; BOOL; U8; U16; U32; UINT; I8; I16; I32; INT; F32; F64; } macro_rules! type_kind { ($(#[$meta:meta])* $vis:vis enum $name:ident {$( $variant:ident, )*}) => { crate::utils::decl_ent! { $(pub struct $variant(u32);)* } $(#[$meta])* $vis enum $name { $($variant($variant),)* } impl $name { const FLAG_BITS: u32 = (${count($variant)} as u32).next_power_of_two().ilog2(); const FLAG_OFFSET: u32 = core::mem::size_of::() as u32 * 8 - Self::FLAG_BITS; const INDEX_MASK: u32 = (1 << (32 - Self::FLAG_BITS)) - 1; $vis fn from_ty(ty: Id) -> Self { let (flag, index) = (ty.repr() >> Self::FLAG_OFFSET, ty.repr() & Self::INDEX_MASK); match flag { $(${index(0)} => Self::$variant($variant(index)),)* i => unreachable!("{i}"), } } $vis const fn compress(self) -> Id { let (index, flag) = match self { $(Self::$variant(index) => (index.0, ${index(0)}),)* }; Id(unsafe { NonZeroU32::new_unchecked((flag << Self::FLAG_OFFSET) | index) }) } } $( impl From<$variant> for $name { fn from(value: $variant) -> Self { Self::$variant(value) } } impl From<$variant> for i64 { fn from(value: $variant) -> Self { Id::from(value).into() } } impl From<$variant> for Id { fn from(value: $variant) -> Self { $name::$variant(value).compress() } } )* }; } type_kind! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Kind { Builtin, Struct, Enum, Union, Ptr, Slice, Opt, Func, Template, Global, Module, Const, } } impl Func { pub const ECA: Func = Func(u32::MAX); pub const MAIN: Func = Func(u32::MIN); } impl Module { pub const MAIN: Self = Self(0); } impl Default for Module { fn default() -> Self { Self(u32::MAX) } } impl TryFrom for Builtin { type Error = (); fn try_from(value: Ident) -> Result { if value.is_null() { Ok(Self(value.len())) } else { Err(()) } } } impl Default for Kind { fn default() -> Self { Id::UNDECLARED.expand() } } pub struct Display<'a> { tys: &'a Types, files: &'a EntSlice, ty: Id, } impl<'a> Display<'a> { pub fn new(tys: &'a Types, files: &'a EntSlice, ty: Id) -> Self { Self { tys, files, ty } } pub fn rety(&self, ty: Id) -> Self { Self::new(self.tys, self.files, ty) } } impl core::fmt::Display for Display<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { use Kind as TK; match TK::from_ty(self.ty) { TK::Module(idx) => { f.write_str("@use(\"")?; self.files[idx].path.fmt(f)?; f.write_str(")[")?; idx.fmt(f)?; f.write_str("]") } TK::Builtin(ty) => f.write_str(to_str(ty)), TK::Opt(ty) => { f.write_str("?")?; self.rety(self.tys.ins.opts[ty].base).fmt(f) } TK::Ptr(ty) => { f.write_str("^")?; self.rety(self.tys.ins.ptrs[ty].base).fmt(f) } TK::Struct(idx) => { let record = &self.tys.ins.structs[idx]; if record.name.is_null() { f.write_str("[")?; idx.fmt(f)?; f.write_str("]{")?; for (i, &StructField { name, ty }) in self.tys.struct_fields(idx).iter().enumerate() { if i != 0 { f.write_str(", ")?; } f.write_str(self.tys.names.ident_str(name))?; f.write_str(": ")?; self.rety(ty).fmt(f)?; } f.write_str("}") } else { let file = &self.files[record.file]; f.write_str(file.ident_str(record.name)) } } TK::Union(idx) => { let record = &self.tys.ins.unions[idx]; if record.name.is_null() { f.write_str("[")?; idx.fmt(f)?; f.write_str("]{")?; for (i, &StructField { name, ty }) in self.tys.union_fields(idx).iter().enumerate() { if i != 0 { f.write_str(", ")?; } f.write_str(self.tys.names.ident_str(name))?; f.write_str(": ")?; self.rety(ty).fmt(f)?; } f.write_str("}") } else { let file = &self.files[record.file]; f.write_str(file.ident_str(record.name)) } } TK::Enum(idx) => { let enm = &self.tys.ins.enums[idx]; debug_assert!(!enm.name.is_null()); let file = &self.files[enm.file]; f.write_str(file.ident_str(enm.name)) } TK::Func(idx) => { f.write_str("fn")?; idx.fmt(f) } TK::Template(idx) => { f.write_str("fn")?; idx.fmt(f) } TK::Global(idx) => { let global = &self.tys.ins.globals[idx]; let file = &self.files[global.file]; f.write_str(file.ident_str(global.name))?; f.write_str(" (global)") } TK::Slice(idx) => { let array = self.tys.ins.slices[idx]; f.write_str("[")?; self.rety(array.elem).fmt(f)?; if array.len != ArrayLen::MAX { f.write_str("; ")?; array.len.fmt(f)?; } f.write_str("]") } TK::Const(idx) => { let cnst = &self.tys.ins.consts[idx]; let file = &self.files[cnst.file]; f.write_str(file.ident_str(cnst.name))?; f.write_str(" (const)") } } } } #[derive(PartialEq, Eq, Hash, Clone, Copy)] pub enum SymKey<'a> { Pointer(&'a PtrData), Optional(&'a OptData), Type(Id, Pos, Tuple), Decl(Id, Ident), Array(&'a ArrayData), Constant(&'a ConstData), } #[derive(Clone, Copy, Default)] pub struct Sig { pub args: Tuple, pub ret: Id, } pub struct TemplateData { pub file: Module, pub parent: Id, pub name: Ident, pub expr: ExprRef, pub is_inline: bool, } #[derive(Default, Clone, Copy)] pub struct FuncData { pub file: Module, pub parent: Id, pub name: Ident, pub pos: Pos, pub expr: ExprRef, pub sig: Sig, pub is_inline: bool, pub is_generic: bool, pub comp_state: [PackedCompState; 2], } #[derive(Clone, Copy, PartialEq, Eq)] pub struct PackedCompState(u16); impl Default for PackedCompState { fn default() -> Self { CompState::default().into() } } impl PackedCompState { const COMPILED: u16 = u16::MAX - 1; const DEAD: u16 = u16::MAX; } impl From for CompState { fn from(value: PackedCompState) -> Self { match value.0 { PackedCompState::DEAD => CompState::Dead, PackedCompState::COMPILED => CompState::Compiled, v => CompState::Queued(v as _), } } } impl From for PackedCompState { fn from(value: CompState) -> Self { Self(match value { CompState::Dead => Self::DEAD, CompState::Queued(v) => v.try_into().unwrap(), CompState::Compiled => Self::COMPILED, }) } } #[derive(Default, PartialEq, Eq, Clone, Copy)] pub enum CompState { #[default] Dead, Queued(usize), Compiled, } #[derive(Clone, Default)] pub struct GlobalData { pub file: Module, pub name: Ident, pub ty: Id, pub data: Vec, } #[derive(PartialEq, Eq, Hash)] pub struct ConstData { pub ast: ExprRef, pub name: Ident, pub file: Module, pub parent: Id, } pub struct EnumField { pub name: Ident, } #[derive(Default)] pub struct TypeBase { pub file: Module, pub parent: Id, pub pos: Pos, pub name: Ident, pub field_start: u32, pub captured: Tuple, pub ast: ExprRef, } #[derive(Default)] pub struct EnumData { pub base: TypeBase, } impl_deref!(EnumData { base: TypeBase }); #[derive(Default)] pub struct UnionData { pub base: TypeBase, pub size: Cell, pub align: Cell, } impl_deref!(UnionData { base: TypeBase }); pub struct StructField { pub name: Ident, pub ty: Id, } #[derive(Default)] pub struct StructData { pub base: TypeBase, pub size: Cell, pub align: Cell, // TODO: make this compact pub explicit_alignment: Option, } impl_deref!(StructData { base: TypeBase }); #[derive(PartialEq, Eq, Hash, Clone, Copy)] pub struct OptData { pub base: Id, } #[derive(PartialEq, Eq, Hash, Clone, Copy)] pub struct PtrData { pub base: Id, } #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct ArrayData { pub elem: Id, pub len: ArrayLen, } impl ArrayData { #[expect(clippy::len_without_is_empty)] pub fn len(&self) -> Option { (self.len != ArrayLen::MAX).then_some(self.len as usize) } } impl ctx_map::CtxEntry for Ident { type Ctx = str; type Key<'a> = &'a str; fn key<'a>(&self, ctx: &'a Self::Ctx) -> Self::Key<'a> { unsafe { ctx.get_unchecked(self.range()) } } } #[derive(Default)] pub struct IdentInterner { lookup: ctx_map::CtxMap, strings: String, } impl IdentInterner { pub fn intern(&mut self, ident: &str) -> Ident { let (entry, hash) = self.lookup.entry(ident, &self.strings); match entry { hash_map::RawEntryMut::Occupied(o) => o.get_key_value().0.value, hash_map::RawEntryMut::Vacant(v) => { let id = Ident::new(self.strings.len() as _, ident.len() as _).unwrap(); self.strings.push_str(ident); v.insert(ctx_map::Key { hash, value: id }, ()); id } } } pub fn ident_str(&self, ident: Ident) -> &str { &self.strings[ident.range()] } pub fn project(&self, ident: &str) -> Option { self.lookup.get(ident, &self.strings).copied() } fn clear(&mut self) { self.lookup.clear(); self.strings.clear() } } #[derive(Default)] pub struct TypesTmp { pub struct_fields: Vec, pub enum_fields: Vec, pub args: Vec, } #[derive(Default)] pub struct TypeIns { pub args: Vec, pub struct_fields: Vec, pub enum_fields: Vec, pub funcs: EntVec, pub templates: EntVec, pub globals: EntVec, pub consts: EntVec, pub structs: EntVec, pub enums: EntVec, pub unions: EntVec, pub ptrs: EntVec, pub opts: EntVec, pub slices: EntVec, } pub struct FTask { pub file: Module, pub id: Func, pub ct: bool, } pub struct StringRef(pub Global); impl ctx_map::CtxEntry for StringRef { type Ctx = EntVec; type Key<'a> = &'a [u8]; fn key<'a>(&self, ctx: &'a Self::Ctx) -> Self::Key<'a> { &ctx[self.0].data } } #[derive(Default)] pub struct Types { pub syms: ctx_map::CtxMap, pub names: IdentInterner, pub strings: ctx_map::CtxMap, pub ins: TypeIns, pub tmp: TypesTmp, pub tasks: Vec>, } impl Types { pub fn case( &self, ty: Id, files: &EntSlice, ) -> fn(&str) -> Result<(), &'static str> { match ty.expand() { Kind::NEVER => |_| Ok(()), Kind::Enum(_) | Kind::Struct(_) | Kind::Union(_) | Kind::Builtin(_) | Kind::Ptr(_) | Kind::Slice(_) | Kind::Opt(_) => utils::is_pascal_case, Kind::Func(f) if let &Expr::Closure { ret: &Expr::Ident { id, .. }, .. } = self.ins.funcs[f].expr.get(&files[self.ins.funcs[f].file]) && id.is_type() => { utils::is_pascal_case } Kind::Template(f) if let &Expr::Closure { ret: &Expr::Ident { id, .. }, .. } = self.ins.templates[f].expr.get(&files[self.ins.templates[f].file]) && id.is_type() => { utils::is_pascal_case } Kind::Func(_) | Kind::Template(_) | Kind::Global(_) | Kind::Module(_) => { utils::is_snake_case } Kind::Const(_) => utils::is_screaming_case, } } pub fn pack_args(&mut self, arg_base: usize) -> Option { let base = self.ins.args.len(); self.ins.args.extend(self.tmp.args.drain(arg_base..)); let needle = &self.ins.args[base..]; if needle.is_empty() { return Some(Tuple::empty()); } let len = needle.len(); // FIXME: maybe later when this becomes a bottleneck we use more // efficient search (SIMD?, indexing?) let sp = self.ins.args.windows(needle.len()).position(|val| val == needle).unwrap(); self.ins.args.truncate((sp + needle.len()).max(base)); Tuple::new(sp, len) } pub fn union_fields(&self, union: Union) -> &[StructField] { &self.ins.struct_fields[self.union_field_range(union)] } fn union_field_range(&self, union: Union) -> Range { let start = self.ins.unions[union].field_start as usize; let end = self .ins .unions .next(union) .map_or(self.ins.struct_fields.len(), |s| s.field_start as usize); start..end } pub fn struct_fields(&self, strct: Struct) -> &[StructField] { &self.ins.struct_fields[self.struct_field_range(strct)] } fn struct_field_range(&self, strct: Struct) -> Range { let start = self.ins.structs[strct].field_start as usize; let end = self .ins .structs .next(strct) .map_or(self.ins.struct_fields.len(), |s| s.field_start as usize); start..end } pub fn enum_fields(&self, enm: Enum) -> &[EnumField] { &self.ins.enum_fields[self.enum_field_range(enm)] } pub fn enum_field_range(&self, enm: Enum) -> Range { let start = self.ins.enums[enm].field_start as usize; let end = self.ins.enums.next(enm).map_or(self.ins.enum_fields.len(), |s| s.field_start as usize); start..end } pub fn make_opt(&mut self, base: Id) -> Id { self.make_generic_ty(OptData { base }, |ins| &mut ins.opts, |e| SymKey::Optional(e)) } pub fn make_ptr(&mut self, base: Id) -> Id { self.make_generic_ty(PtrData { base }, |ins| &mut ins.ptrs, |e| SymKey::Pointer(e)) } pub fn make_array(&mut self, elem: Id, len: ArrayLen) -> Id { self.make_generic_ty(ArrayData { elem, len }, |ins| &mut ins.slices, |e| SymKey::Array(e)) } fn make_generic_ty, T: Copy>( &mut self, ty: T, get_col: fn(&mut TypeIns) -> &mut EntVec, key: fn(&T) -> SymKey, ) -> Id { *self.syms.get_or_insert(key(&{ ty }), &mut self.ins, |ins| get_col(ins).push(ty).into()) } pub fn size_of(&self, ty: Id) -> Size { match ty.expand() { Kind::Slice(arr) => { let arr = &self.ins.slices[arr]; match arr.len { 0 => 0, ArrayLen::MAX => 16, len => self.size_of(arr.elem) * len, } } Kind::Struct(stru) => { if self.ins.structs[stru].size.get() != 0 { return self.ins.structs[stru].size.get(); } let mut oiter = OffsetIter::new(stru, self); while oiter.next(self).is_some() {} self.ins.structs[stru].size.set(oiter.offset); oiter.offset } Kind::Union(union) => { if self.ins.unions[union].size.get() != 0 { return self.ins.unions[union].size.get(); } let size = self.union_fields(union).iter().map(|f| self.size_of(f.ty)).max().unwrap_or(0); self.ins.unions[union].size.set(size); size } Kind::Enum(enm) => (self.enum_field_range(enm).len().ilog2() + 7) / 8, Kind::Opt(opt) => { let base = self.ins.opts[opt].base; if self.nieche_of(base).is_some() { self.size_of(base) } else { self.size_of(base) + self.align_of(base) } } _ if let Some(size) = ty.simple_size() => size, ty => unimplemented!("size_of: {:?}", ty), } } pub fn align_of(&self, ty: Id) -> Size { match ty.expand() { Kind::Union(union) => { if self.ins.unions[union].align.get() != 0 { return self.ins.unions[union].align.get() as _; } let align = self.union_fields(union).iter().map(|f| self.align_of(f.ty)).max().unwrap_or(1); self.ins.unions[union].align.set(align.try_into().unwrap()); align } Kind::Struct(stru) => { if self.ins.structs[stru].align.get() != 0 { return self.ins.structs[stru].align.get() as _; } let align = self.ins.structs[stru].explicit_alignment.map_or_else( || { self.struct_fields(stru) .iter() .map(|&StructField { ty, .. }| self.align_of(ty)) .max() .unwrap_or(1) }, |a| a as _, ); self.ins.structs[stru].align.set(align.try_into().unwrap()); align } Kind::Slice(arr) => { let arr = &self.ins.slices[arr]; match arr.len { ArrayLen::MAX => 8, _ => self.align_of(arr.elem), } } _ => self.size_of(ty).max(1), } } pub fn base_of(&self, ty: Id) -> Option { match ty.expand() { Kind::Ptr(p) => Some(self.ins.ptrs[p].base), _ => None, } } pub fn inner_of(&self, ty: Id) -> Option { match ty.expand() { Kind::Opt(o) => Some(self.ins.opts[o].base), _ => None, } } pub fn opt_layout(&self, inner_ty: Id) -> OptLayout { match self.nieche_of(inner_ty) { Some((_, flag_offset, flag_ty)) => { OptLayout { flag_ty, flag_offset, payload_offset: 0 } } None => OptLayout { flag_ty: Id::BOOL, flag_offset: 0, payload_offset: self.align_of(inner_ty), }, } } pub fn nieche_of(&self, ty: Id) -> Option<(bool, Offset, Id)> { match ty.expand() { Kind::Ptr(_) => Some((false, 0, Id::UINT)), // TODO: cache this Kind::Struct(s) => OffsetIter::new(s, self).into_iter(self).find_map(|(f, off)| { self.nieche_of(f.ty).map(|(uninit, o, ty)| (uninit, o + off, ty)) }), _ => None, } } pub fn find_struct_field(&self, s: Struct, name: &str) -> Option { let name = self.names.project(name)?; self.struct_fields(s).iter().position(|f| f.name == name) } pub fn find_union_field(&self, u: Union, name: &str) -> Option<(usize, &StructField)> { let name = self.names.project(name)?; self.union_fields(u).iter().enumerate().find(|(_, f)| f.name == name) } pub fn clear(&mut self) { self.syms.clear(); self.names.clear(); self.strings.clear(); self.ins.funcs.clear(); self.ins.args.clear(); self.ins.globals.clear(); self.ins.structs.clear(); self.ins.struct_fields.clear(); self.ins.ptrs.clear(); self.ins.slices.clear(); debug_assert_eq!(self.tmp.struct_fields.len(), 0); debug_assert_eq!(self.tmp.args.len(), 0); debug_assert_eq!(self.tasks.len(), 0); } pub fn type_base_of(&self, id: Id) -> Option<&TypeBase> { Some(match id.expand() { Kind::Struct(s) => &*self.ins.structs[s], Kind::Enum(e) => &*self.ins.enums[e], Kind::Union(e) => &*self.ins.unions[e], Kind::Builtin(_) | Kind::Ptr(_) | Kind::Slice(_) | Kind::Opt(_) | Kind::Func(_) | Kind::Template(_) | Kind::Global(_) | Kind::Module(_) | Kind::Const(_) => return None, }) } pub fn scope_of<'a>(&self, parent: Id, file: &'a parser::Ast) -> Option<&'a [Expr<'a>]> { let base = match parent.expand() { _ if let Some(base) = self.type_base_of(parent) => base, Kind::Module(_) => return Some(file.exprs()), _ => return None, }; if let Expr::Struct { fields: [.., CommentOr::Or(Err(scope))], .. } | Expr::Union { fields: [.., CommentOr::Or(Err(scope))], .. } | Expr::Enum { variants: [.., CommentOr::Or(Err(scope))], .. } = base.ast.get(file) { Some(scope) } else { Some(&[]) } } pub fn parent_of(&self, ty: Id) -> Option { self.type_base_of(ty).map(|b| b.parent) } pub fn captures_of<'a>(&self, ty: Id, file: &'a parser::Ast) -> Option<(&'a [Ident], Tuple)> { let base = self.type_base_of(ty)?; let (Expr::Struct { captured, .. } | Expr::Enum { captured, .. } | Expr::Union { captured, .. }) = *base.ast.get(file) else { unreachable!() }; debug_assert_eq!(captured.len(), base.captured.len()); Some((captured, base.captured)) } pub fn len_of(&self, ty: Id) -> Option { Some(match ty.expand() { Kind::Struct(s) => self.struct_field_range(s).len() as _, Kind::Slice(s) => self.ins.slices[s].len()? as _, _ => return None, }) } } pub struct OptLayout { pub flag_ty: Id, pub flag_offset: Offset, pub payload_offset: Offset, } pub struct OffsetIter { strct: Struct, offset: Offset, fields: Range, } impl OffsetIter { pub fn new(strct: Struct, tys: &Types) -> Self { Self { strct, offset: 0, fields: tys.struct_field_range(strct) } } pub fn offset_of(tys: &Types, idx: Struct, field: &str) -> Option<(Offset, Id)> { let field_id = tys.names.project(field)?; OffsetIter::new(idx, tys) .into_iter(tys) .find(|(f, _)| f.name == field_id) .map(|(f, off)| (off, f.ty)) } fn next<'a>(&mut self, tys: &'a Types) -> Option<(&'a StructField, Offset)> { let stru = &tys.ins.structs[self.strct]; let field = &tys.ins.struct_fields[self.fields.next()?]; let align = stru.explicit_alignment.map_or_else(|| tys.align_of(field.ty), |a| a as u32); self.offset = (self.offset + align - 1) & !(align - 1); let off = self.offset; self.offset += tys.size_of(field.ty); Some((field, off)) } pub fn next_ty(&mut self, tys: &Types) -> Option<(Id, Offset)> { let (field, off) = self.next(tys)?; Some((field.ty, off)) } pub fn into_iter(mut self, tys: &Types) -> impl Iterator { core::iter::from_fn(move || self.next(tys)) } }