mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Optimize derived hash implementation for newtypes
When we have a newtype tag union, there is no reason to hash its discriminant.
This commit is contained in:
parent
2517695ce4
commit
a308ebb38c
3 changed files with 109 additions and 26 deletions
|
@ -17,7 +17,7 @@ use roc_region::all::{Loc, Region};
|
||||||
use roc_types::{
|
use roc_types::{
|
||||||
subs::{
|
subs::{
|
||||||
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
|
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
|
||||||
RedundantMark, SubsSlice, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
|
RedundantMark, SubsIndex, SubsSlice, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
|
||||||
},
|
},
|
||||||
types::RecordField,
|
types::RecordField,
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,13 @@ use crate::{synth_var, util::Env, DerivedBody};
|
||||||
pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody {
|
pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody {
|
||||||
let (body_type, body) = match key {
|
let (body_type, body) = match key {
|
||||||
FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields),
|
FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields),
|
||||||
FlatHashKey::TagUnion(tags) => hash_tag_union(env, def_symbol, tags),
|
FlatHashKey::TagUnion(tags) => {
|
||||||
|
if tags.len() == 1 {
|
||||||
|
hash_newtype_tag_union(env, def_symbol, tags.into_iter().next().unwrap())
|
||||||
|
} else {
|
||||||
|
hash_tag_union(env, def_symbol, tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let specialization_lambda_sets =
|
let specialization_lambda_sets =
|
||||||
|
@ -105,7 +111,7 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (V
|
||||||
env,
|
env,
|
||||||
fn_name,
|
fn_name,
|
||||||
(hasher_var, hasher_sym),
|
(hasher_var, hasher_sym),
|
||||||
(record_var, rcd_sym),
|
(record_var, Pattern::Identifier(rcd_sym)),
|
||||||
(body_var, body),
|
(body_var, body),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -256,7 +262,91 @@ fn hash_tag_union(
|
||||||
env,
|
env,
|
||||||
fn_name,
|
fn_name,
|
||||||
(hasher_var, hasher_sym),
|
(hasher_var, hasher_sym),
|
||||||
(union_var, union_sym),
|
(union_var, Pattern::Identifier(union_sym)),
|
||||||
|
(body_var, body_expr),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a `hash` implementation for a newtype (singleton) tag union.
|
||||||
|
/// If a tag union is a newtype, we do not need to hash its discriminant.
|
||||||
|
fn hash_newtype_tag_union(
|
||||||
|
env: &mut Env<'_>,
|
||||||
|
fn_name: Symbol,
|
||||||
|
tag: (TagName, u16),
|
||||||
|
) -> (Variable, Expr) {
|
||||||
|
// Suppose tags = [ A p1 .. pn ]
|
||||||
|
// Build a generalized type t_tags = [ A t1 .. tn ],
|
||||||
|
// with fresh t1, ..., tn, so that we can re-use the derived impl for many
|
||||||
|
// unions of the same tag and payload arity.
|
||||||
|
let (union_var, tag_name, payload_variables) = {
|
||||||
|
let (label, arity) = tag;
|
||||||
|
|
||||||
|
let variables_slice = VariableSubsSlice::reserve_into_subs(env.subs, arity.into());
|
||||||
|
for var_index in variables_slice {
|
||||||
|
env.subs[var_index] = env.subs.fresh_unnamed_flex_var();
|
||||||
|
}
|
||||||
|
|
||||||
|
let variables_slices_slice =
|
||||||
|
SubsSlice::extend_new(&mut env.subs.variable_slices, [variables_slice]);
|
||||||
|
let tag_name_index = SubsIndex::push_new(&mut env.subs.tag_names, label.clone());
|
||||||
|
|
||||||
|
let union_tags = UnionTags::from_slices(tag_name_index.as_slice(), variables_slices_slice);
|
||||||
|
let tag_union_var = synth_var(
|
||||||
|
env.subs,
|
||||||
|
Content::Structure(FlatType::TagUnion(union_tags, Variable::EMPTY_TAG_UNION)),
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
tag_union_var,
|
||||||
|
label,
|
||||||
|
env.subs.get_subs_slice(variables_slice).to_vec(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now, a hasher for this tag union is
|
||||||
|
//
|
||||||
|
// hash_union : hasher, [ A t1 .. tn ] -> hasher | hasher has Hasher
|
||||||
|
// hash_union = \hasher, A x1 .. xn ->
|
||||||
|
// Hash.hash (... (Hash.hash discrHasher x1) ...) xn
|
||||||
|
let hasher_sym = env.new_symbol("hasher");
|
||||||
|
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER));
|
||||||
|
|
||||||
|
// A
|
||||||
|
let tag_name = tag_name;
|
||||||
|
// t1 .. tn
|
||||||
|
let payload_vars = payload_variables;
|
||||||
|
// x11 .. x1n
|
||||||
|
let payload_syms: Vec<_> = std::iter::repeat_with(|| env.unique_symbol())
|
||||||
|
.take(payload_vars.len())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// `A x1 .. x1n` pattern
|
||||||
|
let pattern = Pattern::AppliedTag {
|
||||||
|
whole_var: union_var,
|
||||||
|
tag_name,
|
||||||
|
ext_var: Variable::EMPTY_TAG_UNION,
|
||||||
|
// (t1, v1) (t2, v2)
|
||||||
|
arguments: (payload_vars.iter())
|
||||||
|
.zip(payload_syms.iter())
|
||||||
|
.map(|(var, sym)| (*var, Loc::at_zero(Pattern::Identifier(*sym))))
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fold up `Hash.hash (... (Hash.hash discrHasher x11) ...) x1n`
|
||||||
|
let (body_var, body_expr) = (payload_vars.into_iter()).zip(payload_syms).fold(
|
||||||
|
(hasher_var, Expr::Var(hasher_sym)),
|
||||||
|
|total_hasher, (payload_var, payload_sym)| {
|
||||||
|
call_hash_hash(env, total_hasher, (payload_var, Expr::Var(payload_sym)))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Finally, build the closure
|
||||||
|
// \hasher, rcd -> body
|
||||||
|
build_outer_derived_closure(
|
||||||
|
env,
|
||||||
|
fn_name,
|
||||||
|
(hasher_var, hasher_sym),
|
||||||
|
(union_var, pattern),
|
||||||
(body_var, body_expr),
|
(body_var, body_expr),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -331,11 +421,11 @@ fn build_outer_derived_closure(
|
||||||
env: &mut Env<'_>,
|
env: &mut Env<'_>,
|
||||||
fn_name: Symbol,
|
fn_name: Symbol,
|
||||||
hasher: (Variable, Symbol),
|
hasher: (Variable, Symbol),
|
||||||
val: (Variable, Symbol),
|
val: (Variable, Pattern),
|
||||||
body: (Variable, Expr),
|
body: (Variable, Expr),
|
||||||
) -> (Variable, Expr) {
|
) -> (Variable, Expr) {
|
||||||
let (hasher_var, hasher_sym) = hasher;
|
let (hasher_var, hasher_sym) = hasher;
|
||||||
let (val_var, val_sym) = val;
|
let (val_var, val_pattern) = val;
|
||||||
let (body_var, body_expr) = body;
|
let (body_var, body_expr) = body;
|
||||||
|
|
||||||
let (fn_var, fn_clos_var) = {
|
let (fn_var, fn_clos_var) = {
|
||||||
|
@ -381,7 +471,7 @@ fn build_outer_derived_closure(
|
||||||
(
|
(
|
||||||
val_var,
|
val_var,
|
||||||
AnnotatedMark::known_exhaustive(),
|
AnnotatedMark::known_exhaustive(),
|
||||||
Loc::at_zero(Pattern::Identifier(val_sym)),
|
Loc::at_zero(val_pattern),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
loc_body: Box::new(Loc::at_zero(body_expr)),
|
loc_body: Box::new(Loc::at_zero(body_expr)),
|
||||||
|
|
|
@ -206,16 +206,11 @@ fn tag_one_label_no_payloads() {
|
||||||
derive_test(Hash, v!([A]), |golden| {
|
derive_test(Hash, v!([A]), |golden| {
|
||||||
assert_snapshot!(golden, @r###"
|
assert_snapshot!(golden, @r###"
|
||||||
# derived for [A]
|
# derived for [A]
|
||||||
# a, [A] -[[hash_[A 0](0)]]-> a | a has Hasher
|
# hasher, [A] -[[hash_[A 0](0)]]-> hasher | hasher has Hasher
|
||||||
# a, [A] -[[hash_[A 0](0)]]-> a | a has Hasher
|
# hasher, [A] -[[hash_[A 0](0)]]-> hasher | hasher has Hasher
|
||||||
# Specialization lambda sets:
|
# Specialization lambda sets:
|
||||||
# @<1>: [[hash_[A 0](0)]]
|
# @<1>: [[hash_[A 0](0)]]
|
||||||
#Derived.hash_[A 0] =
|
#Derived.hash_[A 0] = \#Derived.hasher, A -> #Derived.hasher
|
||||||
\#Derived.hasher, #Derived.union ->
|
|
||||||
#Derived.discrHasher =
|
|
||||||
Hash.hash #Derived.hasher (@tag_discriminant #Derived.union)
|
|
||||||
when #Derived.union is
|
|
||||||
A -> #Derived.discrHasher
|
|
||||||
"###
|
"###
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -226,17 +221,13 @@ fn tag_one_label_newtype() {
|
||||||
derive_test(Hash, v!([A v!(U8) v!(STR)]), |golden| {
|
derive_test(Hash, v!([A v!(U8) v!(STR)]), |golden| {
|
||||||
assert_snapshot!(golden, @r###"
|
assert_snapshot!(golden, @r###"
|
||||||
# derived for [A U8 Str]
|
# derived for [A U8 Str]
|
||||||
# a, [A a1 a2] -[[hash_[A 2](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash
|
# hasher, [A a a1] -[[hash_[A 2](0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher
|
||||||
# a, [A a1 a2] -[[hash_[A 2](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash
|
# hasher, [A a a1] -[[hash_[A 2](0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher
|
||||||
# Specialization lambda sets:
|
# Specialization lambda sets:
|
||||||
# @<1>: [[hash_[A 2](0)]]
|
# @<1>: [[hash_[A 2](0)]]
|
||||||
#Derived.hash_[A 2] =
|
#Derived.hash_[A 2] =
|
||||||
\#Derived.hasher, #Derived.union ->
|
\#Derived.hasher, A #Derived.2 #Derived.3 ->
|
||||||
#Derived.discrHasher =
|
Hash.hash (Hash.hash #Derived.hasher #Derived.2) #Derived.3
|
||||||
Hash.hash #Derived.hasher (@tag_discriminant #Derived.union)
|
|
||||||
when #Derived.union is
|
|
||||||
A #Derived.4 #Derived.5 ->
|
|
||||||
Hash.hash (Hash.hash #Derived.discrHasher #Derived.4) #Derived.5
|
|
||||||
"###
|
"###
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1394,7 +1394,9 @@ mod hash {
|
||||||
),
|
),
|
||||||
TEST_HASHER,
|
TEST_HASHER,
|
||||||
),
|
),
|
||||||
RocList::from_slice(&[0, 0]),
|
RocList::from_slice(&[
|
||||||
|
// hash nothing because this is a newtype of a unit layout.
|
||||||
|
] as &[u8]),
|
||||||
RocList<u8>
|
RocList<u8>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1489,7 +1491,7 @@ mod hash {
|
||||||
TEST_HASHER,
|
TEST_HASHER,
|
||||||
),
|
),
|
||||||
RocList::from_slice(&[
|
RocList::from_slice(&[
|
||||||
0, 0, // A
|
// discriminant is skipped because it's a newtype
|
||||||
15, 23, 47
|
15, 23, 47
|
||||||
]),
|
]),
|
||||||
RocList<u8>
|
RocList<u8>
|
||||||
|
@ -1519,7 +1521,7 @@ mod hash {
|
||||||
),
|
),
|
||||||
RocList::from_slice(&[
|
RocList::from_slice(&[
|
||||||
0, 0, // Ok
|
0, 0, // Ok
|
||||||
0, 0, // A
|
// A is skipped because it is a newtype
|
||||||
15, 23, 47
|
15, 23, 47
|
||||||
]),
|
]),
|
||||||
RocList<u8>
|
RocList<u8>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue