mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Merge pull request #4848 from roc-lang/intern-layouts-4
Inline interners into the layout interner module
This commit is contained in:
commit
6989eca6f0
32 changed files with 314 additions and 378 deletions
|
@ -8,7 +8,6 @@ version = "0.0.1"
|
|||
[dependencies]
|
||||
morphic_lib = {path = "../../vendor/morphic_lib"}
|
||||
roc_collections = {path = "../collections"}
|
||||
roc_intern = {path = "../intern"}
|
||||
roc_module = {path = "../module"}
|
||||
roc_mono = {path = "../mono"}
|
||||
roc_debug_flags = {path = "../debug_flags"}
|
||||
|
|
|
@ -6,7 +6,6 @@ use morphic_lib::{
|
|||
TypeDefBuilder, TypeId, TypeName, UpdateModeVar, ValueId,
|
||||
};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_intern::Interner;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
|
@ -15,7 +14,8 @@ use roc_mono::ir::{
|
|||
Literal, ModifyRc, OptLevel, Proc, ProcLayout, SingleEntryPoint, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{
|
||||
Builtin, FieldOrderHash, Layout, Niche, RawFunctionLayout, STLayoutInterner, UnionLayout,
|
||||
Builtin, FieldOrderHash, Layout, LayoutInterner, Niche, RawFunctionLayout, STLayoutInterner,
|
||||
UnionLayout,
|
||||
};
|
||||
|
||||
// just using one module for now
|
||||
|
|
|
@ -8,7 +8,6 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_problem = { path = "../problem" }
|
||||
|
|
|
@ -9,7 +9,6 @@ edition = "2021"
|
|||
[dependencies]
|
||||
roc_alias_analysis = { path = "../alias_analysis" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
|
|
|
@ -38,15 +38,14 @@ use roc_debug_flags::dbg_do;
|
|||
#[cfg(debug_assertions)]
|
||||
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::ir::{
|
||||
BranchInfo, CallType, CrashTag, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc,
|
||||
OptLevel, ProcLayout, SingleEntryPoint,
|
||||
};
|
||||
use roc_mono::layout::{
|
||||
Builtin, LambdaName, LambdaSet, Layout, LayoutIds, Niche, RawFunctionLayout, STLayoutInterner,
|
||||
TagIdIntType, UnionLayout,
|
||||
Builtin, LambdaName, LambdaSet, Layout, LayoutIds, LayoutInterner, Niche, RawFunctionLayout,
|
||||
STLayoutInterner, TagIdIntType, UnionLayout,
|
||||
};
|
||||
use roc_std::RocDec;
|
||||
use roc_target::{PtrWidth, TargetInfo};
|
||||
|
|
|
@ -10,9 +10,8 @@ use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, Str
|
|||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use morphic_lib::UpdateMode;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, InLayout, Layout, LayoutIds, STLayoutInterner};
|
||||
use roc_mono::layout::{Builtin, InLayout, Layout, LayoutIds, LayoutInterner, STLayoutInterner};
|
||||
|
||||
use super::bitcode::{call_list_bitcode_fn, BitcodeReturns};
|
||||
use super::build::{
|
||||
|
|
|
@ -12,9 +12,10 @@ use inkwell::values::{
|
|||
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_intern::Interner;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, InLayout, Layout, LayoutIds, STLayoutInterner, UnionLayout};
|
||||
use roc_mono::layout::{
|
||||
Builtin, InLayout, Layout, LayoutIds, LayoutInterner, STLayoutInterner, UnionLayout,
|
||||
};
|
||||
|
||||
use super::build::{load_roc_value, use_roc_value, BuilderExt};
|
||||
use super::convert::argument_type_from_union_layout;
|
||||
|
|
|
@ -5,8 +5,9 @@ use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
|
|||
use inkwell::values::StructValue;
|
||||
use inkwell::AddressSpace;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_intern::Interner;
|
||||
use roc_mono::layout::{round_up_to_alignment, Builtin, Layout, STLayoutInterner, UnionLayout};
|
||||
use roc_mono::layout::{
|
||||
round_up_to_alignment, Builtin, Layout, LayoutInterner, STLayoutInterner, UnionLayout,
|
||||
};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
fn basic_type_from_record<'a, 'ctx, 'env>(
|
||||
|
|
|
@ -9,10 +9,9 @@ use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
|
|||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
|
||||
use inkwell::AddressSpace;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::LookupType;
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, STLayoutInterner, UnionLayout};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, LayoutInterner, STLayoutInterner, UnionLayout};
|
||||
use roc_region::all::Region;
|
||||
|
||||
use super::build::BuilderExt;
|
||||
|
|
|
@ -9,11 +9,10 @@ use inkwell::{
|
|||
use morphic_lib::{FuncSpec, UpdateMode};
|
||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::{low_level::LowLevel, symbol::Symbol};
|
||||
use roc_mono::{
|
||||
ir::HigherOrderLowLevel,
|
||||
layout::{Builtin, LambdaSet, Layout, LayoutIds, STLayoutInterner},
|
||||
layout::{Builtin, LambdaSet, Layout, LayoutIds, LayoutInterner, STLayoutInterner},
|
||||
};
|
||||
use roc_target::PtrWidth;
|
||||
|
||||
|
|
|
@ -15,10 +15,11 @@ use inkwell::values::{
|
|||
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
|
||||
};
|
||||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use roc_intern::Interner;
|
||||
use roc_module::symbol::Interns;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, InLayout, Layout, LayoutIds, STLayoutInterner, UnionLayout};
|
||||
use roc_mono::layout::{
|
||||
Builtin, InLayout, Layout, LayoutIds, LayoutInterner, STLayoutInterner, UnionLayout,
|
||||
};
|
||||
|
||||
use super::build::{cast_if_necessary_for_opaque_recursive_pointers, load_roc_value, FunctionSpec};
|
||||
use super::convert::{argument_type_from_layout, argument_type_from_union_layout};
|
||||
|
|
|
@ -9,7 +9,6 @@ description = "Provides the WASM backend to generate Roc binaries."
|
|||
[dependencies]
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
|
|
|
@ -4,7 +4,6 @@ use bumpalo::collections::{String, Vec};
|
|||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX};
|
||||
|
@ -12,7 +11,9 @@ use roc_mono::ir::{
|
|||
BranchInfo, CallType, CrashTag, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc,
|
||||
Param, Proc, ProcLayout, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, STLayoutInterner, TagIdIntType, UnionLayout};
|
||||
use roc_mono::layout::{
|
||||
Builtin, Layout, LayoutIds, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
|
||||
};
|
||||
use roc_std::RocDec;
|
||||
|
||||
use roc_wasm_module::linking::{DataSymbol, WasmObjectSymbol};
|
||||
|
|
|
@ -2,12 +2,11 @@ use bumpalo::collections::Vec;
|
|||
use bumpalo::Bump;
|
||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_intern::Interner;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::code_gen_help::HelperOp;
|
||||
use roc_mono::ir::{HigherOrderLowLevel, PassedFunction, ProcLayout};
|
||||
use roc_mono::layout::{Builtin, FieldOrderHash, InLayout, Layout, UnionLayout};
|
||||
use roc_mono::layout::{Builtin, FieldOrderHash, InLayout, Layout, LayoutInterner, UnionLayout};
|
||||
use roc_mono::low_level::HigherOrder;
|
||||
|
||||
use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "roc_intern"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Provides generic interners for concurrent and single-thread use cases."
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
parking_lot = "0.12"
|
|
@ -1,253 +0,0 @@
|
|||
//! Generic interners for concurrent and single-thread use cases.
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
hash::{BuildHasher, Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use roc_collections::{default_hasher, BumpMap};
|
||||
|
||||
/// An interned value.
|
||||
///
|
||||
/// When possible, prefer comparing/hashing on the [Interned] representation of a value, rather
|
||||
/// than the value itself.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Interned<T>(usize, std::marker::PhantomData<T>);
|
||||
impl<T> Clone for Interned<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for Interned<T> {}
|
||||
|
||||
impl<T> Interned<T> {
|
||||
/// # 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 = Interned::from_reserved_index(0);
|
||||
/// let interner = GlobalInterner::with_capacity(1);
|
||||
/// let inserted = interner.insert("something");
|
||||
/// assert_eq!(reserved_interned, inserted);
|
||||
/// ```
|
||||
pub 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
|
||||
/// [ThreadLocalInterner] via [GlobalInterner::fork], for caching purposes.
|
||||
///
|
||||
/// Originally derived from https://gist.github.com/matklad/44ba1a5a6168bc0c26c995131c007907;
|
||||
/// thank you, Aleksey!
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalInterner<'a, K> {
|
||||
map: Mutex<BumpMap<&'a K, Interned<K>>>,
|
||||
vec: RwLock<Vec<&'a K>>,
|
||||
}
|
||||
|
||||
/// A derivative of a [GlobalInterner] interner that provides caching desirable for
|
||||
/// thread-local workloads. The only way to get a [ThreadLocalInterner] is via
|
||||
/// [GlobalInterner::fork].
|
||||
///
|
||||
/// All values interned into a [ThreadLocalInterner] are made available in its parent
|
||||
/// [GlobalInterner], 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 ThreadLocalInterner<'a, K> {
|
||||
parent: Arc<GlobalInterner<'a, K>>,
|
||||
map: BumpMap<&'a K, Interned<K>>,
|
||||
/// Cache of interned values from the parent for local access.
|
||||
vec: RefCell<Vec<Option<&'a K>>>,
|
||||
}
|
||||
|
||||
/// A single-threaded interner, with no concurrency properties.
|
||||
///
|
||||
/// The only way to construct such an interner is to collapse a shared [GlobalInterner] into
|
||||
/// a [SingleThreadedInterner], via [GlobalInterner::unwrap].
|
||||
#[derive(Debug)]
|
||||
pub struct SingleThreadedInterner<'a, K> {
|
||||
map: BumpMap<&'a K, Interned<K>>,
|
||||
vec: Vec<&'a K>,
|
||||
}
|
||||
|
||||
/// 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: Hash>(val: V) -> u64 {
|
||||
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
|
||||
val.hash(&mut state);
|
||||
state.finish()
|
||||
}
|
||||
|
||||
pub trait Interner<'a, K: Hash + Eq> {
|
||||
/// 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 K) -> Interned<K>;
|
||||
|
||||
/// Retrieves a value from the interner.
|
||||
fn get(&self, key: Interned<K>) -> &'a K;
|
||||
}
|
||||
|
||||
impl<'a, K: Hash + Eq> GlobalInterner<'a, K> {
|
||||
/// Creates a new global interner with the given capacity.
|
||||
pub fn with_capacity(cap: usize) -> Arc<GlobalInterner<'a, K>> {
|
||||
let map: BumpMap<&'a K, Interned<K>> =
|
||||
BumpMap::with_capacity_and_hasher(cap, default_hasher());
|
||||
|
||||
Arc::new(GlobalInterner {
|
||||
map: Mutex::new(map),
|
||||
vec: RwLock::new(Vec::with_capacity(cap)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a derivative [ThreadLocalInterner] pointing back to this global interner.
|
||||
pub fn fork(self: &Arc<GlobalInterner<'a, K>>) -> ThreadLocalInterner<'a, K> {
|
||||
ThreadLocalInterner {
|
||||
parent: Arc::clone(self),
|
||||
map: Default::default(),
|
||||
vec: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Collapses a shared [GlobalInterner] into a [SingleThreadedInterner].
|
||||
///
|
||||
/// Returns an [Err] with `self` if there are outstanding references to the [GlobalInterner].
|
||||
pub fn unwrap(
|
||||
self: Arc<GlobalInterner<'a, K>>,
|
||||
) -> Result<SingleThreadedInterner<'a, K>, Arc<Self>> {
|
||||
let GlobalInterner { map, vec } = Arc::try_unwrap(self)?;
|
||||
let map = Mutex::into_inner(map);
|
||||
let vec = RwLock::into_inner(vec);
|
||||
Ok(SingleThreadedInterner { map, vec })
|
||||
}
|
||||
|
||||
/// Interns a value with a pre-computed hash.
|
||||
/// Prefer calling this when possible, especially from [ThreadLocalInterner], to avoid
|
||||
/// re-computing hashes.
|
||||
fn insert_hashed(&self, value: &'a K, hash: u64) -> Interned<K> {
|
||||
let mut map = self.map.lock();
|
||||
let (_, interned) = map
|
||||
.raw_entry_mut()
|
||||
.from_key_hashed_nocheck(hash, &value)
|
||||
.or_insert_with(|| {
|
||||
let mut vec = self.vec.write();
|
||||
let interned = Interned(vec.len(), Default::default());
|
||||
vec.push(value);
|
||||
(value, interned)
|
||||
});
|
||||
*interned
|
||||
}
|
||||
|
||||
fn get(&self, interned: Interned<K>) -> &'a K {
|
||||
let Interned(index, _) = interned;
|
||||
self.vec.read()[index]
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.vec.read().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Hash + Eq> ThreadLocalInterner<'a, K> {
|
||||
/// Records an interned value in thread-specific storage, for faster access on lookups.
|
||||
fn record(&self, key: &'a K, interned: Interned<K>) {
|
||||
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, K: Hash + Eq> Interner<'a, K> for ThreadLocalInterner<'a, K> {
|
||||
fn insert(&mut self, value: &'a K) -> Interned<K> {
|
||||
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: Interned<K>) -> &'a K {
|
||||
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, K> SingleThreadedInterner<'a, K> {
|
||||
/// Creates a new single threaded interner with the given capacity.
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
Self {
|
||||
map: BumpMap::with_capacity_and_hasher(cap, default_hasher()),
|
||||
vec: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
/// Promotes the [SingleThreadedInterner] back to a [GlobalInterner].
|
||||
///
|
||||
/// 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 [ThreadLocalInterner]s.
|
||||
pub fn into_global(self) -> Arc<GlobalInterner<'a, K>> {
|
||||
let SingleThreadedInterner { map, vec } = self;
|
||||
Arc::new(GlobalInterner {
|
||||
map: Mutex::new(map),
|
||||
vec: RwLock::new(vec),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.vec.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Hash + Eq> Interner<'a, K> for SingleThreadedInterner<'a, K> {
|
||||
fn insert(&mut self, value: &'a K) -> Interned<K> {
|
||||
let hash = hash(value);
|
||||
let (_, interned) = self
|
||||
.map
|
||||
.raw_entry_mut()
|
||||
.from_key_hashed_nocheck(hash, value)
|
||||
.or_insert_with(|| {
|
||||
let interned = Interned(self.vec.len(), Default::default());
|
||||
self.vec.push(value);
|
||||
(value, interned)
|
||||
});
|
||||
*interned
|
||||
}
|
||||
|
||||
fn get(&self, key: Interned<K>) -> &'a K {
|
||||
let Interned(index, _) = key;
|
||||
self.vec[index]
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ roc_solve = { path = "../solve" }
|
|||
roc_solve_problem = { path = "../solve_problem" }
|
||||
roc_late_solve = { path = "../late_solve" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_tracing = { path = "../../tracing" }
|
||||
roc_packaging = { path = "../../packaging" }
|
||||
|
|
|
@ -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,22 +1,33 @@
|
|||
use std::sync::Arc;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
hash::{BuildHasher, Hasher},
|
||||
marker::PhantomData,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use roc_builtins::bitcode::IntWidth;
|
||||
use roc_intern::{GlobalInterner, Interned, Interner, SingleThreadedInterner, ThreadLocalInterner};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::{default_hasher, BumpMap};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
use super::{Builtin, Layout};
|
||||
|
||||
#[allow(unused)] // for now
|
||||
pub struct InLayouts(PhantomData<()>);
|
||||
|
||||
macro_rules! cache_interned_layouts {
|
||||
($($i:literal, $name:ident, $layout:expr)*; $total_constants:literal) => {
|
||||
pub trait LayoutInterner<'a>: Interner<'a, Layout<'a>> {
|
||||
impl InLayouts {
|
||||
$(
|
||||
const $name: Interned<Layout<'a>> = 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);
|
||||
)*
|
||||
}
|
||||
|
||||
|
@ -49,61 +60,280 @@ cache_interned_layouts! {
|
|||
10, I32, Layout::Builtin(Builtin::Int(IntWidth::I32))
|
||||
11, I64, Layout::Builtin(Builtin::Int(IntWidth::I64))
|
||||
12, I128, Layout::Builtin(Builtin::Int(IntWidth::I128))
|
||||
13, F32, Layout::Builtin(Builtin::Float(FloatWidth::F32))
|
||||
14, F64, Layout::Builtin(Builtin::Float(FloatWidth::F64))
|
||||
15, DEC, Layout::Builtin(Builtin::Decimal)
|
||||
|
||||
; 13
|
||||
; 16
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalLayoutInterner<'a>(Arc<GlobalInterner<'a, Layout<'a>>>);
|
||||
#[derive(Debug)]
|
||||
pub struct TLLayoutInterner<'a>(ThreadLocalInterner<'a, Layout<'a>>);
|
||||
#[derive(Debug)]
|
||||
pub struct STLayoutInterner<'a>(SingleThreadedInterner<'a, Layout<'a>>);
|
||||
|
||||
impl<'a> GlobalLayoutInterner<'a> {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
STLayoutInterner::with_capacity(capacity).into_global()
|
||||
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,
|
||||
IntWidth::U32 => Self::U32,
|
||||
IntWidth::U64 => Self::U64,
|
||||
IntWidth::U128 => Self::U128,
|
||||
IntWidth::I8 => Self::I8,
|
||||
IntWidth::I16 => Self::I16,
|
||||
IntWidth::I32 => Self::I32,
|
||||
IntWidth::I64 => Self::I64,
|
||||
IntWidth::I128 => Self::I128,
|
||||
}
|
||||
}
|
||||
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)),
|
||||
#[allow(unused)] // for now
|
||||
pub const fn from_float_width(w: FloatWidth) -> InLayout<'static> {
|
||||
match w {
|
||||
FloatWidth::F32 => Self::F32,
|
||||
FloatWidth::F64 => Self::F64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<GlobalLayoutInternerInner<'a>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
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 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> {
|
||||
/// 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 {
|
||||
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