Skip to content

Commit

Permalink
rune: Allow type hashes to be calculated at compile time
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Oct 27, 2024
1 parent 1c95191 commit 260a675
Show file tree
Hide file tree
Showing 36 changed files with 588 additions and 426 deletions.
140 changes: 111 additions & 29 deletions crates/rune-core/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,75 @@ pub use self::to_type_hash::ToTypeHash;
mod to_type_hash;

use core::fmt;
use core::hash::{self, BuildHasher, BuildHasherDefault, Hash as _, Hasher};
use core::hash::{BuildHasher, BuildHasherDefault, Hash as _, Hasher};

#[cfg(feature = "musli")]
use musli::{Decode, Encode};
use serde::{Deserialize, Serialize};
use twox_hash::XxHash64;

#[derive(Debug)]
#[non_exhaustive]
pub struct TooManyParameters;

impl fmt::Display for TooManyParameters {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Only 32 type parameters are supported")
}
}

impl core::error::Error for TooManyParameters {}

use crate::protocol::Protocol;

use crate::alloc;
use crate::alloc::clone::TryClone;

const SEP: u64 = 0x4bc94d6bd06053ad;
const PARAMS: u64 = 0x19893cc8f39b1371;
const TYPE: u64 = 0x2fac10b63a6cc57c;
const ASSOCIATED_FUNCTION_HASH: u64 = 0x5ea77ffbcdf5f302;
const OBJECT_KEYS: u64 = 0x4473d7017aef7645;
const IDENT: u64 = 0x1a095090689d4647;
const INDEX: u64 = 0xe1b2378d7a937035;

// Salt for type parameters.
const TYPE_PARAMETERS: u32 = 16;
// Salt for function parameters.
const FUNCTION_PARAMETERS: u32 = 48;
// A bunch of random hashes to mix in when calculating type parameters.
const PARAMETERS: [u64; 32] = [
0x2d859a05306ebe33,
0x75ac929aabdda742,
0x18f4e51cd6f60e86,
0x3b47f977015b002,
0xd7e3b9e36d59b900,
0xad75a1d63593d47c,
0x8fc37a65ac89ed71,
0x39eb9ab133d1cf22,
0xa287885efb6bf688,
0x9eeef0c7395ea8ca,
0x26a193328114c317,
0x9edc35591d044a28,
0xbfa4e9a8eca88b80,
0x94a626c6aa89a686,
0x95970296235c5b91,
0xa8ab16ceff9068b8,
0x153e675e2a27db85,
0xa873a7e51dfe4205,
0xde401d82396a7876,
0x9dbbae67606eddc3,
0x23d51f8018d09e74,
0xb5bfa5d588fedcc6,
0x9702480ba16eeb96,
0x58549fb39441505c,
0xd88078065e88667d,
0x38a1d4efb147fe18,
0xf712c95e9ffd1ba5,
0x73c2249b2845a5e0,
0x8079aff8b0833e20,
0x7e708fb5e906bbb5,
0x22d363b1d55a5eec,
0x9e2d56cbbd4459f1,
];

/// The primitive hash that among other things is used to reference items,
/// types, and native functions.
Expand Down Expand Up @@ -164,18 +209,20 @@ impl Hash {
}

/// Hash type parameters.
pub fn parameters<I>(parameters: I) -> Self
where
I: IntoIterator,
I::Item: hash::Hash,
{
let mut hasher = ParametersBuilder::new();
pub const fn parameters<const N: usize>(params: [Hash; N]) -> Self {
let mut builder = ParametersBuilder::new();

while builder.index < N {
let param = params[builder.index];

for p in parameters {
hasher.add(p);
let Ok(b) = builder.add(param) else {
panic!("Only up to 32 type parameters are supported");
};

builder = b;
}

hasher.finish()
builder.finish()
}

/// Construct a new hasher.
Expand Down Expand Up @@ -203,30 +250,65 @@ impl fmt::Debug for Hash {
}

/// Helper to build a parameters hash.
#[doc(hidden)]
///
/// A collection of parameters are like the type parameters like `String` and
/// `i64` in a signature like:
///
/// `::my_crate::Map<String, i64>`
///
/// # Examples
///
/// ```
/// use rune::TypeHash;
/// use rune::hash::ParametersBuilder;
///
/// let mut params = ParametersBuilder::new();
///
/// let params = params.add(String::HASH)?;
/// let params = params.add(i64::HASH)?;
///
/// let hash = params.finish();
/// # Ok::<_, rune::hash::TooManyParameters>(())
/// ```
#[derive(Default)]
pub struct ParametersBuilder {
hasher: XxHash64,
base: u64,
index: usize,
shift: u32,
}

impl ParametersBuilder {
#[doc(hidden)]
pub fn new() -> Self {
let mut hasher = Hash::new_hasher();
PARAMS.hash(&mut hasher);
Self { hasher }
/// Construct a new collection of parameters.
pub const fn new() -> Self {
Self {
base: 0,
index: 0,
shift: 0,
}
}

#[doc(hidden)]
pub fn add<P>(&mut self, p: P)
where
P: hash::Hash,
{
SEP.hash(&mut self.hasher);
p.hash(&mut self.hasher);
/// Add a hash to the collection of parameters.
///
/// # Errors
///
/// Errors if too many parameters are added.
pub const fn add(mut self, Hash(hash): Hash) -> Result<Self, TooManyParameters> {
if self.index >= PARAMETERS.len() {
self.shift += 8;
self.index = 0;

if self.shift >= u64::BITS {
return Err(TooManyParameters);
}
}

self.base ^= hash ^ PARAMETERS[self.index].wrapping_shl(self.shift);
self.index += 1;
Ok(self)
}

#[doc(hidden)]
pub fn finish(&self) -> Hash {
Hash::new(self.hasher.finish())
/// Finish building the parameters hash.
pub const fn finish(self) -> Hash {
Hash::new(self.base)
}
}
44 changes: 15 additions & 29 deletions crates/rune-macros/src/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,11 +504,9 @@ where

let Tokens {
alloc,
any_type_info,
any,
box_,
context_error,
core_type_of,
from_value,
hash,
install_with,
Expand All @@ -524,6 +522,7 @@ where
ref_,
static_type_mod,
to_value,
type_hash_t,
type_info,
type_of,
unsafe_to_mut,
Expand Down Expand Up @@ -579,39 +578,27 @@ where
let type_hash = type_hash.into_inner();

let make_hash = if !generic_names.is_empty() {
quote!(#hash::new_with_type_parameters(#type_hash, #hash::parameters([#(<#generic_names as #core_type_of>::type_hash()),*])))
quote!(#hash::new_with_type_parameters(#type_hash, #hash::parameters([#(<#generic_names as #type_hash_t>::HASH),*])))
} else {
quote!(#hash::new(#type_hash))
};

let type_parameters = if !generic_names.is_empty() {
quote!(#hash::parameters([#(<#generic_names as #core_type_of>::type_hash()),*]))
} else {
quote!(#hash::EMPTY)
};
let type_parameters =
quote!(#hash::parameters([#(<#generic_names as #type_hash_t>::HASH),*]));

Some(quote! {
#[automatically_derived]
impl #impl_generics #core_type_of for #ident #type_generics #where_clause {
#[inline]
fn type_hash() -> #hash {
#make_hash
}
impl #impl_generics #type_hash_t for #ident #type_generics #where_clause {
const HASH: #hash = #make_hash;
}

#[automatically_derived]
impl #impl_generics #type_of for #ident #type_generics #where_clause {
#[inline]
fn type_parameters() -> #hash {
#type_parameters
}
const PARAMETERS: #hash = #type_parameters;

#[inline]
fn type_info() -> #type_info {
#type_info::Any(#any_type_info::__private_new(
#raw_str::from_str(core::any::type_name::<Self>()),
<Self as #core_type_of>::type_hash(),
))
#type_info::any::<Self>()
}
}

Expand All @@ -620,27 +607,26 @@ where
#[inline]
fn maybe_type_of() -> #alloc::Result<#meta::DocType> {
#meta::DocType::with_generics(
<Self as #core_type_of>::type_hash(),
<Self as #type_hash_t>::HASH,
[#(<#generic_names as #maybe_type_of>::maybe_type_of()?),*]
)
}
}
})
} else if let Some(ty) = attr.static_type {
let ty_hash = syn::Ident::new(&format!("{ty}_HASH"), ty.span());

Some(quote! {
#[automatically_derived]
impl #impl_generics #core_type_of for #ident #type_generics #where_clause {
#[inline]
fn type_hash() -> #hash {
#static_type_mod::#ty.hash
}
impl #impl_generics #type_hash_t for #ident #type_generics #where_clause {
const HASH: #hash = #static_type_mod::#ty_hash;
}

#[automatically_derived]
impl #impl_generics #type_of for #ident #type_generics #where_clause {
#[inline]
fn type_info() -> #type_info {
#type_info::StaticType(#static_type_mod::#ty)
#type_info::static_type(#static_type_mod::#ty)
}
}

Expand All @@ -649,7 +635,7 @@ where
#[inline]
fn maybe_type_of() -> #alloc::Result<#meta::DocType> {
#meta::DocType::with_generics(
<Self as #core_type_of>::type_hash(),
<Self as #type_hash_t>::HASH,
[#(<#generic_names as #maybe_type_of>::maybe_type_of()),*]
)
}
Expand Down
6 changes: 2 additions & 4 deletions crates/rune-macros/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,12 +613,10 @@ impl Context {

Tokens {
alloc: path(m, ["alloc"]),
any_type_info: path(m, ["runtime", "AnyTypeInfo"]),
any: path(m, ["Any"]),
box_: path(m, ["__private", "Box"]),
compile_error: path(m, ["compile", "Error"]),
context_error: path(m, ["compile", "ContextError"]),
core_type_of: path(m, ["runtime", "CoreTypeOf"]),
double_ended_iterator: path(&core, ["iter", "DoubleEndedIterator"]),
from_value: path(m, ["runtime", "FromValue"]),
hash: path(m, ["Hash"]),
Expand Down Expand Up @@ -656,6 +654,7 @@ impl Context {
try_clone: path(m, ["alloc", "clone", "TryClone"]),
try_from: path(&core, ["convert", "TryFrom"]),
tuple: path(m, ["runtime", "Tuple"]),
type_hash_t: path(m, ["runtime", "TypeHash"]),
type_info: path(m, ["runtime", "TypeInfo"]),
type_name: path(&core, ["any", "type_name"]),
type_of: path(m, ["runtime", "TypeOf"]),
Expand Down Expand Up @@ -705,12 +704,10 @@ fn path<const N: usize>(base: &syn::Path, path: [&'static str; N]) -> syn::Path

pub(crate) struct Tokens {
pub(crate) alloc: syn::Path,
pub(crate) any_type_info: syn::Path,
pub(crate) any: syn::Path,
pub(crate) box_: syn::Path,
pub(crate) compile_error: syn::Path,
pub(crate) context_error: syn::Path,
pub(crate) core_type_of: syn::Path,
pub(crate) double_ended_iterator: syn::Path,
pub(crate) from_value: syn::Path,
pub(crate) hash: syn::Path,
Expand Down Expand Up @@ -748,6 +745,7 @@ pub(crate) struct Tokens {
pub(crate) try_clone: syn::Path,
pub(crate) try_from: syn::Path,
pub(crate) tuple: syn::Path,
pub(crate) type_hash_t: syn::Path,
pub(crate) type_info: syn::Path,
pub(crate) type_name: syn::Path,
pub(crate) type_of: syn::Path,
Expand Down
2 changes: 1 addition & 1 deletion crates/rune-macros/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ impl Function {

for argument in arguments {
array.elems.push(syn::Expr::Verbatim(quote! {
<#argument as rune::__private::CoreTypeOf>::type_hash()
<#argument as rune::__private::TypeHash>::HASH
}));
}

Expand Down
28 changes: 28 additions & 0 deletions crates/rune-shim/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,43 @@
//! This crate is only used to build documentation for the `rune-core` crate.
#[cfg(feature = "rune-alloc")]
pub use rune_alloc as alloc;

#[cfg(feature = "rune-core")]
pub mod item {
#[doc(inline)]
pub use rune_core::item::{Component, ComponentRef, IntoComponent, Item, ItemBuf};
}

#[cfg(feature = "rune-core")]
pub mod hash {
#[doc(inline)]
pub use rune_core::hash::{ParametersBuilder, TooManyParameters};
}

pub mod support {
pub use anyhow::Error;
pub use anyhow::Result;
}

pub mod runtime {
use rune_core::hash::Hash;

pub trait TypeHash {
const HASH: Hash;
}

impl TypeHash for String {
const HASH: Hash = Hash::new(1);
}

impl TypeHash for i64 {
const HASH: Hash = Hash::new(2);
}
}

#[cfg(feature = "rune-core")]
pub use self::runtime::TypeHash;

#[cfg(feature = "rune-core")]
pub use rune_core::item::{Item, ItemBuf};
Loading

0 comments on commit 260a675

Please sign in to comment.