holey-bytes/lang/src/ty.rs
Jakub Doka f7d5bccdd9
fixing @itf type inference
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 13:49:20 +01:00

1236 lines
35 KiB
Rust

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<Self> {
if len >= Self::MAX_LEN {
return None;
}
Some(Self((pos << Self::LEN_BITS | len) as u32))
}
pub fn range(self) -> Range<usize> {
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<usize>);
pub enum Arg {
Type(Id),
Value(Id),
}
impl ArgIter {
pub(crate) fn next(&mut self, tys: &Types) -> Option<Arg> {
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<Id> {
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<Id> 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> {
self.try_upcast_low(ob, false)
}
pub fn try_upcast_low(self, ob: Self, coerce_pointer: bool) -> Option<Self> {
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<Size> {
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<u64> for Id {
fn from(id: u64) -> Self {
Self(unsafe { NonZeroU32::new_unchecked(id as _) })
}
}
const fn array_to_lower_case<const N: usize>(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<Builtin> {
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::<Id>() 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<Ident> for Builtin {
type Error = ();
fn try_from(value: Ident) -> Result<Self, Self::Error> {
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<Module, parser::Ast>,
ty: Id,
}
impl<'a> Display<'a> {
pub fn new(tys: &'a Types, files: &'a EntSlice<Module, parser::Ast>, 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<PackedCompState> 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<CompState> 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<u8>,
}
#[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<Size>,
pub align: Cell<u8>,
}
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<Size>,
pub align: Cell<u8>,
// TODO: make this compact
pub explicit_alignment: Option<u8>,
}
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<usize> {
(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<Ident>,
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<Ident> {
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<StructField>,
pub enum_fields: Vec<EnumField>,
pub args: Vec<Id>,
}
#[derive(Default)]
pub struct TypeIns {
pub args: Vec<Id>,
pub struct_fields: Vec<StructField>,
pub enum_fields: Vec<EnumField>,
pub funcs: EntVec<Func, FuncData>,
pub templates: EntVec<Template, TemplateData>,
pub globals: EntVec<Global, GlobalData>,
pub consts: EntVec<Const, ConstData>,
pub structs: EntVec<Struct, StructData>,
pub enums: EntVec<Enum, EnumData>,
pub unions: EntVec<Union, UnionData>,
pub ptrs: EntVec<Ptr, PtrData>,
pub opts: EntVec<Opt, OptData>,
pub slices: EntVec<Slice, ArrayData>,
}
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<Global, GlobalData>;
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<Id>,
pub names: IdentInterner,
pub strings: ctx_map::CtxMap<StringRef>,
pub ins: TypeIns,
pub tmp: TypesTmp,
pub tasks: Vec<Option<FTask>>,
}
impl Types {
pub fn case(
&self,
ty: Id,
files: &EntSlice<Module, parser::Ast>,
) -> 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<Tuple> {
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<usize> {
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<usize> {
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<usize> {
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<K: Ent + Into<Id>, T: Copy>(
&mut self,
ty: T,
get_col: fn(&mut TypeIns) -> &mut EntVec<K, T>,
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<Id> {
match ty.expand() {
Kind::Ptr(p) => Some(self.ins.ptrs[p].base),
_ => None,
}
}
pub fn inner_of(&self, ty: Id) -> Option<Id> {
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<usize> {
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<Id> {
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<u32> {
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<usize>,
}
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<Item = (&StructField, Offset)> {
core::iter::from_fn(move || self.next(tys))
}
}