Store interened wrapped layout of lambda set on LambdaSet struct

The `LambdaSet` struct is frequently used independently to examine how a
lambda set should be packed or unpacked. However, it is also often
converted into a full layout via `Layout::LambdaSet(LambdaSet)` to be a
part of function arguments, for example.

In preparing to intern all layouts, we need a way to cheaply go from a
`lambda_set` to an interned `Layout::LambdaSet(lambda_set)`, since this
is a very common operation. The proposed solution is to keep the wrapped
layout cached on `LambdaSet` itself, which this PR does.

The tricky bit of inserting a lambda set is we need to fill in the
interned `full_layout` only after the lambda set is inserted,
but we don't want to allocate a new interned slot if the same lambda set
layout has already been inserted with a different `full_layout` slot.

For example, if we insert `LambdaSet { set : [A] }` twice in two
different threads, we want the `full_layout` they map to to be the same.
So we nede to check if an interned representation with a full_layout
exists, before we allocate a new full_layout and insert a fresh lambda
set.

So,
  - check if the "normalized" lambda set (with a void full_layout slot) maps to an
    inserted lambda set in
    - in a thread-local cache, or globally
  - if so, use that one immediately
  - otherwise, allocate a new (global) slot, intern the lambda set, and then fill the slot in
    - save the interned layout and lambda set mapping thread-locally
This commit is contained in:
Ayaz Hafiz 2023-01-03 16:02:22 -06:00
parent ce717dca8b
commit 26f08c999c
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
3 changed files with 177 additions and 18 deletions

View file

@ -1,4 +1,5 @@
use crate::ir::Parens;
use crate::layout::intern::InLayouts;
use bitvec::vec::BitVec;
use bumpalo::collections::Vec;
use bumpalo::Bump;
@ -1336,9 +1337,12 @@ impl<'a> LambdaName<'a> {
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LambdaSet<'a> {
/// collection of function names and their closure arguments
pub(crate) set: &'a [(Symbol, &'a [InLayout<'a>])],
// Double reference to cut from fat slice (16 bytes) to 8 bytes
pub(crate) set: &'a &'a [(Symbol, &'a [InLayout<'a>])],
/// how the closure will be represented at runtime
pub(crate) representation: InLayout<'a>,
/// The interned [Layout] representation of the lambda set, as `Layout::LambdaSet(self)`.
pub(crate) full_layout: InLayout<'a>,
}
#[derive(Debug)]
@ -1864,21 +1868,21 @@ impl<'a> LambdaSet<'a> {
);
let representation = env.cache.interner.insert(representation);
Cacheable(
Ok(LambdaSet {
set: set.into_bump_slice(),
representation,
}),
criteria,
)
let lambda_set = env
.cache
.interner
.insert_lambda_set(env.arena.alloc(set.into_bump_slice()), representation);
Cacheable(Ok(lambda_set), criteria)
}
ResolvedLambdaSet::Unbound => {
// The lambda set is unbound which means it must be unused. Just give it the empty lambda set.
// See also https://github.com/roc-lang/roc/issues/3163.
cacheable(Ok(LambdaSet {
set: &[],
representation: env.cache.interner.insert(Layout::UNIT),
}))
let lambda_set = env
.cache
.interner
.insert_lambda_set(&(&[] as &[(Symbol, &[InLayout])]), InLayouts::UNIT);
cacheable(Ok(lambda_set))
}
}
}
@ -4384,6 +4388,8 @@ where
#[cfg(test)]
mod test {
use crate::layout::intern::InLayouts;
use super::*;
#[test]
@ -4391,8 +4397,9 @@ mod test {
let mut interner = STLayoutInterner::with_capacity(4);
let lambda_set = LambdaSet {
set: &[(Symbol::LIST_MAP, &[])],
set: &(&[(Symbol::LIST_MAP, &[] as &[InLayout])] as &[(Symbol, &[InLayout])]),
representation: interner.insert(Layout::UNIT),
full_layout: InLayouts::VOID,
};
let a = &[Layout::UNIT] as &[_];