mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 13:59:08 +00:00
Do not reuse cached entries for recursive structures that are being reconstructed
This commit is contained in:
parent
caf4a80542
commit
a64d9d97c5
1 changed files with 149 additions and 41 deletions
|
@ -41,11 +41,18 @@ roc_error_macros::assert_sizeof_default!(LambdaSet, 3 * 8);
|
||||||
type LayoutResult<'a> = Result<Layout<'a>, LayoutProblem>;
|
type LayoutResult<'a> = Result<Layout<'a>, LayoutProblem>;
|
||||||
type RawFunctionLayoutResult<'a> = Result<RawFunctionLayout<'a>, LayoutProblem>;
|
type RawFunctionLayoutResult<'a> = Result<RawFunctionLayout<'a>, LayoutProblem>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct CacheMeta {
|
||||||
|
/// Does this cache entry include a recursive structure? If so, what's the recursion variable
|
||||||
|
/// of that structure?
|
||||||
|
has_recursive_structure: Option<Variable>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A single layer of the layout cache.
|
/// A single layer of the layout cache.
|
||||||
/// Snapshots are implemented by operating on new layers, and rollbacks by dropping the latest
|
/// Snapshots are implemented by operating on new layers, and rollbacks by dropping the latest
|
||||||
/// layer.
|
/// layer.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct CacheLayer<Result>(MutMap<Variable, Result>);
|
struct CacheLayer<Result>(MutMap<Variable, (Result, CacheMeta)>);
|
||||||
|
|
||||||
impl<Result> Default for CacheLayer<Result> {
|
impl<Result> Default for CacheLayer<Result> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -95,7 +102,11 @@ impl<'a> LayoutCache<'a> {
|
||||||
|
|
||||||
// [Layout::from_var] should query the cache!
|
// [Layout::from_var] should query the cache!
|
||||||
let Cacheable(value, criteria) = Layout::from_var(&mut env, var);
|
let Cacheable(value, criteria) = Layout::from_var(&mut env, var);
|
||||||
debug_assert!(criteria.is_cacheable());
|
debug_assert!(
|
||||||
|
criteria.is_cacheable(),
|
||||||
|
"{:?} not cacheable as top-level",
|
||||||
|
value
|
||||||
|
);
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +129,11 @@ impl<'a> LayoutCache<'a> {
|
||||||
|
|
||||||
// [Layout::from_var] should query the cache!
|
// [Layout::from_var] should query the cache!
|
||||||
let Cacheable(value, criteria) = RawFunctionLayout::from_var(&mut env, var);
|
let Cacheable(value, criteria) = RawFunctionLayout::from_var(&mut env, var);
|
||||||
debug_assert!(criteria.is_cacheable());
|
debug_assert!(
|
||||||
|
criteria.is_cacheable(),
|
||||||
|
"{:?} not cacheable as top-level",
|
||||||
|
value
|
||||||
|
);
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +141,7 @@ impl<'a> LayoutCache<'a> {
|
||||||
cache: &[CacheLayer<Result>],
|
cache: &[CacheLayer<Result>],
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
) -> Option<Result> {
|
) -> Option<(Result, CacheMeta)> {
|
||||||
let root = subs.get_root_key_without_compacting(var);
|
let root = subs.get_root_key_without_compacting(var);
|
||||||
|
|
||||||
for layer in cache.iter().rev() {
|
for layer in cache.iter().rev() {
|
||||||
|
@ -139,30 +154,49 @@ impl<'a> LayoutCache<'a> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_help<Result>(
|
fn insert_help<Result: std::fmt::Debug + Copy>(
|
||||||
cache: &mut [CacheLayer<Result>],
|
cache: &mut [CacheLayer<Result>],
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
result: Result,
|
result: Result,
|
||||||
|
cache_metadata: CacheMeta,
|
||||||
) {
|
) {
|
||||||
let root = subs.get_root_key_without_compacting(var);
|
let root = subs.get_root_key_without_compacting(var);
|
||||||
let layer = cache
|
let layer = cache
|
||||||
.last_mut()
|
.last_mut()
|
||||||
.expect("cache must have at least one layer");
|
.expect("cache must have at least one layer");
|
||||||
let opt_old_result = layer.0.insert(root, result);
|
let opt_old_result = layer.0.insert(root, (result, cache_metadata));
|
||||||
debug_assert!(opt_old_result.is_none(), "overwritting cache result");
|
if let Some(old_result) = opt_old_result {
|
||||||
|
// Can happen when we need to re-calculate a recursive layout
|
||||||
|
roc_tracing::debug!(
|
||||||
|
?old_result,
|
||||||
|
new_result=?result,
|
||||||
|
?var,
|
||||||
|
"overwritting layout cache"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, subs: &Subs, var: Variable) -> Option<LayoutResult<'a>> {
|
fn get(&self, subs: &Subs, var: Variable) -> Option<(LayoutResult<'a>, CacheMeta)> {
|
||||||
Self::get_help(&self.cache, subs, var)
|
Self::get_help(&self.cache, subs, var)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_raw_function(&self, subs: &Subs, var: Variable) -> Option<RawFunctionLayoutResult<'a>> {
|
fn get_raw_function(
|
||||||
|
&self,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Option<(RawFunctionLayoutResult<'a>, CacheMeta)> {
|
||||||
Self::get_help(&self.raw_function_cache, subs, var)
|
Self::get_help(&self.raw_function_cache, subs, var)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, subs: &Subs, var: Variable, result: LayoutResult<'a>) {
|
fn insert(
|
||||||
Self::insert_help(&mut self.cache, subs, var, result)
|
&mut self,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
result: LayoutResult<'a>,
|
||||||
|
cache_metadata: CacheMeta,
|
||||||
|
) {
|
||||||
|
Self::insert_help(&mut self.cache, subs, var, result, cache_metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_raw_function(
|
fn insert_raw_function(
|
||||||
|
@ -170,8 +204,15 @@ impl<'a> LayoutCache<'a> {
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
result: RawFunctionLayoutResult<'a>,
|
result: RawFunctionLayoutResult<'a>,
|
||||||
|
cache_metadata: CacheMeta,
|
||||||
) {
|
) {
|
||||||
Self::insert_help(&mut self.raw_function_cache, subs, var, result)
|
Self::insert_help(
|
||||||
|
&mut self.raw_function_cache,
|
||||||
|
subs,
|
||||||
|
var,
|
||||||
|
result,
|
||||||
|
cache_metadata,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot(&mut self) -> CacheSnapshot {
|
pub fn snapshot(&mut self) -> CacheSnapshot {
|
||||||
|
@ -199,17 +240,24 @@ pub struct CacheSnapshot {
|
||||||
layer: usize,
|
layer: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct CacheCriteria {
|
struct CacheCriteria {
|
||||||
|
/// Whether there is a naked recursion pointer in this layout, that doesn't pass through a
|
||||||
|
/// recursive structure.
|
||||||
has_naked_recursion_pointer: bool,
|
has_naked_recursion_pointer: bool,
|
||||||
|
/// Whether this layout contains a recursive structure. If `Some`, contains the variable of the
|
||||||
|
/// recursion variable of that structure.
|
||||||
|
has_recursive_structure: Option<Variable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CACHEABLE: CacheCriteria = CacheCriteria {
|
const CACHEABLE: CacheCriteria = CacheCriteria {
|
||||||
has_naked_recursion_pointer: false,
|
has_naked_recursion_pointer: false,
|
||||||
|
has_recursive_structure: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
const NAKED_RECURSION_PTR: CacheCriteria = CacheCriteria {
|
const NAKED_RECURSION_PTR: CacheCriteria = CacheCriteria {
|
||||||
has_naked_recursion_pointer: true,
|
has_naked_recursion_pointer: true,
|
||||||
|
has_recursive_structure: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl CacheCriteria {
|
impl CacheCriteria {
|
||||||
|
@ -222,13 +270,25 @@ impl CacheCriteria {
|
||||||
fn and(&mut self, other: Self) {
|
fn and(&mut self, other: Self) {
|
||||||
self.has_naked_recursion_pointer =
|
self.has_naked_recursion_pointer =
|
||||||
self.has_naked_recursion_pointer || other.has_naked_recursion_pointer;
|
self.has_naked_recursion_pointer || other.has_naked_recursion_pointer;
|
||||||
|
// TODO: can these ever conflict?
|
||||||
|
self.has_recursive_structure = self
|
||||||
|
.has_recursive_structure
|
||||||
|
.or(other.has_recursive_structure);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pass_through_recursive_union(&mut self) {
|
fn pass_through_recursive_union(&mut self, recursion_var: Variable) {
|
||||||
self.has_naked_recursion_pointer = false;
|
self.has_naked_recursion_pointer = false;
|
||||||
|
self.has_recursive_structure = Some(recursion_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_metadata(&self) -> CacheMeta {
|
||||||
|
CacheMeta {
|
||||||
|
has_recursive_structure: self.has_recursive_structure,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct Cacheable<T>(T, CacheCriteria);
|
pub(crate) struct Cacheable<T>(T, CacheCriteria);
|
||||||
|
|
||||||
impl<T> Cacheable<T> {
|
impl<T> Cacheable<T> {
|
||||||
|
@ -1435,8 +1495,6 @@ impl<'a> LambdaSet<'a> {
|
||||||
|
|
||||||
match resolve_lambda_set(env.subs, closure_var) {
|
match resolve_lambda_set(env.subs, closure_var) {
|
||||||
ResolvedLambdaSet::Set(mut lambdas, opt_recursion_var) => {
|
ResolvedLambdaSet::Set(mut lambdas, opt_recursion_var) => {
|
||||||
let mut criteria = CACHEABLE;
|
|
||||||
|
|
||||||
// sort the tags; make sure ordering stays intact!
|
// sort the tags; make sure ordering stays intact!
|
||||||
lambdas.sort_by_key(|(sym, _)| *sym);
|
lambdas.sort_by_key(|(sym, _)| *sym);
|
||||||
|
|
||||||
|
@ -1457,6 +1515,9 @@ impl<'a> LambdaSet<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for var in variables {
|
for var in variables {
|
||||||
|
// We determine cacheability of the lambda set based on the runtime
|
||||||
|
// representation, so here the criteria doesn't matter.
|
||||||
|
let mut criteria = CACHEABLE;
|
||||||
let arg = cached!(Layout::from_var(env, *var), criteria);
|
let arg = cached!(Layout::from_var(env, *var), criteria);
|
||||||
arguments.push(arg);
|
arguments.push(arg);
|
||||||
}
|
}
|
||||||
|
@ -1508,11 +1569,12 @@ impl<'a> LambdaSet<'a> {
|
||||||
(set, set_with_variables)
|
(set, set_with_variables)
|
||||||
};
|
};
|
||||||
|
|
||||||
let representation = env.arena.alloc(Self::make_representation(
|
let Cacheable(representation, criteria) = Self::make_representation(
|
||||||
env,
|
env,
|
||||||
set_with_variables,
|
set_with_variables,
|
||||||
opt_recursion_var.into_variable(),
|
opt_recursion_var.into_variable(),
|
||||||
));
|
);
|
||||||
|
let representation = env.arena.alloc(representation);
|
||||||
|
|
||||||
Cacheable(
|
Cacheable(
|
||||||
Ok(LambdaSet {
|
Ok(LambdaSet {
|
||||||
|
@ -1537,19 +1599,18 @@ impl<'a> LambdaSet<'a> {
|
||||||
env: &mut Env<'a, '_>,
|
env: &mut Env<'a, '_>,
|
||||||
set: std::vec::Vec<(&Symbol, &[Variable])>,
|
set: std::vec::Vec<(&Symbol, &[Variable])>,
|
||||||
opt_rec_var: Option<Variable>,
|
opt_rec_var: Option<Variable>,
|
||||||
) -> Layout<'a> {
|
) -> Cacheable<Layout<'a>> {
|
||||||
let union_labels = UnsortedUnionLabels { tags: set };
|
let union_labels = UnsortedUnionLabels { tags: set };
|
||||||
|
|
||||||
match opt_rec_var {
|
match opt_rec_var {
|
||||||
Some(rec_var) => {
|
Some(rec_var) => {
|
||||||
let Cacheable(result, _) = layout_from_recursive_union(env, rec_var, &union_labels);
|
let Cacheable(result, criteria) =
|
||||||
result.expect("unable to create lambda set representation")
|
layout_from_recursive_union(env, rec_var, &union_labels);
|
||||||
|
let result = result.expect("unable to create lambda set representation");
|
||||||
|
Cacheable(result, criteria)
|
||||||
}
|
}
|
||||||
|
|
||||||
None => {
|
None => layout_from_non_recursive_union(env, &union_labels),
|
||||||
let Cacheable(result, _) = layout_from_non_recursive_union(env, &union_labels);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1780,17 +1841,61 @@ impl<'a, 'b> Env<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn can_reuse_cached(&self, var: Variable, cache_metadata: CacheMeta) -> bool {
|
||||||
|
let CacheMeta {
|
||||||
|
has_recursive_structure,
|
||||||
|
} = cache_metadata;
|
||||||
|
if let Some(recursive_structure) = has_recursive_structure {
|
||||||
|
if self.seen.iter().any(|var| {
|
||||||
|
self.subs
|
||||||
|
.equivalent_without_compacting(*var, recursive_structure)
|
||||||
|
}) {
|
||||||
|
// If the cached entry references a recursive structure that we're in the process
|
||||||
|
// of visiting currently, we can't use the cached entry, and instead must
|
||||||
|
// recalculate the nested layout, because the nested recursive structure will
|
||||||
|
// likely turn into a recursive pointer now.
|
||||||
|
//
|
||||||
|
// For example, suppose we are constructing the layout of
|
||||||
|
//
|
||||||
|
// [A, B (List r)] as r
|
||||||
|
//
|
||||||
|
// and we have already constructed and cached the layout of `List r`, which would
|
||||||
|
// be
|
||||||
|
//
|
||||||
|
// List (Recursive [Unit, List RecursivePointer])
|
||||||
|
//
|
||||||
|
// If we use the cached entry of `List r`, we would end up with the layout
|
||||||
|
//
|
||||||
|
// Recursive [Unit, (List (Recursive [Unit, List RecursivePointer]))]
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cached layout for `List r`
|
||||||
|
//
|
||||||
|
// but this is not correct; the canonical layout of `[A, B (List r)] as r` is
|
||||||
|
//
|
||||||
|
// Recursive [Unit, (List RecursivePointer)]
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn cached_or(
|
fn cached_or(
|
||||||
&mut self,
|
&mut self,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
compute_layout: impl FnOnce(&mut Env<'a, 'b>) -> Cacheable<LayoutResult<'a>>,
|
compute_layout: impl FnOnce(&mut Env<'a, 'b>) -> Cacheable<LayoutResult<'a>>,
|
||||||
) -> Cacheable<LayoutResult<'a>> {
|
) -> Cacheable<LayoutResult<'a>> {
|
||||||
if let Some(result) = self.cache.get(self.subs, var) {
|
if self.is_seen(var) {
|
||||||
|
return Cacheable(Ok(Layout::RecursivePointer), NAKED_RECURSION_PTR);
|
||||||
|
}
|
||||||
|
if let Some((result, metadata)) = self.cache.get(self.subs, var) {
|
||||||
|
if self.can_reuse_cached(var, metadata) {
|
||||||
return cacheable(result);
|
return cacheable(result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let Cacheable(result, criteria) = compute_layout(self);
|
let Cacheable(result, criteria) = compute_layout(self);
|
||||||
if criteria.is_cacheable() {
|
if criteria.is_cacheable() {
|
||||||
self.cache.insert(self.subs, var, result);
|
self.cache
|
||||||
|
.insert(self.subs, var, result, criteria.cache_metadata());
|
||||||
}
|
}
|
||||||
Cacheable(result, criteria)
|
Cacheable(result, criteria)
|
||||||
}
|
}
|
||||||
|
@ -1800,12 +1905,15 @@ impl<'a, 'b> Env<'a, 'b> {
|
||||||
var: Variable,
|
var: Variable,
|
||||||
compute_layout: impl FnOnce(&mut Env<'a, 'b>) -> Cacheable<RawFunctionLayoutResult<'a>>,
|
compute_layout: impl FnOnce(&mut Env<'a, 'b>) -> Cacheable<RawFunctionLayoutResult<'a>>,
|
||||||
) -> Cacheable<RawFunctionLayoutResult<'a>> {
|
) -> Cacheable<RawFunctionLayoutResult<'a>> {
|
||||||
if let Some(result) = self.cache.get_raw_function(self.subs, var) {
|
if let Some((result, metadata)) = self.cache.get_raw_function(self.subs, var) {
|
||||||
|
if self.can_reuse_cached(var, metadata) {
|
||||||
return cacheable(result);
|
return cacheable(result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let Cacheable(result, criteria) = compute_layout(self);
|
let Cacheable(result, criteria) = compute_layout(self);
|
||||||
if criteria.is_cacheable() {
|
if criteria.is_cacheable() {
|
||||||
self.cache.insert_raw_function(self.subs, var, result);
|
self.cache
|
||||||
|
.insert_raw_function(self.subs, var, result, criteria.cache_metadata());
|
||||||
}
|
}
|
||||||
Cacheable(result, criteria)
|
Cacheable(result, criteria)
|
||||||
}
|
}
|
||||||
|
@ -1958,12 +2066,8 @@ impl<'a> Layout<'a> {
|
||||||
/// monomorphized away already!
|
/// monomorphized away already!
|
||||||
fn from_var(env: &mut Env<'a, '_>, var: Variable) -> Cacheable<LayoutResult<'a>> {
|
fn from_var(env: &mut Env<'a, '_>, var: Variable) -> Cacheable<LayoutResult<'a>> {
|
||||||
env.cached_or(var, |env| {
|
env.cached_or(var, |env| {
|
||||||
if env.is_seen(var) {
|
|
||||||
Cacheable(Ok(Layout::RecursivePointer), NAKED_RECURSION_PTR)
|
|
||||||
} else {
|
|
||||||
let content = env.subs.get_content_without_compacting(var);
|
let content = env.subs.get_content_without_compacting(var);
|
||||||
Self::new_help(env, var, *content)
|
Self::new_help(env, var, *content)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3255,12 +3359,12 @@ where
|
||||||
} else {
|
} else {
|
||||||
Cacheable(UnionVariant::Unit, cache_criteria)
|
Cacheable(UnionVariant::Unit, cache_criteria)
|
||||||
}
|
}
|
||||||
} else if opt_rec_var.is_some() {
|
} else if let Some(rec_var) = opt_rec_var {
|
||||||
let variant = UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped {
|
let variant = UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped {
|
||||||
tag_name: tag_name.into(),
|
tag_name: tag_name.into(),
|
||||||
fields: layouts.into_bump_slice(),
|
fields: layouts.into_bump_slice(),
|
||||||
});
|
});
|
||||||
cache_criteria.pass_through_recursive_union();
|
cache_criteria.pass_through_recursive_union(rec_var);
|
||||||
Cacheable(variant, cache_criteria)
|
Cacheable(variant, cache_criteria)
|
||||||
} else {
|
} else {
|
||||||
Cacheable(
|
Cacheable(
|
||||||
|
@ -3394,7 +3498,11 @@ where
|
||||||
sorted_tag_layouts: answer,
|
sorted_tag_layouts: answer,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cache_criteria.pass_through_recursive_union();
|
|
||||||
|
if let Some(rec_var) = opt_rec_var {
|
||||||
|
cache_criteria.pass_through_recursive_union(rec_var);
|
||||||
|
debug_assert!(!matches!(variant, WrappedVariant::NonRecursive { .. }));
|
||||||
|
}
|
||||||
|
|
||||||
Cacheable(UnionVariant::Wrapped(variant), cache_criteria)
|
Cacheable(UnionVariant::Wrapped(variant), cache_criteria)
|
||||||
}
|
}
|
||||||
|
@ -3579,7 +3687,7 @@ where
|
||||||
} else {
|
} else {
|
||||||
UnionLayout::Recursive(tag_layouts.into_bump_slice())
|
UnionLayout::Recursive(tag_layouts.into_bump_slice())
|
||||||
};
|
};
|
||||||
criteria.pass_through_recursive_union();
|
criteria.pass_through_recursive_union(rec_var);
|
||||||
|
|
||||||
Cacheable(Ok(Layout::Union(union_layout)), criteria)
|
Cacheable(Ok(Layout::Union(union_layout)), criteria)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue