mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 21:39:07 +00:00
First pass at layout caching
This commit is contained in:
parent
8b0f7dc82f
commit
d757701426
3 changed files with 163 additions and 42 deletions
|
@ -3030,7 +3030,7 @@ fn generate_runtime_error_function<'a>(
|
||||||
/// Includes the exact types, but also auxiliary information like layouts.
|
/// Includes the exact types, but also auxiliary information like layouts.
|
||||||
struct TypeStateSnapshot {
|
struct TypeStateSnapshot {
|
||||||
subs_snapshot: roc_types::subs::SubsSnapshot,
|
subs_snapshot: roc_types::subs::SubsSnapshot,
|
||||||
layout_snapshot: crate::layout::SnapshotKeyPlaceholder,
|
layout_snapshot: crate::layout::CacheSnapshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a snapshot of the type state. Snapshots should be taken before new specializations, and
|
/// Takes a snapshot of the type state. Snapshots should be taken before new specializations, and
|
||||||
|
|
|
@ -42,7 +42,7 @@ pub type TagIdIntType = u16;
|
||||||
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<TagIdIntType>() * 8) as usize;
|
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<TagIdIntType>() * 8) as usize;
|
||||||
const GENERATE_NULLABLE: bool = true;
|
const GENERATE_NULLABLE: bool = true;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum LayoutProblem {
|
pub enum LayoutProblem {
|
||||||
UnresolvedTypeVar(Variable),
|
UnresolvedTypeVar(Variable),
|
||||||
Erroneous,
|
Erroneous,
|
||||||
|
@ -225,12 +225,14 @@ impl<'a> RawFunctionLayout<'a> {
|
||||||
/// Panics if given a FlexVar or RigidVar, since those should have been
|
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||||
/// monomorphized away already!
|
/// monomorphized away already!
|
||||||
fn from_var(env: &mut Env<'a, '_>, var: Variable) -> Result<Self, LayoutProblem> {
|
fn from_var(env: &mut Env<'a, '_>, var: Variable) -> Result<Self, LayoutProblem> {
|
||||||
|
env.cached_raw_function_or(var, |env| {
|
||||||
if env.is_seen(var) {
|
if env.is_seen(var) {
|
||||||
unreachable!("The initial variable of a signature cannot be seen already")
|
unreachable!("The initial variable of a signature cannot be seen already")
|
||||||
} else {
|
} 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)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1142,7 +1144,7 @@ impl<'a> LambdaSet<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_var(
|
pub fn from_var(
|
||||||
cache: &LayoutCache<'a>,
|
cache: &mut LayoutCache<'a>,
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
closure_var: Variable,
|
closure_var: Variable,
|
||||||
|
@ -1156,6 +1158,18 @@ impl<'a> LambdaSet<'a> {
|
||||||
// which should also resolve the issue here.
|
// which should also resolve the issue here.
|
||||||
let mut env = Env::from_components(cache, subs, arena, target_info);
|
let mut env = Env::from_components(cache, subs, arena, target_info);
|
||||||
|
|
||||||
|
let result = env.cached_or(closure_var, |env| {
|
||||||
|
Self::from_var_help(env, closure_var).map(|layout| Layout::LambdaSet(layout))
|
||||||
|
});
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(Layout::LambdaSet(lambda_set)) => Ok(lambda_set),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
Ok(layout) => internal_error!("other layout found for lambda set: {:?}", layout),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_var_help(env: &mut Env<'a, '_>, closure_var: Variable) -> Result<Self, LayoutProblem> {
|
||||||
roc_tracing::debug!(var = ?closure_var, size = ?lambda_set_size(env.subs, closure_var), "building lambda set layout");
|
roc_tracing::debug!(var = ?closure_var, size = ?lambda_set_size(env.subs, closure_var), "building lambda set layout");
|
||||||
|
|
||||||
match resolve_lambda_set(env.subs, closure_var) {
|
match resolve_lambda_set(env.subs, closure_var) {
|
||||||
|
@ -1180,7 +1194,7 @@ impl<'a> LambdaSet<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for var in variables {
|
for var in variables {
|
||||||
arguments.push(Layout::from_var(&mut env, *var)?);
|
arguments.push(Layout::from_var(env, *var)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let arguments = arguments.into_bump_slice();
|
let arguments = arguments.into_bump_slice();
|
||||||
|
@ -1231,7 +1245,7 @@ impl<'a> LambdaSet<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let representation = env.arena.alloc(Self::make_representation(
|
let representation = env.arena.alloc(Self::make_representation(
|
||||||
&mut env,
|
env,
|
||||||
set_with_variables,
|
set_with_variables,
|
||||||
opt_recursion_var.into_variable(),
|
opt_recursion_var.into_variable(),
|
||||||
));
|
));
|
||||||
|
@ -1452,13 +1466,12 @@ pub struct Env<'a, 'b> {
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
seen: Vec<'a, Variable>,
|
seen: Vec<'a, Variable>,
|
||||||
subs: &'b Subs,
|
subs: &'b Subs,
|
||||||
#[allow(unused)]
|
cache: &'b mut LayoutCache<'a>,
|
||||||
cache: &'b LayoutCache<'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Env<'a, 'b> {
|
impl<'a, 'b> Env<'a, 'b> {
|
||||||
pub fn from_components(
|
pub fn from_components(
|
||||||
cache: &'b LayoutCache<'a>,
|
cache: &'b mut LayoutCache<'a>,
|
||||||
subs: &'b Subs,
|
subs: &'b Subs,
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
target_info: TargetInfo,
|
target_info: TargetInfo,
|
||||||
|
@ -1494,6 +1507,30 @@ impl<'a, 'b> Env<'a, 'b> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cached_or(
|
||||||
|
&mut self,
|
||||||
|
var: Variable,
|
||||||
|
compute_layout: impl FnOnce(&mut Env<'a, 'b>) -> LayoutResult<'a>,
|
||||||
|
) -> LayoutResult<'a> {
|
||||||
|
if let Some(result) = self.cache.get(self.subs, var) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
let result = compute_layout(self);
|
||||||
|
self.cache.insert(self.subs, var, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cached_raw_function_or(
|
||||||
|
&mut self,
|
||||||
|
var: Variable,
|
||||||
|
compute_layout: impl FnOnce(&mut Env<'a, 'b>) -> RawFunctionLayoutResult<'a>,
|
||||||
|
) -> RawFunctionLayoutResult<'a> {
|
||||||
|
if let Some(result) = self.cache.get_raw_function(self.subs, var) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
let result = compute_layout(self);
|
||||||
|
self.cache.insert_raw_function(self.subs, var, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 {
|
pub const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 {
|
||||||
|
@ -1642,12 +1679,14 @@ impl<'a> Layout<'a> {
|
||||||
/// Panics if given a FlexVar or RigidVar, since those should have been
|
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||||
/// monomorphized away already!
|
/// monomorphized away already!
|
||||||
fn from_var(env: &mut Env<'a, '_>, var: Variable) -> Result<Self, LayoutProblem> {
|
fn from_var(env: &mut Env<'a, '_>, var: Variable) -> Result<Self, LayoutProblem> {
|
||||||
|
env.cached_or(var, |env| {
|
||||||
if env.is_seen(var) {
|
if env.is_seen(var) {
|
||||||
Ok(Layout::RecursivePointer)
|
Ok(Layout::RecursivePointer)
|
||||||
} else {
|
} 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)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn safe_to_memcpy(&self) -> bool {
|
pub fn safe_to_memcpy(&self) -> bool {
|
||||||
|
@ -1968,29 +2007,40 @@ impl<'a> Layout<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Avoid recomputing Layout from Variable multiple times.
|
type LayoutResult<'a> = Result<Layout<'a>, LayoutProblem>;
|
||||||
/// We use `ena` for easy snapshots and rollbacks of the cache.
|
type RawFunctionLayoutResult<'a> = Result<RawFunctionLayout<'a>, LayoutProblem>;
|
||||||
/// During specialization, a type variable `a` can be specialized to different layouts,
|
|
||||||
/// e.g. `identity : a -> a` could be specialized to `Bool -> Bool` or `Str -> Str`.
|
/// A single layer of the layout cache.
|
||||||
/// Therefore in general it's invalid to store a map from variables to layouts
|
/// Snapshots are implemented by operating on new layers, and rollbacks by dropping the latest
|
||||||
/// But if we're careful when to invalidate certain keys, we still get some benefit
|
/// layer.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CacheLayer<Result>(MutMap<Variable, Result>);
|
||||||
|
|
||||||
|
impl<Result> Default for CacheLayer<Result> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout cache to avoid recomputing [Layout] from a [Variable] multiple times.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LayoutCache<'a> {
|
pub struct LayoutCache<'a> {
|
||||||
pub target_info: TargetInfo,
|
pub target_info: TargetInfo,
|
||||||
|
cache: std::vec::Vec<CacheLayer<LayoutResult<'a>>>,
|
||||||
|
raw_function_cache: std::vec::Vec<CacheLayer<RawFunctionLayoutResult<'a>>>,
|
||||||
_marker: std::marker::PhantomData<&'a u8>,
|
_marker: std::marker::PhantomData<&'a u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum CachedLayout<'a> {
|
|
||||||
Cached(Layout<'a>),
|
|
||||||
NotCached,
|
|
||||||
Problem(LayoutProblem),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LayoutCache<'a> {
|
impl<'a> LayoutCache<'a> {
|
||||||
pub fn new(target_info: TargetInfo) -> Self {
|
pub fn new(target_info: TargetInfo) -> Self {
|
||||||
|
let mut cache = std::vec::Vec::with_capacity(4);
|
||||||
|
cache.push(CacheLayer::default());
|
||||||
|
let mut raw_cache = std::vec::Vec::with_capacity(4);
|
||||||
|
raw_cache.push(CacheLayer::default());
|
||||||
Self {
|
Self {
|
||||||
target_info,
|
target_info,
|
||||||
|
cache,
|
||||||
|
raw_function_cache: raw_cache,
|
||||||
_marker: Default::default(),
|
_marker: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2012,6 +2062,7 @@ impl<'a> LayoutCache<'a> {
|
||||||
cache: self,
|
cache: self,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// [Layout::from_var] should query the cache!
|
||||||
Layout::from_var(&mut env, var)
|
Layout::from_var(&mut env, var)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2031,18 +2082,88 @@ impl<'a> LayoutCache<'a> {
|
||||||
target_info: self.target_info,
|
target_info: self.target_info,
|
||||||
cache: self,
|
cache: self,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// [Layout::from_var] should query the cache!
|
||||||
RawFunctionLayout::from_var(&mut env, var)
|
RawFunctionLayout::from_var(&mut env, var)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot(&mut self) -> SnapshotKeyPlaceholder {
|
fn get_help<Result: Copy>(
|
||||||
SnapshotKeyPlaceholder
|
cache: &std::vec::Vec<CacheLayer<Result>>,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Option<Result> {
|
||||||
|
let root = subs.get_root_key_without_compacting(var);
|
||||||
|
|
||||||
|
for layer in cache.iter().rev() {
|
||||||
|
// TODO: it's possible that after unification, roots in earlier cache layers changed...
|
||||||
|
// how often does that happen?
|
||||||
|
if let Some(result) = layer.0.get(&root) {
|
||||||
|
return Some(*result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rollback_to(&mut self, _snapshot: SnapshotKeyPlaceholder) {}
|
fn insert_help<Result: Copy>(
|
||||||
|
cache: &mut std::vec::Vec<CacheLayer<Result>>,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
result: Result,
|
||||||
|
) -> Result {
|
||||||
|
let root = subs.get_root_key_without_compacting(var);
|
||||||
|
let layer = cache
|
||||||
|
.last_mut()
|
||||||
|
.expect("cache must have at least one layer");
|
||||||
|
let opt_old_result = layer.0.insert(root, result);
|
||||||
|
debug_assert!(opt_old_result.is_none(), "overwritting cache result");
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, subs: &Subs, var: Variable) -> Option<LayoutResult<'a>> {
|
||||||
|
Self::get_help(&self.cache, subs, var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw_function(&self, subs: &Subs, var: Variable) -> Option<RawFunctionLayoutResult<'a>> {
|
||||||
|
Self::get_help(&self.raw_function_cache, subs, var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, subs: &Subs, var: Variable, result: LayoutResult<'a>) -> LayoutResult<'a> {
|
||||||
|
Self::insert_help(&mut self.cache, subs, var, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_raw_function(
|
||||||
|
&mut self,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
result: RawFunctionLayoutResult<'a>,
|
||||||
|
) -> RawFunctionLayoutResult<'a> {
|
||||||
|
Self::insert_help(&mut self.raw_function_cache, subs, var, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snapshot(&mut self) -> CacheSnapshot {
|
||||||
|
debug_assert_eq!(self.raw_function_cache.len(), self.cache.len());
|
||||||
|
self.cache.push(Default::default());
|
||||||
|
self.raw_function_cache.push(Default::default());
|
||||||
|
CacheSnapshot {
|
||||||
|
layer: self.cache.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rollback_to(&mut self, snapshot: CacheSnapshot) {
|
||||||
|
let CacheSnapshot { layer } = snapshot;
|
||||||
|
|
||||||
|
debug_assert_eq!(self.cache.len(), layer);
|
||||||
|
debug_assert_eq!(self.raw_function_cache.len(), layer);
|
||||||
|
|
||||||
|
self.cache.pop();
|
||||||
|
self.raw_function_cache.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// placeholder for the type ven_ena::unify::Snapshot<ven_ena::unify::InPlace<CachedVariable<'a>>>
|
pub struct CacheSnapshot {
|
||||||
pub struct SnapshotKeyPlaceholder;
|
/// Index of the pushed layer
|
||||||
|
layer: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Layout<'a> {
|
impl<'a> Layout<'a> {
|
||||||
pub fn int_width(width: IntWidth) -> Layout<'a> {
|
pub fn int_width(width: IntWidth) -> Layout<'a> {
|
||||||
|
|
|
@ -198,9 +198,9 @@ fn get_tags_vars_and_variant<'a>(
|
||||||
let vars_of_tag: MutMap<_, _> = tags_vec.iter().cloned().collect();
|
let vars_of_tag: MutMap<_, _> = tags_vec.iter().cloned().collect();
|
||||||
|
|
||||||
let union_variant = {
|
let union_variant = {
|
||||||
let cache = LayoutCache::new(env.target_info);
|
let mut cache = LayoutCache::new(env.target_info);
|
||||||
let mut layout_env =
|
let mut layout_env =
|
||||||
layout::Env::from_components(&cache, env.subs, env.arena, env.target_info);
|
layout::Env::from_components(&mut cache, env.subs, env.arena, env.target_info);
|
||||||
union_sorted_tags_help(&mut layout_env, tags_vec, opt_rec_var)
|
union_sorted_tags_help(&mut layout_env, tags_vec, opt_rec_var)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1201,9 +1201,9 @@ fn byte_to_ast<'a, M: ReplAppMemory>(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let union_variant = {
|
let union_variant = {
|
||||||
let cache = LayoutCache::new(env.target_info);
|
let mut cache = LayoutCache::new(env.target_info);
|
||||||
let mut layout_env = layout::Env::from_components(
|
let mut layout_env = layout::Env::from_components(
|
||||||
&cache,
|
&mut cache,
|
||||||
env.subs,
|
env.subs,
|
||||||
env.arena,
|
env.arena,
|
||||||
env.target_info,
|
env.target_info,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue