stabby_abi/
report.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   Pierre Avital, <pierre.avital@me.com>
13//
14
15use crate::{str::Str, StableLike};
16use sha2_const_stable::Sha256;
17
18/// A type
19type NextField = StableLike<Option<&'static FieldReport>, usize>;
20
21/// A report of a type's layout.
22#[crate::stabby]
23#[derive(Debug, PartialEq, Eq, Clone, Copy)]
24pub struct TypeReport {
25    /// The type's name.
26    pub name: Str<'static>,
27    /// The type's parent module's path.
28    pub module: Str<'static>,
29    /// The fields of this type.
30    pub fields: NextField,
31    /// How the type was declared
32    pub tyty: TyTy,
33    /// The version of the type's invariants.
34    pub version: u32,
35}
36
37impl core::fmt::Display for TypeReport {
38    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        let Self {
40            name,
41            module,
42            version,
43            tyty,
44            ..
45        } = self;
46        write!(f, "{tyty:?} {module} :: {name} (version{version}) {{")?;
47        for FieldReport { name, ty, .. } in self.fields() {
48            write!(f, "{name}: {ty}, ")?
49        }
50        write!(f, "}}")
51    }
52}
53impl core::hash::Hash for TypeReport {
54    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
55        self.name.hash(state);
56        self.module.hash(state);
57        for field in self.fields() {
58            field.hash(state);
59        }
60        self.version.hash(state);
61        self.tyty.hash(state);
62    }
63}
64
65impl TypeReport {
66    /// Whether or not two reports correspond to the same type, with the same layout and invariants.
67    pub fn is_compatible(&self, other: &Self) -> bool {
68        self.name == other.name
69            && self.module == other.module
70            && self.version == other.version
71            && self.tyty == other.tyty
72            && self
73                .fields()
74                .zip(other.fields())
75                .all(|(s, o)| s.name == o.name && s.ty.is_compatible(o.ty))
76    }
77}
78
79/// How a type was declared.
80#[crate::stabby]
81#[repr(u8)]
82#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
83pub enum TyTy {
84    /// As a struct
85    Struct,
86    /// As an enum (with which calling convention)
87    Enum(Str<'static>),
88    /// As a union.
89    Union,
90}
91
92/// A type's field.
93#[crate::stabby]
94#[derive(Debug, PartialEq, Eq, Clone, Copy)]
95pub struct FieldReport {
96    /// The field's name.
97    pub name: Str<'static>,
98    /// The field;s type.
99    pub ty: &'static TypeReport,
100    /// The next field in the [`TypeReport`]
101    pub next_field: NextField,
102}
103impl core::hash::Hash for FieldReport {
104    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
105        self.name.hash(state);
106        self.ty.hash(state);
107    }
108}
109
110impl TypeReport {
111    /// Returns an iterator over the type's fields.
112    pub const fn fields(&self) -> Fields {
113        Fields(self.fields.value)
114    }
115}
116/// An iterator over a type's fields.
117#[crate::stabby]
118pub struct Fields(Option<&'static FieldReport>);
119impl Fields {
120    /// A `const` compatible alternative to [`Iterator::next`]
121    pub const fn next_const(self) -> (Self, Option<&'static FieldReport>) {
122        match self.0 {
123            Some(field) => (Self(*field.next_field.as_ref()), Some(field)),
124            None => (self, None),
125        }
126    }
127}
128impl Iterator for Fields {
129    type Item = &'static FieldReport;
130    fn next(&mut self) -> Option<Self::Item> {
131        let field = self.0.take()?;
132        self.0 = field.next_field.value;
133        Some(field)
134    }
135}
136
137const fn hash_report(mut hash: Sha256, report: &TypeReport) -> Sha256 {
138    hash = hash
139        .update(report.module.as_str().as_bytes())
140        .update(report.name.as_str().as_bytes());
141    hash = match report.tyty {
142        crate::report::TyTy::Struct => hash.update(&[0]),
143        crate::report::TyTy::Union => hash.update(&[1]),
144        crate::report::TyTy::Enum(s) => hash.update(s.as_str().as_bytes()),
145    };
146    let mut fields = report.fields();
147    while let (new, Some(next)) = fields.next_const() {
148        fields = new;
149        hash = hash_report(hash.update(next.name.as_str().as_bytes()), next.ty)
150    }
151    hash
152}
153
154#[rustversion::attr(nightly, allow(unnecessary_transmutes))]
155const ARCH_INFO: [u8; 8] = [
156    0,
157    core::mem::size_of::<usize>() as u8,
158    core::mem::align_of::<usize>() as u8,
159    core::mem::size_of::<&()>() as u8,
160    core::mem::align_of::<&()>() as u8,
161    core::mem::align_of::<u128>() as u8,
162    // SAFETY: allows us to observe the architecture's endianness
163    unsafe { core::mem::transmute::<[u8; 2], u16>([0, 1]) } as u8,
164    0,
165];
166
167/// Generates an ID based on a [`TypeReport`] using [`Sha256`].
168///
169/// The hash also contains information about the architecture, allowing to consider a same struct distinct depending
170/// on the architecture's pointer size, alignment of u128, or endianness.
171pub const fn gen_id(report: &TypeReport) -> u64 {
172    let [hash @ .., _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _] =
173        hash_report(Sha256::new(), report).finalize();
174    u64::from_le_bytes(hash) ^ u64::from_le_bytes(ARCH_INFO)
175}