Compare commits

..

2 Commits

Author SHA1 Message Date
Laurenz
19559466e5 Miri test 2025-07-08 10:49:13 +02:00
Laurenz
d12cad6e46 New Content implementation 2025-07-08 10:49:13 +02:00
2 changed files with 40 additions and 48 deletions

View File

@ -47,11 +47,11 @@ pub struct RawContent {
/// The allocated part of an element's representation. /// The allocated part of an element's representation.
/// ///
/// This is `repr(C)` to ensure that a pointer to the whole structure may be /// This is `repr(C)` to ensure that a pointer to the whole structure may be
/// casted to a pointer to its first field. /// cast to a pointer to its first field.
#[repr(C)] #[repr(C)]
struct Inner<E> { struct Inner<E> {
/// It is crucial that this is the first field because we cast between /// It is crucial that this is the first field because we cast between
/// pointer to `Inner<E>` and pointers to `Header`. See the documentation /// pointers to `Inner<E>` and pointers to `Header`. See the documentation
/// of `RawContent::ptr` for more details. /// of `RawContent::ptr` for more details.
header: Header, header: Header,
/// The element struct. E.g. `E = HeadingElem`. /// The element struct. E.g. `E = HeadingElem`.
@ -60,12 +60,12 @@ struct Inner<E> {
/// The header that is shared by all elements. /// The header that is shared by all elements.
struct Header { struct Header {
/// The element's reference count. This work just like for `Arc`. /// The element's reference count. This works just like for `Arc`.
/// Unfortunately, we implement a custom fat pointer and `Arc` wouldn't know /// Unfortunately, we have to reimplement reference counting because we
/// how to drop its contents. Something with `ManuallyDrop<Arc<_>>` might /// have a custom fat pointer and `Arc` wouldn't know how to drop its
/// also work, but at that point we're not gaining much and with the way /// contents. Something with `ManuallyDrop<Arc<_>>` might also work, but at
/// it's implemented now we can also skip the unnecessary weak reference /// that point we're not gaining much and with the way it's implemented now
/// count. /// we can also skip the unnecessary weak reference count.
refs: AtomicUsize, refs: AtomicUsize,
/// Metadata for the element. /// Metadata for the element.
meta: Meta, meta: Meta,
@ -78,7 +78,7 @@ struct Header {
pub(super) struct Meta { pub(super) struct Meta {
/// An optional label attached to the element. /// An optional label attached to the element.
pub label: Option<Label>, pub label: Option<Label>,
/// The element's location which identifies it in the layouted output. /// The element's location which identifies it in the laid-out output.
pub location: Option<Location>, pub location: Option<Location>,
/// Manages the element during realization. /// Manages the element during realization.
/// - If bit 0 is set, the element is prepared. /// - If bit 0 is set, the element is prepared.
@ -88,8 +88,8 @@ pub(super) struct Meta {
} }
impl RawContent { impl RawContent {
/// Create raw content wrapped an element, with all metadata set to its /// Creates raw content wrapping an element, with all metadata set to
/// defaults (including a detached span). /// default (including a detached span).
pub(super) fn new<E: NativeElement>(data: E) -> Self { pub(super) fn new<E: NativeElement>(data: E) -> Self {
Self::create( Self::create(
data, data,
@ -103,7 +103,7 @@ impl RawContent {
) )
} }
/// Creates raw content and allocates. /// Creates and allocates raw content.
fn create<E: NativeElement>(data: E, meta: Meta, hash: HashLock, span: Span) -> Self { fn create<E: NativeElement>(data: E, meta: Meta, hash: HashLock, span: Span) -> Self {
let raw = Box::into_raw(Box::<Inner<E>>::new(Inner { let raw = Box::into_raw(Box::<Inner<E>>::new(Inner {
header: Header { refs: AtomicUsize::new(1), meta, hash }, header: Header { refs: AtomicUsize::new(1), meta, hash },
@ -177,7 +177,7 @@ impl RawContent {
debug_assert!(self.is::<E>()); debug_assert!(self.is::<E>());
// Safety: // Safety:
// - The caller guarantees that the content is of type `E` // - The caller guarantees that the content is of type `E`.
// - `self.ptr` is a valid pointer to an `Inner<E>` (see // - `self.ptr` is a valid pointer to an `Inner<E>` (see
// `RawContent::ptr`). // `RawContent::ptr`).
unsafe { &self.ptr.cast::<Inner<E>>().as_ref().data } unsafe { &self.ptr.cast::<Inner<E>>().as_ref().data }
@ -199,7 +199,7 @@ impl RawContent {
self.header_mut().hash.reset(); self.header_mut().hash.reset();
// Safety: // Safety:
// - The caller guarantees that the content is of type `E` // - The caller guarantees that the content is of type `E`.
// - `self.ptr` is a valid pointer to an `Inner<E>` (see // - `self.ptr` is a valid pointer to an `Inner<E>` (see
// `RawContent::ptr`). // `RawContent::ptr`).
// - We have unique access to the backing allocation (due to header_mut). // - We have unique access to the backing allocation (due to header_mut).
@ -245,36 +245,36 @@ impl RawContent {
&mut self.header_mut().meta &mut self.header_mut().meta
} }
/// Casts into a trait object for a given trait if the packed elements /// Casts into a trait object for a given trait if the packed element
/// implements said trait. /// implements said trait.
pub(super) fn with<C>(&self) -> Option<&C> pub(super) fn with<C>(&self) -> Option<&C>
where where
C: ?Sized + 'static, C: ?Sized + 'static,
{ {
// Safety: The vtable comes from the `Capable` implementation which // Safety: The vtable comes from the `Capable` implementation which
// guarantees to return a matching vtable for `Packed<T>` and `C`. // guarantees to return a matching vtable for `Packed<T>` and `C`. Since
// Since any `Packed<T>` is a repr(transparent) `Content`, we can also // any `Packed<T>` is repr(transparent) with `Content` and `RawContent`,
// use a `*const Content` pointer. // we can also use a `*const RawContent` pointer.
let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?; let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?;
let data = self as *const Self as *const (); let data = self as *const Self as *const ();
Some(unsafe { &*fat::from_raw_parts(data, vtable.as_ptr()) }) Some(unsafe { &*fat::from_raw_parts(data, vtable.as_ptr()) })
} }
/// Casts into a mutable trait object for a given trait if the packed elements /// Casts into a mutable trait object for a given trait if the packed
/// implements said trait. /// element implements said trait.
pub(super) fn with_mut<C>(&mut self) -> Option<&mut C> pub(super) fn with_mut<C>(&mut self) -> Option<&mut C>
where where
C: ?Sized + 'static, C: ?Sized + 'static,
{ {
// Safety: The vtable comes from the `Capable` implementation which // Safety: The vtable comes from the `Capable` implementation which
// guarantees to return a matching vtable for `Packed<T>` and `C`. // guarantees to return a matching vtable for `Packed<T>` and `C`. Since
// Since any `Packed<T>` is a repr(transparent) `Content`, we can also // any `Packed<T>` is repr(transparent) with `Content` and `RawContent`,
// use a `*const Content` pointer. // we can also use a `*const Content` pointer.
// //
// The resulting trait object contains an `&mut Packed<T>`. We do _not_ // The resulting trait object contains an `&mut Packed<T>`. We do _not_
// need to ensure that we hold the only reference to the `Arc` here // need to ensure that we hold the only reference to the `Arc` here
// because `Packed<T>`'s DerefMut impl will take care of that if // because `Packed<T>`'s DerefMut impl will take care of that if mutable
// mutable access is required. // access is required.
let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?; let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?;
let data = self as *mut Self as *mut (); let data = self as *mut Self as *mut ();
Some(unsafe { &mut *fat::from_raw_parts_mut(data, vtable.as_ptr()) }) Some(unsafe { &mut *fat::from_raw_parts_mut(data, vtable.as_ptr()) })

View File

@ -17,27 +17,19 @@
//! Note that all vtable methods receive elements of type `Packed<E>`, but some //! Note that all vtable methods receive elements of type `Packed<E>`, but some
//! only perform actions on the `E` itself, with the shared part kept outside of //! only perform actions on the `E` itself, with the shared part kept outside of
//! the vtable (e.g. `hash`), while some perform the full action (e.g. `clone` //! the vtable (e.g. `hash`), while some perform the full action (e.g. `clone`
//! function as it needs to return new, fully populated raw content). Which one //! as it needs to return new, fully populated raw content). Which one it is, is
//! it is, is documented for each. //! documented for each.
//! //!
//! # Safety //! # Safety
//! All function pointers that operate on a specific element type are marked as
//! unsafe. In combination with `repr(C)`, this grants us the ability to safely
//! transmute a `ContentVtable<Packed<E>>` into a `ContentVtable<RawContent>`
//! (or just short `ContentVtable`). Callers of functions marked as unsafe have
//! to guarantee that the `ContentVtable` was transmuted from the same `E` as
//! the RawContent was constructed from.
//!
//! This module contains a lot of `unsafe` keywords, but almost all of it is the //! This module contains a lot of `unsafe` keywords, but almost all of it is the
//! same and quite straightfoward. All function pointers that operate on a //! same and quite straightfoward. All function pointers that operate on a
//! specific element type are marked as unsafe. In combination with `repr(C)`, //! specific element type are marked as unsafe. In combination with `repr(C)`,
//! this grants us the ability to safely transmute a `ContentVtable<Packed<E>>` //! this grants us the ability to safely transmute a `ContentVtable<Packed<E>>`
//! into a `ContentVtable<RawContent>` (or just short `ContentVtable`). //! into a `ContentVtable<RawContent>` (or just short `ContentVtable`). Callers
//! //! of functions marked as unsafe have to guarantee that the `ContentVtable` was
//! Callers of functions marked as unsafe have to guarantee that the //! transmuted from the same `E` as the RawContent was constructed from. The
//! `ContentVtable` was transmuted from the same `E` as the RawContent was //! `Handle` struct provides a safe access layer, moving the guarantee that the
//! constructed from. The `Handle` struct provides a safe access layer, moving //! vtable is matching into a single spot.
//! the guarantee that the vtable is matching into a single spot.
//! //!
//! [vtable]: https://en.wikipedia.org/wiki/Virtual_method_table //! [vtable]: https://en.wikipedia.org/wiki/Virtual_method_table
@ -97,7 +89,7 @@ pub struct ContentVtable<T: 'static = RawContent> {
/// Subvtables for all fields of the element. /// Subvtables for all fields of the element.
pub(super) fields: &'static [FieldVtable<T>], pub(super) fields: &'static [FieldVtable<T>],
/// Determine's the ID for a field name. This is a separate function instead /// Determines the ID for a field name. This is a separate function instead
/// of searching through `fields` so that Rust can generate optimized code /// of searching through `fields` so that Rust can generate optimized code
/// for the string matching. /// for the string matching.
pub(super) field_id: fn(name: &str) -> Option<u8>, pub(super) field_id: fn(name: &str) -> Option<u8>,
@ -106,7 +98,7 @@ pub struct ContentVtable<T: 'static = RawContent> {
pub(super) construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>, pub(super) construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>,
/// The set rule of the element. /// The set rule of the element.
pub(super) set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>, pub(super) set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>,
/// The element's local name in a specific lang-region pairing. /// The element's local name in a specific lang-region pairing.
pub(super) local_name: Option<fn(Lang, Option<Region>) -> &'static str>, pub(super) local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
/// Produces the associated [`Scope`] of the element. /// Produces the associated [`Scope`] of the element.
pub(super) scope: fn() -> Scope, pub(super) scope: fn() -> Scope,
@ -226,7 +218,7 @@ impl<E: NativeElement> ContentVtable<Packed<E>> {
/// Type-erases the data. /// Type-erases the data.
pub const fn erase(self) -> ContentVtable { pub const fn erase(self) -> ContentVtable {
// Safety: // Safety:
// - `ContentVtable` is `repr(C)` // - `ContentVtable` is `repr(C)`.
// - `ContentVtable` does not hold any `E`-specific data except for // - `ContentVtable` does not hold any `E`-specific data except for
// function pointers. // function pointers.
// - All functions pointers have the same memory layout. // - All functions pointers have the same memory layout.
@ -265,13 +257,13 @@ impl<T> ContentHandle<T> {
} }
impl ContentHandle<&RawContent> { impl ContentHandle<&RawContent> {
/// See [`ContentVtable::has`]. /// See [`ContentVtable::debug`].
pub fn debug(&self, f: &mut Formatter) -> fmt::Result { pub fn debug(&self, f: &mut Formatter) -> fmt::Result {
// Safety: `Handle` has the invariant that the vtable is matching. // Safety: `Handle` has the invariant that the vtable is matching.
unsafe { (self.1.debug)(self.0, f) } unsafe { (self.1.debug)(self.0, f) }
} }
/// See [`ContentVtable::has`]. /// See [`ContentVtable::repr`].
pub fn repr(&self) -> Option<EcoString> { pub fn repr(&self) -> Option<EcoString> {
// Safety: `Handle` has the invariant that the vtable is matching. // Safety: `Handle` has the invariant that the vtable is matching.
unsafe { self.1.repr.map(|f| f(self.0)) } unsafe { self.1.repr.map(|f| f(self.0)) }
@ -330,8 +322,8 @@ pub struct FieldVtable<T: 'static = RawContent> {
pub(super) synthesized: bool, pub(super) synthesized: bool,
/// Reflects what types the field's parameter accepts. /// Reflects what types the field's parameter accepts.
pub(super) input: fn() -> CastInfo, pub(super) input: fn() -> CastInfo,
/// The default value of the field, if any. This would e.g. be `None` for /// Produces the default value of the field, if any. This would e.g. be
/// a required parameter. /// `None` for a required parameter.
pub(super) default: Option<fn() -> Value>, pub(super) default: Option<fn() -> Value>,
/// Whether the field is set on the given element. Always true for required /// Whether the field is set on the given element. Always true for required
@ -341,7 +333,7 @@ pub struct FieldVtable<T: 'static = RawContent> {
/// value](crate::foundations::IntoValue). /// value](crate::foundations::IntoValue).
pub(super) get: unsafe fn(elem: &T) -> Option<Value>, pub(super) get: unsafe fn(elem: &T) -> Option<Value>,
/// Retrieves the field given styles. The resulting value may come from the /// Retrieves the field given styles. The resulting value may come from the
/// element, they style chain, or a mix (if it's a /// element, the style chain, or a mix (if it's a
/// [`Fold`](crate::foundations::Fold) field). /// [`Fold`](crate::foundations::Fold) field).
pub(super) get_with_styles: unsafe fn(elem: &T, StyleChain) -> Option<Value>, pub(super) get_with_styles: unsafe fn(elem: &T, StyleChain) -> Option<Value>,
/// Retrieves the field just from the styles. /// Retrieves the field just from the styles.