Share allocations for singletons (#4794)

This commit is contained in:
Laurenz 2024-08-20 12:56:54 +02:00 committed by GitHub
parent a30b681251
commit 986d624b3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 92 additions and 47 deletions

View File

@ -1,15 +1,16 @@
use std::io::{self, IsTerminal, Write}; use std::io::{self, IsTerminal, Write};
use codespan_reporting::term::termcolor; use codespan_reporting::term::termcolor;
use once_cell::sync::Lazy;
use termcolor::{ColorChoice, WriteColor}; use termcolor::{ColorChoice, WriteColor};
use typst::utils::singleton;
use crate::ARGS; use crate::ARGS;
/// Returns a handle to the optionally colored terminal output. /// Returns a handle to the optionally colored terminal output.
pub fn out() -> TermOut { pub fn out() -> TermOut {
static OUTPUT: Lazy<TermOutInner> = Lazy::new(TermOutInner::new); TermOut {
TermOut { inner: &OUTPUT } inner: singleton!(TermOutInner, TermOutInner::new()),
}
} }
/// The stuff that has to be shared between instances of [`TermOut`]. /// The stuff that has to be shared between instances of [`TermOut`].

View File

@ -97,13 +97,12 @@ fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -> Ec
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use once_cell::sync::Lazy;
use typst::diag::{FileError, FileResult}; use typst::diag::{FileError, FileResult};
use typst::foundations::{Bytes, Datetime, Smart}; use typst::foundations::{Bytes, Datetime, Smart};
use typst::layout::{Abs, Margin, PageElem}; use typst::layout::{Abs, Margin, PageElem};
use typst::syntax::{FileId, Source}; use typst::syntax::{FileId, Source};
use typst::text::{Font, FontBook, TextElem, TextSize}; use typst::text::{Font, FontBook, TextElem, TextSize};
use typst::utils::LazyHash; use typst::utils::{singleton, LazyHash};
use typst::{Library, World}; use typst::{Library, World};
/// A world for IDE testing. /// A world for IDE testing.
@ -118,15 +117,16 @@ mod tests {
/// This is cheap because the shared base for all test runs is lazily /// This is cheap because the shared base for all test runs is lazily
/// initialized just once. /// initialized just once.
pub fn new(text: &str) -> Self { pub fn new(text: &str) -> Self {
static BASE: Lazy<TestBase> = Lazy::new(TestBase::default);
let main = Source::detached(text); let main = Source::detached(text);
Self { main, base: &*BASE } Self {
main,
base: singleton!(TestBase, TestBase::default()),
}
} }
/// The ID of the main file in a `TestWorld`. /// The ID of the main file in a `TestWorld`.
pub fn main_id() -> FileId { pub fn main_id() -> FileId {
static ID: Lazy<FileId> = Lazy::new(|| Source::detached("").id()); *singleton!(FileId, Source::detached("").id())
*ID
} }
} }

View File

@ -25,6 +25,9 @@ use std::sync::Arc;
use siphasher::sip128::{Hasher128, SipHasher13}; use siphasher::sip128::{Hasher128, SipHasher13};
#[doc(hidden)]
pub use once_cell;
/// Turn a closure into a struct implementing [`Debug`]. /// Turn a closure into a struct implementing [`Debug`].
pub fn debug<F>(f: F) -> impl Debug pub fn debug<F>(f: F) -> impl Debug
where where

View File

@ -1,8 +1,18 @@
/// Create a lazy initialized, globally unique `'static` reference to a value.
#[macro_export]
macro_rules! singleton {
($ty:ty, $value:expr) => {{
static VALUE: $crate::once_cell::sync::Lazy<$ty> =
$crate::once_cell::sync::Lazy::new(|| $value);
&*VALUE
}};
}
/// Implement the `Sub` trait based on existing `Neg` and `Add` impls. /// Implement the `Sub` trait based on existing `Neg` and `Add` impls.
#[macro_export] #[macro_export]
macro_rules! sub_impl { macro_rules! sub_impl {
($a:ident - $b:ident -> $c:ident) => { ($a:ident - $b:ident -> $c:ident) => {
impl std::ops::Sub<$b> for $a { impl ::core::ops::Sub<$b> for $a {
type Output = $c; type Output = $c;
fn sub(self, other: $b) -> $c { fn sub(self, other: $b) -> $c {
@ -16,7 +26,7 @@ macro_rules! sub_impl {
#[macro_export] #[macro_export]
macro_rules! assign_impl { macro_rules! assign_impl {
($a:ident += $b:ident) => { ($a:ident += $b:ident) => {
impl std::ops::AddAssign<$b> for $a { impl ::core::ops::AddAssign<$b> for $a {
fn add_assign(&mut self, other: $b) { fn add_assign(&mut self, other: $b) {
*self = *self + other; *self = *self + other;
} }
@ -24,7 +34,7 @@ macro_rules! assign_impl {
}; };
($a:ident -= $b:ident) => { ($a:ident -= $b:ident) => {
impl std::ops::SubAssign<$b> for $a { impl ::core::ops::SubAssign<$b> for $a {
fn sub_assign(&mut self, other: $b) { fn sub_assign(&mut self, other: $b) {
*self = *self - other; *self = *self - other;
} }
@ -32,7 +42,7 @@ macro_rules! assign_impl {
}; };
($a:ident *= $b:ident) => { ($a:ident *= $b:ident) => {
impl std::ops::MulAssign<$b> for $a { impl ::core::ops::MulAssign<$b> for $a {
fn mul_assign(&mut self, other: $b) { fn mul_assign(&mut self, other: $b) {
*self = *self * other; *self = *self * other;
} }
@ -40,7 +50,7 @@ macro_rules! assign_impl {
}; };
($a:ident /= $b:ident) => { ($a:ident /= $b:ident) => {
impl std::ops::DivAssign<$b> for $a { impl ::core::ops::DivAssign<$b> for $a {
fn div_assign(&mut self, other: $b) { fn div_assign(&mut self, other: $b) {
*self = *self / other; *self = *self / other;
} }

View File

@ -83,7 +83,7 @@ impl Eval for ast::Space<'_> {
type Output = Content; type Output = Content;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> { fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(SpaceElem::new().pack()) Ok(SpaceElem::shared().clone())
} }
} }
@ -91,7 +91,7 @@ impl Eval for ast::Linebreak<'_> {
type Output = Content; type Output = Content;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> { fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(LinebreakElem::new().pack()) Ok(LinebreakElem::shared().clone())
} }
} }
@ -99,7 +99,7 @@ impl Eval for ast::Parbreak<'_> {
type Output = Content; type Output = Content;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> { fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(ParbreakElem::new().pack()) Ok(ParbreakElem::shared().clone())
} }
} }

View File

@ -39,7 +39,7 @@ impl Eval for ast::MathAlignPoint<'_> {
type Output = Content; type Output = Content;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> { fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(AlignPointElem::new().pack()) Ok(AlignPointElem::shared().clone())
} }
} }

View File

@ -12,7 +12,7 @@ use crate::foundations::{
Selector, Type, Value, Selector, Type, Value,
}; };
use crate::syntax::{ast, Span, SyntaxNode}; use crate::syntax::{ast, Span, SyntaxNode};
use crate::utils::{LazyHash, Static}; use crate::utils::{singleton, LazyHash, Static};
#[doc(inline)] #[doc(inline)]
pub use typst_macros::func; pub use typst_macros::func;
@ -216,11 +216,11 @@ impl Func {
/// Get details about the function's return type. /// Get details about the function's return type.
pub fn returns(&self) -> Option<&'static CastInfo> { pub fn returns(&self) -> Option<&'static CastInfo> {
static CONTENT: Lazy<CastInfo> =
Lazy::new(|| CastInfo::Type(Type::of::<Content>()));
match &self.repr { match &self.repr {
Repr::Native(native) => Some(&native.0.returns), Repr::Native(native) => Some(&native.0.returns),
Repr::Element(_) => Some(&CONTENT), Repr::Element(_) => {
Some(singleton!(CastInfo, CastInfo::Type(Type::of::<Content>())))
}
Repr::Closure(_) => None, Repr::Closure(_) => None,
Repr::With(with) => with.0.returns(), Repr::With(with) => with.0.returns(),
} }

View File

@ -1,12 +1,20 @@
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::foundations::{elem, Packed, StyleChain}; use crate::foundations::{elem, Content, NativeElement, Packed, StyleChain};
use crate::layout::Abs; use crate::layout::Abs;
use crate::math::{LayoutMath, MathContext, MathFragment, MathRun}; use crate::math::{LayoutMath, MathContext, MathFragment, MathRun};
use crate::utils::singleton;
/// A math alignment point: `&`, `&&`. /// A math alignment point: `&`, `&&`.
#[elem(title = "Alignment Point", LayoutMath)] #[elem(title = "Alignment Point", LayoutMath)]
pub struct AlignPointElem {} pub struct AlignPointElem {}
impl AlignPointElem {
/// Get the globally shared alignment point element.
pub fn shared() -> &'static Content {
singleton!(Content, AlignPointElem::new().pack())
}
}
impl LayoutMath for Packed<AlignPointElem> { impl LayoutMath for Packed<AlignPointElem> {
fn layout_math(&self, ctx: &mut MathContext, _: StyleChain) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, _: StyleChain) -> SourceResult<()> {
ctx.push(MathFragment::Align); ctx.push(MathFragment::Align);

View File

@ -331,7 +331,7 @@ impl Outlinable for Packed<HeadingElem> {
styles, styles,
numbering, numbering,
)?; )?;
content = numbers + SpaceElem::new().pack() + content; content = numbers + SpaceElem::shared().clone() + content;
}; };
Ok(Some(content)) Ok(Some(content))

View File

@ -190,7 +190,7 @@ impl OutlineElem {
impl Show for Packed<OutlineElem> { impl Show for Packed<OutlineElem> {
#[typst_macros::time(name = "outline", span = self.span())] #[typst_macros::time(name = "outline", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let mut seq = vec![ParbreakElem::new().pack()]; let mut seq = vec![ParbreakElem::shared().clone()];
// Build the outline title. // Build the outline title.
if let Some(title) = self.title(styles).unwrap_or_else(|| { if let Some(title) = self.title(styles).unwrap_or_else(|| {
Some(TextElem::packed(Self::local_name_in(styles)).spanned(self.span())) Some(TextElem::packed(Self::local_name_in(styles)).spanned(self.span()))
@ -247,12 +247,12 @@ impl Show for Packed<OutlineElem> {
// Add the overridable outline entry, followed by a line break. // Add the overridable outline entry, followed by a line break.
seq.push(entry.pack()); seq.push(entry.pack());
seq.push(LinebreakElem::new().pack()); seq.push(LinebreakElem::shared().clone());
ancestors.push(elem); ancestors.push(elem);
} }
seq.push(ParbreakElem::new().pack()); seq.push(ParbreakElem::shared().clone());
Ok(Content::sequence(seq)) Ok(Content::sequence(seq))
} }
@ -325,13 +325,13 @@ impl OutlineIndent {
numbering, numbering,
)?; )?;
hidden += numbers + SpaceElem::new().pack(); hidden += numbers + SpaceElem::shared().clone();
}; };
} }
if !ancestors.is_empty() { if !ancestors.is_empty() {
seq.push(HideElem::new(hidden).pack()); seq.push(HideElem::new(hidden).pack());
seq.push(SpaceElem::new().pack()); seq.push(SpaceElem::shared().clone());
} }
} }
@ -508,7 +508,7 @@ impl Show for Packed<OutlineEntry> {
// Add filler symbols between the section name and page number. // Add filler symbols between the section name and page number.
if let Some(filler) = self.fill() { if let Some(filler) = self.fill() {
seq.push(SpaceElem::new().pack()); seq.push(SpaceElem::shared().clone());
seq.push( seq.push(
BoxElem::new() BoxElem::new()
.with_body(Some(filler.clone())) .with_body(Some(filler.clone()))
@ -516,7 +516,7 @@ impl Show for Packed<OutlineEntry> {
.pack() .pack()
.spanned(self.span()), .spanned(self.span()),
); );
seq.push(SpaceElem::new().pack()); seq.push(SpaceElem::shared().clone());
} else { } else {
seq.push(HElem::new(Fr::one().into()).pack()); seq.push(HElem::new(Fr::one().into()).pack());
} }

View File

@ -7,6 +7,7 @@ use crate::foundations::{
}; };
use crate::layout::{Em, Length}; use crate::layout::{Em, Length};
use crate::realize::StyleVec; use crate::realize::StyleVec;
use crate::utils::singleton;
/// Arranges text, spacing and inline-level elements into a paragraph. /// Arranges text, spacing and inline-level elements into a paragraph.
/// ///
@ -150,9 +151,9 @@ impl Construct for ParElem {
let styles = Self::set(engine, args)?; let styles = Self::set(engine, args)?;
let body = args.expect::<Content>("body")?; let body = args.expect::<Content>("body")?;
Ok(Content::sequence([ Ok(Content::sequence([
ParbreakElem::new().pack(), ParbreakElem::shared().clone(),
body.styled_with_map(styles), body.styled_with_map(styles),
ParbreakElem::new().pack(), ParbreakElem::shared().clone(),
])) ]))
} }
} }
@ -197,4 +198,11 @@ pub enum Linebreaks {
#[elem(title = "Paragraph Break", Unlabellable)] #[elem(title = "Paragraph Break", Unlabellable)]
pub struct ParbreakElem {} pub struct ParbreakElem {}
impl ParbreakElem {
/// Get the globally shared paragraph element.
pub fn shared() -> &'static Content {
singleton!(Content, ParbreakElem::new().pack())
}
}
impl Unlabellable for Packed<ParbreakElem> {} impl Unlabellable for Packed<ParbreakElem> {}

View File

@ -189,7 +189,7 @@ impl Show for Packed<QuoteElem> {
.spanned(self.span()); .spanned(self.span());
if let Some(attribution) = self.attribution(styles).as_ref() { if let Some(attribution) = self.attribution(styles).as_ref() {
let mut seq = vec![TextElem::packed('—'), SpaceElem::new().pack()]; let mut seq = vec![TextElem::packed('—'), SpaceElem::shared().clone()];
match attribution { match attribution {
Attribution::Content(content) => { Attribution::Content(content) => {
@ -213,7 +213,7 @@ impl Show for Packed<QuoteElem> {
realized = PadElem::new(realized).pack(); realized = PadElem::new(realized).pack();
} else if let Some(Attribution::Label(label)) = self.attribution(styles) { } else if let Some(Attribution::Label(label)) = self.attribution(styles) {
realized += SpaceElem::new().pack() realized += SpaceElem::shared().clone()
+ CiteElem::new(*label).pack().spanned(self.span()); + CiteElem::new(*label).pack().spanned(self.span());
} }

View File

@ -1,5 +1,6 @@
use crate::foundations::{elem, Packed}; use crate::foundations::{elem, Content, NativeElement, Packed};
use crate::realize::{Behave, Behaviour}; use crate::realize::{Behave, Behaviour};
use crate::utils::singleton;
/// Inserts a line break. /// Inserts a line break.
/// ///
@ -37,6 +38,13 @@ pub struct LinebreakElem {
pub justify: bool, pub justify: bool,
} }
impl LinebreakElem {
/// Get the globally shared linebreak element.
pub fn shared() -> &'static Content {
singleton!(Content, LinebreakElem::new().pack())
}
}
impl Behave for Packed<LinebreakElem> { impl Behave for Packed<LinebreakElem> {
fn behaviour(&self) -> Behaviour { fn behaviour(&self) -> Behaviour {
Behaviour::Destructive Behaviour::Destructive

View File

@ -440,7 +440,7 @@ impl Show for Packed<RawElem> {
let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1)); let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
for (i, line) in lines.iter().enumerate() { for (i, line) in lines.iter().enumerate() {
if i != 0 { if i != 0 {
seq.push(LinebreakElem::new().pack()); seq.push(LinebreakElem::shared().clone());
} }
seq.push(line.clone().pack()); seq.push(line.clone().pack());

View File

@ -1,12 +1,22 @@
use ecow::EcoString; use ecow::EcoString;
use crate::foundations::{elem, Packed, PlainText, Repr, Unlabellable}; use crate::foundations::{
elem, Content, NativeElement, Packed, PlainText, Repr, Unlabellable,
};
use crate::realize::{Behave, Behaviour}; use crate::realize::{Behave, Behaviour};
use crate::utils::singleton;
/// A text space. /// A text space.
#[elem(Behave, Unlabellable, PlainText, Repr)] #[elem(Behave, Unlabellable, PlainText, Repr)]
pub struct SpaceElem {} pub struct SpaceElem {}
impl SpaceElem {
/// Get the globally shared space element.
pub fn shared() -> &'static Content {
singleton!(Content, SpaceElem::new().pack())
}
}
impl Repr for SpaceElem { impl Repr for SpaceElem {
fn repr(&self) -> EcoString { fn repr(&self) -> EcoString {
"[ ]".into() "[ ]".into()

View File

@ -211,11 +211,7 @@ pub enum Color {
#[scope] #[scope]
impl Color { impl Color {
/// The module of preset color maps. /// The module of preset color maps.
pub const MAP: fn() -> Module = || { pub const MAP: fn() -> Module = || crate::utils::singleton!(Module, map()).clone();
// Lazy to avoid re-allocating.
static MODULE: Lazy<Module> = Lazy::new(map);
MODULE.clone()
};
pub const BLACK: Self = Self::Luma(Luma::new(0.0, 1.0)); pub const BLACK: Self = Self::Luma(Luma::new(0.0, 1.0));
pub const GRAY: Self = Self::Luma(Luma::new(0.6666666, 1.0)); pub const GRAY: Self = Self::Luma(Luma::new(0.6666666, 1.0));

View File

@ -5,14 +5,13 @@ use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::OnceLock; use std::sync::OnceLock;
use once_cell::sync::Lazy;
use parking_lot::Mutex; use parking_lot::Mutex;
use typst::diag::{bail, FileError, FileResult, StrResult}; use typst::diag::{bail, FileError, FileResult, StrResult};
use typst::foundations::{func, Bytes, Datetime, NoneValue, Repr, Smart, Value}; use typst::foundations::{func, Bytes, Datetime, NoneValue, Repr, Smart, Value};
use typst::layout::{Abs, Margin, PageElem}; use typst::layout::{Abs, Margin, PageElem};
use typst::syntax::{FileId, Source}; use typst::syntax::{FileId, Source};
use typst::text::{Font, FontBook, TextElem, TextSize}; use typst::text::{Font, FontBook, TextElem, TextSize};
use typst::utils::LazyHash; use typst::utils::{singleton, LazyHash};
use typst::visualize::Color; use typst::visualize::Color;
use typst::{Library, World}; use typst::{Library, World};
@ -29,8 +28,10 @@ impl TestWorld {
/// This is cheap because the shared base for all test runs is lazily /// This is cheap because the shared base for all test runs is lazily
/// initialized just once. /// initialized just once.
pub fn new(source: Source) -> Self { pub fn new(source: Source) -> Self {
static BASE: Lazy<TestBase> = Lazy::new(TestBase::default); Self {
Self { main: source, base: &*BASE } main: source,
base: singleton!(TestBase, TestBase::default()),
}
} }
} }