Support lambda sets with recursive pointers and their equivalence-checking

This commit is contained in:
Ayaz Hafiz 2023-01-25 17:57:49 -06:00
parent fa47e82d72
commit 478d4a2d44
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
4 changed files with 263 additions and 81 deletions

View file

@ -1523,35 +1523,7 @@ impl<'a> LambdaSet<'a> {
where
I: LayoutInterner<'a>,
{
if left == right {
return true;
}
let left = interner.get(*left);
let right = interner.get(*right);
let left = if matches!(left, Layout::RecursivePointer(_)) {
let runtime_repr = self.runtime_representation();
debug_assert!(matches!(
interner.get(runtime_repr),
Layout::Union(UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. })
));
Layout::LambdaSet(*self)
} else {
left
};
let right = if matches!(right, Layout::RecursivePointer(_)) {
let runtime_repr = self.runtime_representation();
debug_assert!(matches!(
interner.get(runtime_repr),
Layout::Union(UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. })
));
Layout::LambdaSet(*self)
} else {
right
};
left == right
interner.equiv(*left, *right)
}
fn layout_for_member<I, F>(&self, interner: &I, comparator: F) -> ClosureRepresentation<'a>
@ -1564,7 +1536,7 @@ impl<'a> LambdaSet<'a> {
return ClosureRepresentation::UnwrappedCapture(self.representation);
}
let repr = interner.get(self.representation);
let repr = interner.chase_recursive(self.representation);
match repr {
Layout::Union(union) => {
@ -1648,7 +1620,7 @@ impl<'a> LambdaSet<'a> {
ClosureRepresentation::AlphabeticOrderStruct(fields)
}
layout => {
debug_assert!(self.has_enum_dispatch_repr(),);
debug_assert!(self.has_enum_dispatch_repr());
let enum_repr = match layout {
Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool,
Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8,
@ -1675,7 +1647,7 @@ impl<'a> LambdaSet<'a> {
return ClosureCallOptions::UnwrappedCapture(self.representation);
}
let repr = interner.get(self.representation);
let repr = interner.chase_recursive(self.representation);
match repr {
Layout::Union(union_layout) => {
@ -1770,7 +1742,7 @@ impl<'a> LambdaSet<'a> {
Cacheable(result, criteria)
});
match result.map(|l| env.cache.get_in(l)) {
match result.map(|l| env.cache.interner.chase_recursive(l)) {
Ok(Layout::LambdaSet(lambda_set)) => Cacheable(Ok(lambda_set), criteria),
Err(err) => Cacheable(Err(err), criteria),
Ok(layout) => internal_error!("other layout found for lambda set: {:?}", layout),
@ -1810,6 +1782,7 @@ impl<'a> LambdaSet<'a> {
Vec::with_capacity_in(lambdas.len(), env.arena);
let mut set_with_variables: std::vec::Vec<(&Symbol, &[Variable])> =
std::vec::Vec::with_capacity(lambdas.len());
let mut set_captures_have_naked_rec_ptr = false;
let mut last_function_symbol = None;
let mut lambdas_it = lambdas.iter().peekable();
@ -1828,6 +1801,8 @@ impl<'a> LambdaSet<'a> {
let mut criteria = CACHEABLE;
let arg = cached!(Layout::from_var(env, *var), criteria);
arguments.push(arg);
set_captures_have_naked_rec_ptr =
set_captures_have_naked_rec_ptr || criteria.has_naked_recursion_pointer;
}
let arguments = arguments.into_bump_slice();
@ -1889,9 +1864,11 @@ impl<'a> LambdaSet<'a> {
cache_criteria.and(criteria);
let lambda_set = env.cache.interner.insert_lambda_set(
env.arena,
fn_args,
ret,
env.arena.alloc(set.into_bump_slice()),
set_captures_have_naked_rec_ptr,
representation,
);
@ -1901,9 +1878,11 @@ impl<'a> LambdaSet<'a> {
// 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.
let lambda_set = env.cache.interner.insert_lambda_set(
env.arena,
fn_args,
ret,
&(&[] as &[(Symbol, &[InLayout])]),
false,
Layout::UNIT,
);
Cacheable(Ok(lambda_set), cache_criteria)

View file

@ -135,9 +135,11 @@ pub trait LayoutInterner<'a>: Sized {
/// lambda set onto itself.
fn insert_lambda_set(
&mut self,
arena: &'a Bump,
args: &'a &'a [InLayout<'a>],
ret: InLayout<'a>,
set: &'a &'a [(Symbol, &'a [InLayout<'a>])],
set_may_have_naked_rec_ptr: bool,
representation: InLayout<'a>,
) -> LambdaSet<'a>;
@ -515,40 +517,65 @@ impl<'a> GlobalLayoutInterner<'a> {
fn get_or_insert_hashed_normalized_lambda_set(
&self,
arena: &'a Bump,
normalized: LambdaSet<'a>,
set_may_have_naked_rec_ptr: bool,
normalized_hash: u64,
) -> WrittenGlobalLambdaSet<'a> {
let mut normalized_lambda_set_map = self.0.normalized_lambda_set_map.lock();
let (_, full_lambda_set) = normalized_lambda_set_map
.raw_entry_mut()
if let Some((_, &full_lambda_set)) = normalized_lambda_set_map
.raw_entry()
.from_key_hashed_nocheck(normalized_hash, &normalized)
.or_insert_with(|| {
// We don't already have an entry for the lambda set, which means it must be new to
// the world. Reserve a slot, insert the lambda set, and that should fill the slot
// in.
let mut map = self.0.map.lock();
let mut vec = self.0.vec.write();
{
let full_layout = self.0.vec.read()[full_lambda_set.full_layout.0];
return WrittenGlobalLambdaSet {
full_lambda_set,
full_layout,
};
}
let slot = unsafe { InLayout::from_index(vec.len()) };
// We don't already have an entry for the lambda set, which means it must be new to
// the world. Reserve a slot, insert the lambda set, and that should fill the slot
// in.
let mut map = self.0.map.lock();
let mut vec = self.0.vec.write();
let lambda_set = LambdaSet {
full_layout: slot,
..normalized
};
let lambda_set_layout = Layout::LambdaSet(lambda_set);
let slot = unsafe { InLayout::from_index(vec.len()) };
vec.push(Layout::VOID_NAKED);
vec.push(lambda_set_layout);
let set = if set_may_have_naked_rec_ptr {
let mut interner = LockedGlobalInterner {
map: &mut map,
normalized_lambda_set_map: &mut normalized_lambda_set_map,
vec: &mut vec,
target_info: self.0.target_info,
};
let done = reify::reify_lambda_set_captures(arena, &mut interner, slot, normalized.set);
done
} else {
normalized.set
};
// TODO: Is it helpful to persist the hash and give it back to the thread-local
// interner?
let _old = map.insert(lambda_set_layout, slot);
debug_assert!(_old.is_none());
let full_lambda_set = LambdaSet {
full_layout: slot,
set,
..normalized
};
let lambda_set_layout = Layout::LambdaSet(full_lambda_set);
(normalized, lambda_set)
});
let full_layout = self.0.vec.read()[full_lambda_set.full_layout.0];
vec[slot.0] = lambda_set_layout;
// TODO: Is it helpful to persist the hash and give it back to the thread-local
// interner?
let _old = map.insert(lambda_set_layout, slot);
debug_assert!(_old.is_none());
let _old_normalized = normalized_lambda_set_map.insert(normalized, full_lambda_set);
debug_assert!(_old_normalized.is_none());
let full_layout = vec[full_lambda_set.full_layout.0];
WrittenGlobalLambdaSet {
full_lambda_set: *full_lambda_set,
full_lambda_set,
full_layout,
}
}
@ -647,9 +674,11 @@ impl<'a> LayoutInterner<'a> for TLLayoutInterner<'a> {
fn insert_lambda_set(
&mut self,
arena: &'a Bump,
args: &'a &'a [InLayout<'a>],
ret: InLayout<'a>,
set: &'a &'a [(Symbol, &'a [InLayout<'a>])],
set_may_have_naked_rec_ptr: bool,
representation: InLayout<'a>,
) -> LambdaSet<'a> {
// The tricky bit of inserting a lambda set is we need to fill in the `full_layout` only
@ -675,7 +704,12 @@ impl<'a> LayoutInterner<'a> for TLLayoutInterner<'a> {
let WrittenGlobalLambdaSet {
full_lambda_set,
full_layout,
} = global.get_or_insert_hashed_normalized_lambda_set(normalized, normalized_hash);
} = global.get_or_insert_hashed_normalized_lambda_set(
arena,
normalized,
set_may_have_naked_rec_ptr,
normalized_hash,
);
// The Layout(lambda_set) isn't present in our thread; make sure it is for future
// reference.
@ -797,9 +831,11 @@ macro_rules! st_impl {
fn insert_lambda_set(
&mut self,
arena: &'a Bump,
args: &'a &'a [InLayout<'a>],
ret: InLayout<'a>,
set: &'a &'a [(Symbol, &'a [InLayout<'a>])],
set_may_have_naked_rec_ptr: bool,
representation: InLayout<'a>,
) -> LambdaSet<'a> {
// IDEA:
@ -816,6 +852,14 @@ macro_rules! st_impl {
// This lambda set must be new to the interner, reserve a slot and fill it in.
let slot = unsafe { InLayout::from_index(self.vec.len()) };
self.vec.push(Layout::VOID_NAKED);
let set = if set_may_have_naked_rec_ptr {
reify::reify_lambda_set_captures(arena, self, slot, set)
} else {
set
};
let lambda_set = LambdaSet {
args,
ret,
@ -823,11 +867,14 @@ macro_rules! st_impl {
representation,
full_layout: slot,
};
let filled_slot = self.insert(Layout::LambdaSet(lambda_set));
assert_eq!(slot, filled_slot);
self.vec[slot.0] = Layout::LambdaSet(lambda_set);
self.normalized_lambda_set_map
let _old = self.map.insert(Layout::LambdaSet(lambda_set), slot);
debug_assert!(_old.is_none());
let _old = self.normalized_lambda_set_map
.insert(normalized_lambda_set, lambda_set);
debug_assert!(_old.is_none());
lambda_set
}
@ -876,6 +923,7 @@ st_impl!('r LockedGlobalInterner);
mod reify {
use bumpalo::{collections::Vec, Bump};
use roc_module::symbol::Symbol;
use crate::layout::{Builtin, LambdaSet, Layout, UnionLayout};
@ -1020,12 +1068,33 @@ mod reify {
};
let representation = reify_layout(arena, interner, slot, representation);
interner.insert_lambda_set(arena.alloc(args), ret, arena.alloc(set), representation)
interner.insert_lambda_set(
arena,
arena.alloc(args),
ret,
arena.alloc(set),
true,
representation,
)
}
pub fn reify_lambda_set_captures<'a>(
arena: &'a Bump,
interner: &mut impl LayoutInterner<'a>,
slot: InLayout<'a>,
set: &[(Symbol, &'a [InLayout<'a>])],
) -> &'a &'a [(Symbol, &'a [InLayout<'a>])] {
let mut reified_set = Vec::with_capacity_in(set.len(), arena);
for (f, captures) in set.iter() {
let reified_captures = reify_layout_slice(arena, interner, slot, captures);
reified_set.push((*f, reified_captures));
}
arena.alloc(reified_set.into_bump_slice())
}
}
mod equiv {
use crate::layout::{Layout, UnionLayout};
use crate::layout::{self, Layout, UnionLayout};
use super::{InLayout, LayoutInterner};
@ -1169,6 +1238,7 @@ mod equiv {
#[cfg(test)]
mod insert_lambda_set {
use bumpalo::Bump;
use roc_module::symbol::Symbol;
use roc_target::TargetInfo;
@ -1184,48 +1254,53 @@ mod insert_lambda_set {
#[test]
fn two_threads_write() {
let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO);
let set = TEST_SET;
let repr = Layout::UNIT;
for _ in 0..100 {
let mut handles = Vec::with_capacity(10);
for _ in 0..10 {
let mut interner = global.fork();
handles.push(std::thread::spawn(move || {
interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr)
}))
}
let ins: Vec<LambdaSet> = handles.into_iter().map(|t| t.join().unwrap()).collect();
let interned = ins[0];
assert!(ins.iter().all(|in2| interned == *in2));
let mut arenas: Vec<_> = std::iter::repeat_with(Bump::new).take(10).collect();
let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO);
let set = TEST_SET;
let repr = Layout::UNIT;
std::thread::scope(|s| {
let mut handles = Vec::with_capacity(10);
for arena in arenas.iter_mut() {
let mut interner = global.fork();
handles.push(s.spawn(move || {
interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, true, repr)
}))
}
let ins: Vec<LambdaSet> = handles.into_iter().map(|t| t.join().unwrap()).collect();
let interned = ins[0];
assert!(ins.iter().all(|in2| interned == *in2));
});
}
}
#[test]
fn insert_then_reintern() {
let arena = &Bump::new();
let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO);
let mut interner = global.fork();
let lambda_set = interner.insert_lambda_set(TEST_ARGS, TEST_RET, TEST_SET, Layout::UNIT);
let lambda_set =
interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, TEST_SET, true, Layout::UNIT);
let lambda_set_layout_in = interner.insert(Layout::LambdaSet(lambda_set));
assert_eq!(lambda_set.full_layout, lambda_set_layout_in);
}
#[test]
fn write_global_then_single_threaded() {
let arena = &Bump::new();
let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO);
let set = TEST_SET;
let repr = Layout::UNIT;
let in1 = {
let mut interner = global.fork();
interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr)
interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, true, repr)
};
let in2 = {
let mut st_interner = global.unwrap().unwrap();
st_interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr)
st_interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, true, repr)
};
assert_eq!(in1, in2);
@ -1233,18 +1308,19 @@ mod insert_lambda_set {
#[test]
fn write_single_threaded_then_global() {
let arena = &Bump::new();
let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO);
let mut st_interner = global.unwrap().unwrap();
let set = TEST_SET;
let repr = Layout::UNIT;
let in1 = st_interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr);
let in1 = st_interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, true, repr);
let global = st_interner.into_global();
let mut interner = global.fork();
let in2 = interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr);
let in2 = interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, true, repr);
assert_eq!(in1, in2);
}