Merge pull request #34 from typst/cfg-conditional
Put incremental compilation behind feature
This commit is contained in:
commit
d5d2b80699
9
.github/workflows/rust.yml
vendored
9
.github/workflows/rust.yml
vendored
@ -36,6 +36,9 @@ jobs:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Dependency cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@ -47,3 +50,9 @@ jobs:
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path typst/Cargo.toml --all-features
|
||||
|
||||
- name: Test without incremental
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path typst/Cargo.toml --no-default-features --features fs
|
||||
|
@ -5,9 +5,10 @@ authors = ["The Typst Project Developers"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["cli", "fs"]
|
||||
default = ["cli", "fs", "layout-cache"]
|
||||
cli = ["anyhow", "fs", "same-file"]
|
||||
fs = ["dirs", "memmap2", "same-file", "walkdir"]
|
||||
layout-cache = []
|
||||
|
||||
[workspace]
|
||||
members = ["bench"]
|
||||
|
@ -5,9 +5,13 @@ authors = ["The Typst Project Developers"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = ["layout-cache"]
|
||||
layout-cache = ["typst/layout-cache"]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
typst = { path = ".." }
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
typst = { path = "..", default-features = false, features = ["fs"] }
|
||||
|
||||
[[bench]]
|
||||
name = "typst"
|
||||
|
@ -1,14 +1,17 @@
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use typst::eval::eval;
|
||||
use typst::exec::exec;
|
||||
use typst::cache::Cache;
|
||||
use typst::eval::{eval, Module, Scope};
|
||||
use typst::exec::{exec, State};
|
||||
use typst::export::pdf;
|
||||
use typst::layout::layout;
|
||||
use typst::layout::{self, layout, Frame};
|
||||
use typst::loading::FsLoader;
|
||||
use typst::parse::parse;
|
||||
use typst::syntax;
|
||||
use typst::typeset;
|
||||
|
||||
const FONT_DIR: &str = "../fonts";
|
||||
@ -16,42 +19,144 @@ const TYP_DIR: &str = "../tests/typ";
|
||||
const CASES: &[&str] = &["coma.typ", "text/basic.typ"];
|
||||
|
||||
fn benchmarks(c: &mut Criterion) {
|
||||
let mut loader = FsLoader::new();
|
||||
loader.search_path(FONT_DIR);
|
||||
|
||||
let mut cache = typst::cache::Cache::new(&loader);
|
||||
let scope = typst::library::new();
|
||||
let state = typst::exec::State::default();
|
||||
|
||||
let ctx = Context::new();
|
||||
for case in CASES {
|
||||
let path = Path::new(TYP_DIR).join(case);
|
||||
let name = path.file_stem().unwrap().to_string_lossy();
|
||||
let src = std::fs::read_to_string(&path).unwrap();
|
||||
let case = Case::new(src, ctx.clone());
|
||||
|
||||
/// Bench with all caches.
|
||||
macro_rules! bench {
|
||||
($step:literal: $code:expr) => {
|
||||
($step:literal, setup = |$cache:ident| $setup:expr, code = $code:expr $(,)?) => {
|
||||
c.bench_function(&format!("{}-{}", $step, name), |b| {
|
||||
b.iter(|| {
|
||||
cache.layout.clear();
|
||||
$code
|
||||
});
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let mut borrowed = ctx.borrow_mut();
|
||||
let $cache = &mut borrowed.cache;
|
||||
$setup
|
||||
},
|
||||
|_| $code,
|
||||
criterion::BatchSize::PerIteration,
|
||||
)
|
||||
});
|
||||
};
|
||||
($step:literal, $code:expr) => {
|
||||
c.bench_function(&format!("{}-{}", $step, name), |b| b.iter(|| $code));
|
||||
};
|
||||
}
|
||||
|
||||
// Prepare intermediate results, run warm and fill caches.
|
||||
let src = std::fs::read_to_string(&path).unwrap();
|
||||
let tree = Rc::new(parse(&src).output);
|
||||
let evaluated = eval(&mut loader, &mut cache, Some(&path), tree.clone(), &scope);
|
||||
let executed = exec(&evaluated.output.template, state.clone());
|
||||
let layouted = layout(&mut loader, &mut cache, &executed.output);
|
||||
bench!("parse", case.parse());
|
||||
bench!("eval", case.eval());
|
||||
bench!("exec", case.exec());
|
||||
|
||||
// Bench!
|
||||
bench!("parse": parse(&src));
|
||||
bench!("eval": eval(&mut loader, &mut cache, Some(&path), tree.clone(), &scope));
|
||||
bench!("exec": exec(&evaluated.output.template, state.clone()));
|
||||
bench!("layout": layout(&mut loader, &mut cache, &executed.output));
|
||||
bench!("typeset": typeset(&mut loader, &mut cache, Some(&path), &src, &scope, state.clone()));
|
||||
bench!("pdf": pdf(&cache, &layouted));
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
{
|
||||
bench!("layout", case.layout());
|
||||
bench!("typeset", case.typeset());
|
||||
}
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
{
|
||||
bench!(
|
||||
"layout",
|
||||
setup = |cache| cache.layout.clear(),
|
||||
code = case.layout(),
|
||||
);
|
||||
bench!(
|
||||
"typeset",
|
||||
setup = |cache| cache.layout.clear(),
|
||||
code = case.typeset(),
|
||||
);
|
||||
bench!("layout-cached", case.layout());
|
||||
bench!("typeset-cached", case.typeset());
|
||||
}
|
||||
|
||||
bench!("pdf", case.pdf());
|
||||
}
|
||||
}
|
||||
|
||||
/// The context required for benchmarking a case.
|
||||
struct Context {
|
||||
loader: FsLoader,
|
||||
cache: Cache,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
fn new() -> Rc<RefCell<Self>> {
|
||||
let mut loader = FsLoader::new();
|
||||
loader.search_path(FONT_DIR);
|
||||
let cache = Cache::new(&loader);
|
||||
Rc::new(RefCell::new(Self { loader, cache }))
|
||||
}
|
||||
}
|
||||
|
||||
/// A test case with prepared intermediate results.
|
||||
struct Case {
|
||||
ctx: Rc<RefCell<Context>>,
|
||||
src: String,
|
||||
scope: Scope,
|
||||
state: State,
|
||||
ast: Rc<syntax::Tree>,
|
||||
module: Module,
|
||||
tree: layout::Tree,
|
||||
frames: Vec<Rc<Frame>>,
|
||||
}
|
||||
|
||||
impl Case {
|
||||
fn new(src: impl Into<String>, ctx: Rc<RefCell<Context>>) -> Self {
|
||||
let mut borrowed = ctx.borrow_mut();
|
||||
let Context { loader, cache } = &mut *borrowed;
|
||||
let scope = typst::library::new();
|
||||
let state = typst::exec::State::default();
|
||||
let src = src.into();
|
||||
let ast = Rc::new(parse(&src).output);
|
||||
let module = eval(loader, cache, None, ast.clone(), &scope).output;
|
||||
let tree = exec(&module.template, state.clone()).output;
|
||||
let frames = layout(loader, cache, &tree);
|
||||
drop(borrowed);
|
||||
Self {
|
||||
ctx,
|
||||
src,
|
||||
scope,
|
||||
state,
|
||||
ast,
|
||||
module,
|
||||
tree,
|
||||
frames,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(&self) -> syntax::Tree {
|
||||
parse(&self.src).output
|
||||
}
|
||||
|
||||
fn eval(&self) -> Module {
|
||||
let mut borrowed = self.ctx.borrow_mut();
|
||||
let Context { loader, cache } = &mut *borrowed;
|
||||
eval(loader, cache, None, self.ast.clone(), &self.scope).output
|
||||
}
|
||||
|
||||
fn exec(&self) -> layout::Tree {
|
||||
exec(&self.module.template, self.state.clone()).output
|
||||
}
|
||||
|
||||
fn layout(&self) -> Vec<Rc<Frame>> {
|
||||
let mut borrowed = self.ctx.borrow_mut();
|
||||
let Context { loader, cache } = &mut *borrowed;
|
||||
layout(loader, cache, &self.tree)
|
||||
}
|
||||
|
||||
fn typeset(&self) -> Vec<Rc<Frame>> {
|
||||
let mut borrowed = self.ctx.borrow_mut();
|
||||
let Context { loader, cache } = &mut *borrowed;
|
||||
let state = self.state.clone();
|
||||
typeset(loader, cache, None, &self.src, &self.scope, state).output
|
||||
}
|
||||
|
||||
fn pdf(&self) -> Vec<u8> {
|
||||
let ctx = self.ctx.borrow();
|
||||
pdf(&ctx.cache, &self.frames)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use crate::font::FontCache;
|
||||
use crate::image::ImageCache;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use crate::layout::LayoutCache;
|
||||
use crate::loading::Loader;
|
||||
|
||||
@ -12,6 +13,7 @@ pub struct Cache {
|
||||
/// Caches decoded images.
|
||||
pub image: ImageCache,
|
||||
/// Caches layouting artifacts.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub layout: LayoutCache,
|
||||
}
|
||||
|
||||
@ -21,6 +23,7 @@ impl Cache {
|
||||
Self {
|
||||
font: FontCache::new(loader),
|
||||
image: ImageCache::new(),
|
||||
#[cfg(feature = "layout-cache")]
|
||||
layout: LayoutCache::new(),
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use super::*;
|
||||
|
||||
/// A node that places a rectangular filled background behind its child.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub struct BackgroundNode {
|
||||
/// The kind of shape to use as a background.
|
||||
pub shape: BackgroundShape,
|
||||
|
@ -1,7 +1,8 @@
|
||||
use super::*;
|
||||
|
||||
/// A node that can fix its child's width and height.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub struct FixedNode {
|
||||
/// The fixed width, if any.
|
||||
pub width: Option<Linear>,
|
||||
|
@ -1,7 +1,8 @@
|
||||
use super::*;
|
||||
|
||||
/// A node that arranges its children in a grid.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub struct GridNode {
|
||||
/// The `main` and `cross` directions of this grid.
|
||||
///
|
||||
|
@ -4,7 +4,8 @@ use crate::image::ImageId;
|
||||
use ::image::GenericImageView;
|
||||
|
||||
/// An image node.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub struct ImageNode {
|
||||
/// The id of the image file.
|
||||
pub id: ImageId,
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
use std::ops::Deref;
|
||||
|
||||
@ -5,6 +6,7 @@ use super::*;
|
||||
|
||||
/// Caches layouting artifacts.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub struct LayoutCache {
|
||||
/// Maps from node hashes to the resulting frames and regions in which the
|
||||
/// frames are valid. The right hand side of the hash map is a vector of
|
||||
@ -15,6 +17,7 @@ pub struct LayoutCache {
|
||||
age: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
impl LayoutCache {
|
||||
/// Create a new, empty layout cache.
|
||||
pub fn new() -> Self {
|
||||
@ -100,6 +103,7 @@ impl LayoutCache {
|
||||
|
||||
/// Cached frames from past layouting.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub struct FramesEntry {
|
||||
/// The cached frames for a node.
|
||||
pub frames: Vec<Constrained<Rc<Frame>>>,
|
||||
@ -112,6 +116,7 @@ pub struct FramesEntry {
|
||||
temperature: [usize; 5],
|
||||
}
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
impl FramesEntry {
|
||||
/// Construct a new instance.
|
||||
pub fn new(frames: Vec<Constrained<Rc<Frame>>>, level: usize) -> Self {
|
||||
@ -205,6 +210,7 @@ impl Constraints {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
fn check(&self, regions: &Regions) -> bool {
|
||||
if self.expand != regions.expand {
|
||||
return false;
|
||||
|
@ -24,9 +24,12 @@ pub use stack::*;
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::hash::Hash;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use std::hash::Hasher;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use fxhash::FxHasher64;
|
||||
|
||||
use crate::cache::Cache;
|
||||
@ -35,7 +38,12 @@ use crate::loading::Loader;
|
||||
|
||||
/// Layout a tree into a collection of frames.
|
||||
pub fn layout(loader: &mut dyn Loader, cache: &mut Cache, tree: &Tree) -> Vec<Rc<Frame>> {
|
||||
tree.layout(&mut LayoutContext { loader, cache, level: 0 })
|
||||
tree.layout(&mut LayoutContext {
|
||||
loader,
|
||||
cache,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
level: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// A tree of layout nodes.
|
||||
@ -77,22 +85,35 @@ impl PageRun {
|
||||
/// A wrapper around a dynamic layouting node.
|
||||
pub struct AnyNode {
|
||||
node: Box<dyn Bounds>,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
hash: u64,
|
||||
}
|
||||
|
||||
impl AnyNode {
|
||||
/// Create a new instance from any node that satisifies the required bounds.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub fn new<T>(node: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Clone + PartialEq + Hash + 'static,
|
||||
{
|
||||
let mut state = FxHasher64::default();
|
||||
node.type_id().hash(&mut state);
|
||||
node.hash(&mut state);
|
||||
let hash = state.finish();
|
||||
let hash = {
|
||||
let mut state = FxHasher64::default();
|
||||
node.type_id().hash(&mut state);
|
||||
node.hash(&mut state);
|
||||
state.finish()
|
||||
};
|
||||
|
||||
Self { node: Box::new(node), hash }
|
||||
}
|
||||
|
||||
/// Create a new instance from any node that satisifies the required bounds.
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
pub fn new<T>(node: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Clone + PartialEq + 'static,
|
||||
{
|
||||
Self { node: Box::new(node) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for AnyNode {
|
||||
@ -101,15 +122,20 @@ impl Layout for AnyNode {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
ctx.level += 1;
|
||||
let frames =
|
||||
ctx.cache.layout.get(self.hash, regions.clone()).unwrap_or_else(|| {
|
||||
let frames = self.node.layout(ctx, regions);
|
||||
ctx.cache.layout.insert(self.hash, frames.clone(), ctx.level - 1);
|
||||
frames
|
||||
});
|
||||
ctx.level -= 1;
|
||||
frames
|
||||
#[cfg(feature = "layout-cache")]
|
||||
{
|
||||
ctx.level += 1;
|
||||
let frames =
|
||||
ctx.cache.layout.get(self.hash, regions.clone()).unwrap_or_else(|| {
|
||||
let frames = self.node.layout(ctx, regions);
|
||||
ctx.cache.layout.insert(self.hash, frames.clone(), ctx.level - 1);
|
||||
frames
|
||||
});
|
||||
ctx.level -= 1;
|
||||
frames
|
||||
}
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
self.node.layout(ctx, regions)
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,6 +143,7 @@ impl Clone for AnyNode {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
node: self.node.dyn_clone(),
|
||||
#[cfg(feature = "layout-cache")]
|
||||
hash: self.hash,
|
||||
}
|
||||
}
|
||||
@ -128,6 +155,7 @@ impl PartialEq for AnyNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
impl Hash for AnyNode {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.hash);
|
||||
@ -184,6 +212,7 @@ pub struct LayoutContext<'a> {
|
||||
/// A cache for loaded fonts and artifacts from past layouting.
|
||||
pub cache: &'a mut Cache,
|
||||
/// How deeply nested the current layout tree position is.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub level: usize,
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use super::*;
|
||||
|
||||
/// A node that adds padding to its child.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub struct PadNode {
|
||||
/// The amount of padding.
|
||||
pub padding: Sides<Linear>,
|
||||
|
@ -11,7 +11,8 @@ use crate::util::{RangeExt, SliceExt};
|
||||
type Range = std::ops::Range<usize>;
|
||||
|
||||
/// A node that arranges its children into a paragraph.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub struct ParNode {
|
||||
/// The inline direction of this paragraph.
|
||||
pub dir: Dir,
|
||||
@ -22,7 +23,8 @@ pub struct ParNode {
|
||||
}
|
||||
|
||||
/// A child of a paragraph node.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub enum ParChild {
|
||||
/// Spacing between other nodes.
|
||||
Spacing(Length),
|
||||
|
@ -3,7 +3,8 @@ use decorum::N64;
|
||||
use super::*;
|
||||
|
||||
/// A node that stacks its children.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub struct StackNode {
|
||||
/// The `main` and `cross` directions of this stack.
|
||||
///
|
||||
@ -19,7 +20,8 @@ pub struct StackNode {
|
||||
}
|
||||
|
||||
/// A child of a stack node.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||
pub enum StackChild {
|
||||
/// Spacing between other nodes.
|
||||
Spacing(Length),
|
||||
|
@ -272,46 +272,49 @@ fn test_part(
|
||||
}
|
||||
}
|
||||
|
||||
let reference_cache = cache.layout.clone();
|
||||
for level in 0 .. reference_cache.levels() {
|
||||
cache.layout = reference_cache.clone();
|
||||
cache.layout.retain(|x| x == level);
|
||||
if cache.layout.frames.is_empty() {
|
||||
continue;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
{
|
||||
let reference_cache = cache.layout.clone();
|
||||
for level in 0 .. reference_cache.levels() {
|
||||
cache.layout = reference_cache.clone();
|
||||
cache.layout.retain(|x| x == level);
|
||||
if cache.layout.frames.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
cache.layout.turnaround();
|
||||
|
||||
let cached_result = layout(loader, cache, &executed.output);
|
||||
|
||||
let misses = cache
|
||||
.layout
|
||||
.frames
|
||||
.iter()
|
||||
.flat_map(|(_, e)| e)
|
||||
.filter(|e| e.level == level && !e.hit() && e.age() == 2)
|
||||
.count();
|
||||
|
||||
if misses > 0 {
|
||||
ok = false;
|
||||
println!(
|
||||
" Recompilation had {} cache misses on level {} (Subtest {}) ❌",
|
||||
misses, level, i
|
||||
);
|
||||
}
|
||||
|
||||
if cached_result != layouted {
|
||||
ok = false;
|
||||
println!(
|
||||
" Recompilation of subtest {} differs from clean pass ❌",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
cache.layout = reference_cache;
|
||||
cache.layout.turnaround();
|
||||
|
||||
let cached_result = layout(loader, cache, &executed.output);
|
||||
|
||||
let misses = cache
|
||||
.layout
|
||||
.frames
|
||||
.iter()
|
||||
.flat_map(|(_, e)| e)
|
||||
.filter(|e| e.level == level && !e.hit() && e.age() == 2)
|
||||
.count();
|
||||
|
||||
if misses > 0 {
|
||||
ok = false;
|
||||
println!(
|
||||
" Recompilation had {} cache misses on level {} (Subtest {}) ❌",
|
||||
misses, level, i
|
||||
);
|
||||
}
|
||||
|
||||
if cached_result != layouted {
|
||||
ok = false;
|
||||
println!(
|
||||
" Recompilation of subtest {} differs from clean pass ❌",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
cache.layout = reference_cache;
|
||||
cache.layout.turnaround();
|
||||
|
||||
if !compare_ref {
|
||||
layouted.clear();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user