mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Inline interners into the layout interner module
I realized that we'll need to make the layout interner more complicated to support things like recursive pointers pointing to their parents and to support lambda set layout caching. Since the layout interner is the only user of intern crate right now anyway, just inline the whole thing.
This commit is contained in:
parent
82e6982196
commit
50826d1a83
32 changed files with 277 additions and 376 deletions
|
@ -15,7 +15,6 @@ roc_types = { path = "../types" }
|
|||
roc_can = { path = "../can" }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_derive = { path = "../derive" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_late_solve = { path = "../late_solve" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_problem = { path = "../problem" }
|
||||
|
@ -30,3 +29,4 @@ bumpalo.workspace = true
|
|||
hashbrown.workspace = true
|
||||
static_assertions.workspace = true
|
||||
bitvec.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{IdentIds, Symbol};
|
||||
|
||||
use crate::ir::{
|
||||
BranchInfo, Call, CallType, Expr, JoinPointId, Literal, Param, Stmt, UpdateModeId,
|
||||
};
|
||||
use crate::layout::{Builtin, InLayout, Layout, STLayoutInterner, TagIdIntType, UnionLayout};
|
||||
use crate::layout::{
|
||||
Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
|
||||
};
|
||||
|
||||
use super::{let_lowlevel, CodeGenHelp, Context, LAYOUT_BOOL};
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_target::TargetInfo;
|
||||
|
@ -9,7 +8,9 @@ use crate::ir::{
|
|||
Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, ModifyRc, Proc, ProcLayout,
|
||||
SelfRecursive, Stmt, UpdateModeId,
|
||||
};
|
||||
use crate::layout::{Builtin, LambdaName, Layout, Niche, STLayoutInterner, UnionLayout};
|
||||
use crate::layout::{
|
||||
Builtin, LambdaName, Layout, LayoutInterner, Niche, STLayoutInterner, UnionLayout,
|
||||
};
|
||||
|
||||
mod equality;
|
||||
mod refcount;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use roc_builtins::bitcode::IntWidth;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::low_level::{LowLevel, LowLevel::*};
|
||||
use roc_module::symbol::{IdentIds, Symbol};
|
||||
use roc_target::PtrWidth;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::{MutMap, VecMap, VecSet};
|
||||
use roc_intern::Interner;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::{
|
||||
|
@ -10,7 +9,9 @@ use crate::{
|
|||
Call, CallSpecId, CallType, Expr, HigherOrderLowLevel, JoinPointId, ListLiteralElement,
|
||||
ModifyRc, Param, Proc, ProcLayout, Stmt,
|
||||
},
|
||||
layout::{Builtin, LambdaSet, Layout, STLayoutInterner, TagIdIntType, UnionLayout},
|
||||
layout::{
|
||||
Builtin, LambdaSet, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
|
||||
},
|
||||
};
|
||||
|
||||
pub enum UseKind {
|
||||
|
|
|
@ -2,12 +2,13 @@ use crate::ir::{
|
|||
build_list_index_probe, BranchInfo, Call, CallType, DestructType, Env, Expr, JoinPointId,
|
||||
ListIndex, Literal, Param, Pattern, Procs, Stmt,
|
||||
};
|
||||
use crate::layout::{Builtin, Layout, LayoutCache, TLLayoutInterner, TagIdIntType, UnionLayout};
|
||||
use crate::layout::{
|
||||
Builtin, Layout, LayoutCache, LayoutInterner, TLLayoutInterner, TagIdIntType, UnionLayout,
|
||||
};
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId, Union};
|
||||
use roc_intern::Interner;
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
|
|
@ -5,7 +5,6 @@ use bumpalo::Bump;
|
|||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::{default_hasher, FnvMap, MutMap};
|
||||
use roc_error_macros::{internal_error, todo_abilities};
|
||||
use roc_intern::Interner;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_problem::can::RuntimeError;
|
||||
|
@ -23,7 +22,9 @@ use std::hash::{Hash, Hasher};
|
|||
use ven_pretty::{DocAllocator, DocBuilder};
|
||||
|
||||
mod intern;
|
||||
pub use intern::{GlobalLayoutInterner, LayoutInterner, STLayoutInterner, TLLayoutInterner};
|
||||
pub use intern::{
|
||||
GlobalLayoutInterner, InLayout, LayoutInterner, STLayoutInterner, TLLayoutInterner,
|
||||
};
|
||||
|
||||
// if your changes cause this number to go down, great!
|
||||
// please change it to the lower number.
|
||||
|
@ -661,10 +662,6 @@ impl FieldOrderHash {
|
|||
}
|
||||
}
|
||||
|
||||
/// An interned layout.
|
||||
// One day I would like to take over `LayoutId` as the name here, but that is currently used elsewhere.
|
||||
pub type InLayout<'a> = roc_intern::Interned<Layout<'a>>;
|
||||
|
||||
/// Types for code gen must be monomorphic. No type variables allowed!
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum Layout<'a> {
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
use std::{marker::PhantomData, sync::Arc};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
hash::{BuildHasher, Hasher},
|
||||
marker::PhantomData,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_intern::{GlobalInterner, Interned, Interner, SingleThreadedInterner, ThreadLocalInterner};
|
||||
use roc_collections::{default_hasher, BumpMap};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
use super::{Builtin, Layout};
|
||||
|
||||
pub struct InternedLayouts(PhantomData<()>);
|
||||
#[allow(unused)] // for now
|
||||
pub struct InLayouts(PhantomData<()>);
|
||||
|
||||
macro_rules! cache_interned_layouts {
|
||||
($($i:literal, $name:ident, $layout:expr)*; $total_constants:literal) => {
|
||||
impl InternedLayouts {
|
||||
impl InLayouts {
|
||||
$(
|
||||
pub const $name: Interned<Layout<'static>> = unsafe { Interned::from_reserved_index($i) };
|
||||
#[allow(unused)] // for now
|
||||
pub const $name: InLayout<'static> = unsafe { InLayout::from_reserved_index($i) };
|
||||
)*
|
||||
}
|
||||
|
||||
fn fill_reserved_layouts<'a>(interner: &mut STLayoutInterner<'a>) {
|
||||
assert!(interner.0.is_empty());
|
||||
assert!(interner.is_empty());
|
||||
$(
|
||||
interner.0.insert(&$layout);
|
||||
interner.insert(&$layout);
|
||||
)*
|
||||
}
|
||||
|
||||
|
@ -59,8 +67,9 @@ cache_interned_layouts! {
|
|||
; 16
|
||||
}
|
||||
|
||||
impl InternedLayouts {
|
||||
pub const fn from_int_width(w: IntWidth) -> Interned<Layout<'static>> {
|
||||
impl InLayouts {
|
||||
#[allow(unused)] // for now
|
||||
pub const fn from_int_width(w: IntWidth) -> InLayout<'static> {
|
||||
match w {
|
||||
IntWidth::U8 => Self::U8,
|
||||
IntWidth::U16 => Self::U16,
|
||||
|
@ -74,7 +83,8 @@ impl InternedLayouts {
|
|||
IntWidth::I128 => Self::I128,
|
||||
}
|
||||
}
|
||||
pub const fn from_float_width(w: FloatWidth) -> Interned<Layout<'static>> {
|
||||
#[allow(unused)] // for now
|
||||
pub const fn from_float_width(w: FloatWidth) -> InLayout<'static> {
|
||||
match w {
|
||||
FloatWidth::F32 => Self::F32,
|
||||
FloatWidth::F64 => Self::F64,
|
||||
|
@ -82,63 +92,248 @@ impl InternedLayouts {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait LayoutInterner<'a>: Interner<'a, Layout<'a>> + Sized {
|
||||
fn alignment_bytes(&self, target_info: TargetInfo, layout: Interned<Layout<'a>>) -> u32 {
|
||||
pub trait LayoutInterner<'a>: Sized {
|
||||
/// Interns a value, returning its interned representation.
|
||||
/// If the value has been interned before, the old interned representation will be re-used.
|
||||
///
|
||||
/// Note that the provided value must be allocated into an arena of your choosing, but which
|
||||
/// must live at least as long as the interner lives.
|
||||
// TODO: we should consider maintaining our own arena in the interner, to avoid redundant
|
||||
// allocations when values already have interned representations.
|
||||
fn insert(&mut self, value: &'a Layout<'a>) -> InLayout<'a>;
|
||||
|
||||
/// Retrieves a value from the interner.
|
||||
fn get(&self, key: InLayout<'a>) -> &'a Layout<'a>;
|
||||
|
||||
fn alignment_bytes(&self, target_info: TargetInfo, layout: InLayout<'a>) -> u32 {
|
||||
self.get(layout).alignment_bytes(self, target_info)
|
||||
}
|
||||
}
|
||||
|
||||
/// An interned layout.
|
||||
///
|
||||
/// When possible, prefer comparing/hashing on the [InLayout] representation of a value, rather
|
||||
/// than the value itself.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct InLayout<'a>(usize, std::marker::PhantomData<&'a ()>);
|
||||
impl<'a> Clone for InLayout<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Copy for InLayout<'a> {}
|
||||
|
||||
impl<'a> InLayout<'a> {
|
||||
/// # Safety
|
||||
///
|
||||
/// The index is not guaranteed to exist. Use this only when creating an interner with constant
|
||||
/// indices, with the variant that `insert` returns a monotonically increasing index.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```ignore(illustrative)
|
||||
/// let reserved_interned = InLayout::from_reserved_index(0);
|
||||
/// let interner = GlobalLayoutInterner::with_capacity(1);
|
||||
/// let inserted = interner.insert("something");
|
||||
/// assert_eq!(reserved_interned, inserted);
|
||||
/// ```
|
||||
const unsafe fn from_reserved_index(index: usize) -> Self {
|
||||
Self(index, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
/// A concurrent interner, suitable for usage between threads.
|
||||
///
|
||||
/// The interner does not currently maintain its own arena; you will have to supply
|
||||
/// values-to-be-interned as allocated in an independent arena.
|
||||
///
|
||||
/// If you need a concurrent global interner, you'll likely want each thread to take a
|
||||
/// [TLLayoutInterner] via [GlobalLayoutInterner::fork], for caching purposes.
|
||||
///
|
||||
/// Originally derived from https://gist.github.com/matklad/44ba1a5a6168bc0c26c995131c007907;
|
||||
/// thank you, Aleksey!
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalLayoutInterner<'a>(Arc<GlobalInterner<'a, Layout<'a>>>);
|
||||
pub struct GlobalLayoutInterner<'a>(Arc<GlobalLayoutInternerInner<'a>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TLLayoutInterner<'a>(ThreadLocalInterner<'a, Layout<'a>>);
|
||||
struct GlobalLayoutInternerInner<'a> {
|
||||
map: Mutex<BumpMap<&'a Layout<'a>, InLayout<'a>>>,
|
||||
vec: RwLock<Vec<&'a Layout<'a>>>,
|
||||
}
|
||||
|
||||
/// A derivative of a [GlobalLayoutInterner] interner that provides caching desirable for
|
||||
/// thread-local workloads. The only way to get a [TLLayoutInterner] is via
|
||||
/// [GlobalLayoutInterner::fork].
|
||||
///
|
||||
/// All values interned into a [TLLayoutInterner] are made available in its parent
|
||||
/// [GlobalLayoutInterner], making this suitable for global sharing of interned values.
|
||||
///
|
||||
/// Originally derived from https://gist.github.com/matklad/44ba1a5a6168bc0c26c995131c007907;
|
||||
/// thank you, Aleksey!
|
||||
#[derive(Debug)]
|
||||
pub struct STLayoutInterner<'a>(SingleThreadedInterner<'a, Layout<'a>>);
|
||||
pub struct TLLayoutInterner<'a> {
|
||||
parent: GlobalLayoutInterner<'a>,
|
||||
map: BumpMap<&'a Layout<'a>, InLayout<'a>>,
|
||||
/// Cache of interned values from the parent for local access.
|
||||
vec: RefCell<Vec<Option<&'a Layout<'a>>>>,
|
||||
}
|
||||
|
||||
/// A single-threaded interner, with no concurrency properties.
|
||||
///
|
||||
/// The only way to construct such an interner is to collapse a shared [GlobalLayoutInterner] into
|
||||
/// a [STLayoutInterner], via [GlobalLayoutInterner::unwrap].
|
||||
#[derive(Debug)]
|
||||
pub struct STLayoutInterner<'a> {
|
||||
map: BumpMap<&'a Layout<'a>, InLayout<'a>>,
|
||||
vec: Vec<&'a Layout<'a>>,
|
||||
}
|
||||
|
||||
/// Generic hasher for a value, to be used by all interners.
|
||||
///
|
||||
/// This uses the [default_hasher], so interner maps should also rely on [default_hasher].
|
||||
fn hash<V: std::hash::Hash>(val: V) -> u64 {
|
||||
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
|
||||
val.hash(&mut state);
|
||||
state.finish()
|
||||
}
|
||||
|
||||
impl<'a> GlobalLayoutInterner<'a> {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
STLayoutInterner::with_capacity(capacity).into_global()
|
||||
/// Creates a new global interner with the given capacity.
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
STLayoutInterner::with_capacity(cap).into_global()
|
||||
}
|
||||
|
||||
/// Creates a derivative [TLLayoutInterner] pointing back to this global interner.
|
||||
pub fn fork(&self) -> TLLayoutInterner<'a> {
|
||||
TLLayoutInterner(self.0.fork())
|
||||
}
|
||||
pub fn unwrap(self) -> Result<STLayoutInterner<'a>, Self> {
|
||||
match self.0.unwrap() {
|
||||
Ok(st) => Ok(STLayoutInterner(st)),
|
||||
Err(global) => Err(Self(global)),
|
||||
TLLayoutInterner {
|
||||
parent: Self(Arc::clone(&self.0)),
|
||||
map: Default::default(),
|
||||
vec: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Collapses a shared [GlobalLayoutInterner] into a [STLayoutInterner].
|
||||
///
|
||||
/// Returns an [Err] with `self` if there are outstanding references to the [GlobalLayoutInterner].
|
||||
pub fn unwrap(self) -> Result<STLayoutInterner<'a>, Self> {
|
||||
let GlobalLayoutInternerInner { map, vec } = match Arc::try_unwrap(self.0) {
|
||||
Ok(inner) => inner,
|
||||
Err(li) => return Err(Self(li)),
|
||||
};
|
||||
let map = Mutex::into_inner(map);
|
||||
let vec = RwLock::into_inner(vec);
|
||||
Ok(STLayoutInterner { map, vec })
|
||||
}
|
||||
|
||||
/// Interns a value with a pre-computed hash.
|
||||
/// Prefer calling this when possible, especially from [TLLayoutInterner], to avoid
|
||||
/// re-computing hashes.
|
||||
fn insert_hashed(&self, value: &'a Layout<'a>, hash: u64) -> InLayout<'a> {
|
||||
let mut map = self.0.map.lock();
|
||||
let (_, interned) = map
|
||||
.raw_entry_mut()
|
||||
.from_key_hashed_nocheck(hash, &value)
|
||||
.or_insert_with(|| {
|
||||
let mut vec = self.0.vec.write();
|
||||
let interned = InLayout(vec.len(), Default::default());
|
||||
vec.push(value);
|
||||
(value, interned)
|
||||
});
|
||||
*interned
|
||||
}
|
||||
|
||||
fn get(&self, interned: InLayout<'a>) -> &'a Layout<'a> {
|
||||
let InLayout(index, _) = interned;
|
||||
self.0.vec.read()[index]
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.vec.read().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TLLayoutInterner<'a> {
|
||||
/// Records an interned value in thread-specific storage, for faster access on lookups.
|
||||
fn record(&self, key: &'a Layout<'a>, interned: InLayout<'a>) {
|
||||
let mut vec = self.vec.borrow_mut();
|
||||
let len = vec.len().max(interned.0 + 1);
|
||||
vec.resize(len, None);
|
||||
vec[interned.0] = Some(key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LayoutInterner<'a> for TLLayoutInterner<'a> {
|
||||
fn insert(&mut self, value: &'a Layout<'a>) -> InLayout<'a> {
|
||||
let global = &self.parent;
|
||||
let hash = hash(value);
|
||||
let (&mut value, &mut interned) = self
|
||||
.map
|
||||
.raw_entry_mut()
|
||||
.from_key_hashed_nocheck(hash, &value)
|
||||
.or_insert_with(|| {
|
||||
let interned = global.insert_hashed(value, hash);
|
||||
(value, interned)
|
||||
});
|
||||
self.record(value, interned);
|
||||
interned
|
||||
}
|
||||
|
||||
fn get(&self, key: InLayout<'a>) -> &'a Layout<'a> {
|
||||
if let Some(Some(value)) = self.vec.borrow().get(key.0) {
|
||||
return value;
|
||||
}
|
||||
let value = self.parent.get(key);
|
||||
self.record(value, key);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> STLayoutInterner<'a> {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
let mut interner = Self(SingleThreadedInterner::with_capacity(capacity));
|
||||
/// Creates a new single threaded interner with the given capacity.
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
let mut interner = Self {
|
||||
map: BumpMap::with_capacity_and_hasher(cap, default_hasher()),
|
||||
vec: Vec::with_capacity(cap),
|
||||
};
|
||||
fill_reserved_layouts(&mut interner);
|
||||
interner
|
||||
}
|
||||
|
||||
/// Promotes the [STLayoutInterner] back to a [GlobalLayoutInterner].
|
||||
///
|
||||
/// You should *only* use this if you need to go from a single-threaded to a concurrent context,
|
||||
/// or in a case where you explicitly need access to [TLLayoutInterner]s.
|
||||
pub fn into_global(self) -> GlobalLayoutInterner<'a> {
|
||||
GlobalLayoutInterner(self.0.into_global())
|
||||
let STLayoutInterner { map, vec } = self;
|
||||
GlobalLayoutInterner(Arc::new(GlobalLayoutInternerInner {
|
||||
map: Mutex::new(map),
|
||||
vec: RwLock::new(vec),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.vec.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Interner<'a, Layout<'a>> for TLLayoutInterner<'a> {
|
||||
fn insert(&mut self, value: &'a Layout<'a>) -> Interned<Layout<'a>> {
|
||||
self.0.insert(value)
|
||||
impl<'a> LayoutInterner<'a> for STLayoutInterner<'a> {
|
||||
fn insert(&mut self, value: &'a Layout<'a>) -> InLayout<'a> {
|
||||
let hash = hash(value);
|
||||
let (_, interned) = self
|
||||
.map
|
||||
.raw_entry_mut()
|
||||
.from_key_hashed_nocheck(hash, value)
|
||||
.or_insert_with(|| {
|
||||
let interned = InLayout(self.vec.len(), Default::default());
|
||||
self.vec.push(value);
|
||||
(value, interned)
|
||||
});
|
||||
*interned
|
||||
}
|
||||
|
||||
fn get(&self, key: Interned<Layout<'a>>) -> &'a Layout<'a> {
|
||||
self.0.get(key)
|
||||
fn get(&self, key: InLayout<'a>) -> &'a Layout<'a> {
|
||||
let InLayout(index, _) = key;
|
||||
self.vec[index]
|
||||
}
|
||||
}
|
||||
impl<'a> LayoutInterner<'a> for TLLayoutInterner<'a> {}
|
||||
|
||||
impl<'a> Interner<'a, Layout<'a>> for STLayoutInterner<'a> {
|
||||
fn insert(&mut self, value: &'a Layout<'a>) -> Interned<Layout<'a>> {
|
||||
self.0.insert(value)
|
||||
}
|
||||
|
||||
fn get(&self, key: Interned<Layout<'a>>) -> &'a Layout<'a> {
|
||||
self.0.get(key)
|
||||
}
|
||||
}
|
||||
impl<'a> LayoutInterner<'a> for STLayoutInterner<'a> {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue