* Cranelift: make `ir::Type` a `u16`.
* Cranelift: pack ValueData back into 64 bits.
After extending `Type` to a `u16`, `ValueData` became 12 bytes rather
than 8. This packs it back down to 8 bytes (64 bits) by stealing two
bits from the `Type` for the enum discriminant (leaving 14 bits for the
type itself).
Performance comparison (3-way between original (`ty-u8`), 16-bit `Type`
(`ty-u16`), and this PR (`ty-packed`)):
```
~/work/sightglass% target/release/sightglass-cli benchmark \
-e ~/ty-u8.so -e ~/ty-u16.so -e ~/ty-packed.so \
--iterations-per-process 10 --processes 2 \
benchmarks-next/spidermonkey/benchmark.wasm
compilation
benchmarks-next/spidermonkey/benchmark.wasm
cycles
[20654406874 21749213920.50 22958520306] /home/cfallin/ty-packed.so
[22227738316 22584704883.90 22916433748] /home/cfallin/ty-u16.so
[20659150490 21598675968.60 22588108428] /home/cfallin/ty-u8.so
nanoseconds
[5435333269 5723139427.25 6041072883] /home/cfallin/ty-packed.so
[5848788229 5942729637.85 6030030341] /home/cfallin/ty-u16.so
[5436002390 5683248226.10 5943626225] /home/cfallin/ty-u8.so
```
So, when compiling SpiderMonkey.wasm, making `Type` 16 bits regresses
performance by 4.5% (5.683s -> 5.723s), while this PR gets 14 bits for a 1.0%
cost (5.683s -> 5.723s). That's still not great, and we can likely do better,
but it's a start.
* Fix test failure: entities to/from u32 via `{from,to}_bits`, not `{from,to}_u32`.
329 lines
11 KiB
Rust
329 lines
11 KiB
Rust
//! Array-based data structures using densely numbered entity references as mapping keys.
|
|
//!
|
|
//! This crate defines a number of data structures based on arrays. The arrays are not indexed by
|
|
//! `usize` as usual, but by *entity references* which are integers wrapped in new-types. This has
|
|
//! a couple advantages:
|
|
//!
|
|
//! - Improved type safety. The various map and set types accept a specific key type, so there is
|
|
//! no confusion about the meaning of an array index, as there is with plain arrays.
|
|
//! - Smaller indexes. The normal `usize` index is often 64 bits which is way too large for most
|
|
//! purposes. The entity reference types can be smaller, allowing for more compact data
|
|
//! structures.
|
|
//!
|
|
//! The `EntityRef` trait should be implemented by types to be used as indexed. The `entity_impl!`
|
|
//! macro provides convenient defaults for types wrapping `u32` which is common.
|
|
//!
|
|
//! - [`PrimaryMap`](struct.PrimaryMap.html) is used to keep track of a vector of entities,
|
|
//! assigning a unique entity reference to each.
|
|
//! - [`SecondaryMap`](struct.SecondaryMap.html) is used to associate secondary information to an
|
|
//! entity. The map is implemented as a simple vector, so it does not keep track of which
|
|
//! entities have been inserted. Instead, any unknown entities map to the default value.
|
|
//! - [`SparseMap`](struct.SparseMap.html) is used to associate secondary information to a small
|
|
//! number of entities. It tracks accurately which entities have been inserted. This is a
|
|
//! specialized data structure which can use a lot of memory, so read the documentation before
|
|
//! using it.
|
|
//! - [`EntitySet`](struct.EntitySet.html) is used to represent a secondary set of entities.
|
|
//! The set is implemented as a simple vector, so it does not keep track of which entities have
|
|
//! been inserted into the primary map. Instead, any unknown entities are not in the set.
|
|
//! - [`EntityList`](struct.EntityList.html) is a compact representation of lists of entity
|
|
//! references allocated from an associated memory pool. It has a much smaller footprint than
|
|
//! `Vec`.
|
|
|
|
#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)]
|
|
#![warn(unused_import_braces)]
|
|
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))]
|
|
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
|
#![cfg_attr(
|
|
feature = "cargo-clippy",
|
|
warn(
|
|
clippy::float_arithmetic,
|
|
clippy::mut_mut,
|
|
clippy::nonminimal_bool,
|
|
clippy::map_unwrap_or,
|
|
clippy::clippy::print_stdout,
|
|
clippy::unicode_not_nfc,
|
|
clippy::use_self
|
|
)
|
|
)]
|
|
#![no_std]
|
|
|
|
extern crate alloc;
|
|
|
|
// Re-export core so that the macros works with both std and no_std crates
|
|
#[doc(hidden)]
|
|
pub extern crate core as __core;
|
|
|
|
/// A type wrapping a small integer index should implement `EntityRef` so it can be used as the key
|
|
/// of an `SecondaryMap` or `SparseMap`.
|
|
pub trait EntityRef: Copy + Eq {
|
|
/// Create a new entity reference from a small integer.
|
|
/// This should crash if the requested index is not representable.
|
|
fn new(_: usize) -> Self;
|
|
|
|
/// Get the index that was used to create this entity reference.
|
|
fn index(self) -> usize;
|
|
}
|
|
|
|
/// Macro which provides the common implementation of a 32-bit entity reference.
|
|
#[macro_export]
|
|
macro_rules! entity_impl {
|
|
// Basic traits.
|
|
($entity:ident) => {
|
|
impl $crate::EntityRef for $entity {
|
|
#[inline]
|
|
fn new(index: usize) -> Self {
|
|
debug_assert!(index < ($crate::__core::u32::MAX as usize));
|
|
$entity(index as u32)
|
|
}
|
|
|
|
#[inline]
|
|
fn index(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
}
|
|
|
|
impl $crate::packed_option::ReservedValue for $entity {
|
|
#[inline]
|
|
fn reserved_value() -> $entity {
|
|
$entity($crate::__core::u32::MAX)
|
|
}
|
|
|
|
#[inline]
|
|
fn is_reserved_value(&self) -> bool {
|
|
self.0 == $crate::__core::u32::MAX
|
|
}
|
|
}
|
|
|
|
impl $entity {
|
|
/// Create a new instance from a `u32`.
|
|
#[allow(dead_code)]
|
|
#[inline]
|
|
pub fn from_u32(x: u32) -> Self {
|
|
debug_assert!(x < $crate::__core::u32::MAX);
|
|
$entity(x)
|
|
}
|
|
|
|
/// Return the underlying index value as a `u32`.
|
|
#[allow(dead_code)]
|
|
#[inline]
|
|
pub fn as_u32(self) -> u32 {
|
|
self.0
|
|
}
|
|
|
|
/// Return the raw bit encoding for this instance.
|
|
#[allow(dead_code)]
|
|
#[inline]
|
|
pub fn as_bits(self) -> u32 {
|
|
self.0
|
|
}
|
|
|
|
/// Create a new instance from the raw bit encoding.
|
|
#[allow(dead_code)]
|
|
#[inline]
|
|
pub fn from_bits(x: u32) -> Self {
|
|
$entity(x)
|
|
}
|
|
}
|
|
};
|
|
|
|
// Include basic `Display` impl using the given display prefix.
|
|
// Display a `Block` reference as "block12".
|
|
($entity:ident, $display_prefix:expr) => {
|
|
entity_impl!($entity);
|
|
|
|
impl $crate::__core::fmt::Display for $entity {
|
|
fn fmt(&self, f: &mut $crate::__core::fmt::Formatter) -> $crate::__core::fmt::Result {
|
|
write!(f, concat!($display_prefix, "{}"), self.0)
|
|
}
|
|
}
|
|
|
|
impl $crate::__core::fmt::Debug for $entity {
|
|
fn fmt(&self, f: &mut $crate::__core::fmt::Formatter) -> $crate::__core::fmt::Result {
|
|
(self as &dyn $crate::__core::fmt::Display).fmt(f)
|
|
}
|
|
}
|
|
};
|
|
|
|
// Alternate form for tuples we can't directly construct; providing "to" and "from" expressions
|
|
// to turn an index *into* an entity, or get an index *from* an entity.
|
|
($entity:ident, $display_prefix:expr, $arg:ident, $to_expr:expr, $from_expr:expr) => {
|
|
impl $crate::EntityRef for $entity {
|
|
#[inline]
|
|
fn new(index: usize) -> Self {
|
|
debug_assert!(index < ($crate::__core::u32::MAX as usize));
|
|
let $arg = index as u32;
|
|
$to_expr
|
|
}
|
|
|
|
#[inline]
|
|
fn index(self) -> usize {
|
|
let $arg = self;
|
|
$from_expr as usize
|
|
}
|
|
}
|
|
|
|
impl $crate::packed_option::ReservedValue for $entity {
|
|
#[inline]
|
|
fn reserved_value() -> $entity {
|
|
$entity::from_u32($crate::__core::u32::MAX)
|
|
}
|
|
|
|
#[inline]
|
|
fn is_reserved_value(&self) -> bool {
|
|
self.as_u32() == $crate::__core::u32::MAX
|
|
}
|
|
}
|
|
|
|
impl $entity {
|
|
/// Create a new instance from a `u32`.
|
|
#[allow(dead_code)]
|
|
#[inline]
|
|
pub fn from_u32(x: u32) -> Self {
|
|
debug_assert!(x < $crate::__core::u32::MAX);
|
|
let $arg = x;
|
|
$to_expr
|
|
}
|
|
|
|
/// Return the underlying index value as a `u32`.
|
|
#[allow(dead_code)]
|
|
#[inline]
|
|
pub fn as_u32(self) -> u32 {
|
|
let $arg = self;
|
|
$from_expr
|
|
}
|
|
}
|
|
|
|
impl $crate::__core::fmt::Display for $entity {
|
|
fn fmt(&self, f: &mut $crate::__core::fmt::Formatter) -> $crate::__core::fmt::Result {
|
|
write!(f, concat!($display_prefix, "{}"), self.as_u32())
|
|
}
|
|
}
|
|
|
|
impl $crate::__core::fmt::Debug for $entity {
|
|
fn fmt(&self, f: &mut $crate::__core::fmt::Formatter) -> $crate::__core::fmt::Result {
|
|
(self as &dyn $crate::__core::fmt::Display).fmt(f)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub mod packed_option;
|
|
|
|
mod boxed_slice;
|
|
mod iter;
|
|
mod keys;
|
|
mod list;
|
|
mod map;
|
|
mod primary;
|
|
mod set;
|
|
mod sparse;
|
|
|
|
pub use self::boxed_slice::BoxedSlice;
|
|
pub use self::iter::{Iter, IterMut};
|
|
pub use self::keys::Keys;
|
|
pub use self::list::{EntityList, ListPool};
|
|
pub use self::map::SecondaryMap;
|
|
pub use self::primary::PrimaryMap;
|
|
pub use self::set::EntitySet;
|
|
pub use self::sparse::{SparseMap, SparseMapValue, SparseSet};
|
|
|
|
/// A collection of tests to ensure that use of the different `entity_impl!` forms will generate
|
|
/// `EntityRef` implementations that behave the same way.
|
|
#[cfg(test)]
|
|
mod tests {
|
|
/// A macro used to emit some basic tests to show that entities behave as we expect.
|
|
macro_rules! entity_test {
|
|
($entity:ident) => {
|
|
#[test]
|
|
fn from_usize_to_u32() {
|
|
let e = $entity::new(42);
|
|
assert_eq!(e.as_u32(), 42_u32);
|
|
}
|
|
|
|
#[test]
|
|
fn from_u32_to_usize() {
|
|
let e = $entity::from_u32(42);
|
|
assert_eq!(e.index(), 42_usize);
|
|
}
|
|
|
|
#[test]
|
|
fn comparisons_work() {
|
|
let a = $entity::from_u32(42);
|
|
let b = $entity::new(42);
|
|
assert_eq!(a, b);
|
|
}
|
|
|
|
#[should_panic]
|
|
#[test]
|
|
fn cannot_construct_from_reserved_u32() {
|
|
use crate::packed_option::ReservedValue;
|
|
let reserved = $entity::reserved_value().as_u32();
|
|
let _ = $entity::from_u32(reserved); // panic
|
|
}
|
|
|
|
#[should_panic]
|
|
#[test]
|
|
fn cannot_construct_from_reserved_usize() {
|
|
use crate::packed_option::ReservedValue;
|
|
let reserved = $entity::reserved_value().index();
|
|
let _ = $entity::new(reserved); // panic
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Test cases for a plain ol' `EntityRef` implementation.
|
|
mod basic_entity {
|
|
use crate::EntityRef;
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
struct BasicEntity(u32);
|
|
entity_impl!(BasicEntity);
|
|
entity_test!(BasicEntity);
|
|
}
|
|
|
|
/// Test cases for an `EntityRef` implementation that includes a display prefix.
|
|
mod prefix_entity {
|
|
use crate::EntityRef;
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
struct PrefixEntity(u32);
|
|
entity_impl!(PrefixEntity, "prefix-");
|
|
entity_test!(PrefixEntity);
|
|
|
|
#[test]
|
|
fn display_prefix_works() {
|
|
let e = PrefixEntity::new(0);
|
|
assert_eq!(alloc::format!("{}", e), "prefix-0");
|
|
}
|
|
}
|
|
|
|
/// Test cases for an `EntityRef` implementation for a type we can only construct through
|
|
/// other means, such as calls to `core::convert::From<u32>`.
|
|
mod other_entity {
|
|
mod inner {
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
pub struct InnerEntity(u32);
|
|
|
|
impl From<u32> for InnerEntity {
|
|
fn from(x: u32) -> Self {
|
|
Self(x)
|
|
}
|
|
}
|
|
|
|
impl From<InnerEntity> for u32 {
|
|
fn from(x: InnerEntity) -> Self {
|
|
x.0
|
|
}
|
|
}
|
|
}
|
|
|
|
use {self::inner::InnerEntity, crate::EntityRef};
|
|
entity_impl!(InnerEntity, "inner-", i, InnerEntity::from(i), u32::from(i));
|
|
entity_test!(InnerEntity);
|
|
|
|
#[test]
|
|
fn display_prefix_works() {
|
|
let e = InnerEntity::new(0);
|
|
assert_eq!(alloc::format!("{}", e), "inner-0");
|
|
}
|
|
}
|
|
}
|