From b42faea98099e75f751cb58bd0c7809ae1e4ebb4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 12:13:00 -0800 Subject: [PATCH] Implement PackedOption to address #19. The PackedOption struct uses the same amount of memory as T, but can represent None via a reserved value. --- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/packed_option.rs | 121 ++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 lib/cretonne/src/packed_option.rs diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 67694e139f..25feb3c0db 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -24,3 +24,4 @@ mod constant_hash; mod predicates; mod legalizer; mod ref_slice; +mod packed_option; diff --git a/lib/cretonne/src/packed_option.rs b/lib/cretonne/src/packed_option.rs new file mode 100644 index 0000000000..2c93b38ead --- /dev/null +++ b/lib/cretonne/src/packed_option.rs @@ -0,0 +1,121 @@ +//! Compact representation of `Option` for types with a reserved value. +//! +//! Small Cretonne types like the 32-bit entity references are often used in tables and linked +//! lists where an `Option` is needed. Unfortunately, that would double the size of the tables +//! because `Option` is twice as big as `T`. +//! +//! This module provides a `PackedOption` for types that have a reserved value that can be used +//! to represent `None`. + +use std::fmt; + +/// Types that have a reserved value which can't be created any other way. +pub trait ReservedValue: Eq { + /// Create an instance of the reserved value. + fn reserved_value() -> Self; +} + +/// Packed representation of `Option`. +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub struct PackedOption(T); + +impl PackedOption { + /// Returns `true` if the packed option is a `None` value. + pub fn is_none(&self) -> bool { + self.0 == T::reserved_value() + } + + /// Expand the packed option into a normal `Option`. + pub fn expand(self) -> Option { + if self.is_none() { None } else { Some(self.0) } + } +} + +impl Default for PackedOption { + /// Create a default packed option representing `None`. + fn default() -> PackedOption { + PackedOption(T::reserved_value()) + } +} + +impl From for PackedOption { + /// Convert `t` into a packed `Some(x)`. + fn from(t: T) -> PackedOption { + debug_assert!(t != T::reserved_value(), + "Can't make a PackedOption from the reserved value."); + PackedOption(t) + } +} + +impl From> for PackedOption { + /// Convert an option into its packed equivalent. + fn from(opt: Option) -> PackedOption { + match opt { + None => Self::default(), + Some(t) => t.into(), + } + } +} + +impl Into> for PackedOption { + fn into(self) -> Option { + self.expand() + } +} + +impl fmt::Debug for PackedOption + where T: ReservedValue + fmt::Debug +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_none() { + write!(f, "None") + } else { + write!(f, "Some({:?})", self.0) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Dummy entity class, with no Copy or Clone. + #[derive(Debug, PartialEq, Eq)] + struct NoC(u32); + + impl ReservedValue for NoC { + fn reserved_value() -> Self { + NoC(13) + } + } + + #[test] + fn moves() { + let x = NoC(3); + let somex: PackedOption = x.into(); + assert!(!somex.is_none()); + assert_eq!(somex.expand(), Some(NoC(3))); + + let none: PackedOption = None.into(); + assert!(none.is_none()); + assert_eq!(none.expand(), None); + } + + // Dummy entity class, with Copy. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct Ent(u32); + + impl ReservedValue for Ent { + fn reserved_value() -> Self { + Ent(13) + } + } + + #[test] + fn copies() { + let x = Ent(2); + let some: PackedOption = x.into(); + assert_eq!(some.expand(), x.into()); + assert_eq!(some, x.into()); + } +}