Skip to content

Commit f481530

Browse files
authored
Merge pull request #13056 from xwu/much-of-a-muchness
[stdlib/Sema/AST] Adopt a commonly used implementation for combining hashes
2 parents 7d9a885 + 30d5e34 commit f481530

File tree

4 files changed

+51
-41
lines changed

4 files changed

+51
-41
lines changed

include/swift/AST/ASTContext.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ class ASTContext {
482482
FuncDecl *getEqualIntDecl() const;
483483

484484
/// Retrieve the declaration of
485-
/// Swift._mixForSynthesizedHashValue (Int, Int) -> Int.
486-
FuncDecl *getMixForSynthesizedHashValueDecl() const;
485+
/// Swift._combineHashValues(Int, Int) -> Int.
486+
FuncDecl *getCombineHashValuesDecl() const;
487487

488488
/// Retrieve the declaration of Swift._mixInt(Int) -> Int.
489489
FuncDecl *getMixIntDecl() const;

lib/AST/ASTContext.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL)
194194
/// func ==(Int, Int) -> Bool
195195
FuncDecl *EqualIntDecl = nullptr;
196196

197-
/// func _mixForSynthesizedHashValue(Int, Int) -> Int
198-
FuncDecl *MixForSynthesizedHashValueDecl = nullptr;
197+
/// func _combineHashValues(Int, Int) -> Int
198+
FuncDecl *CombineHashValuesDecl = nullptr;
199199

200200
/// func _mixInt(Int) -> Int
201201
FuncDecl *MixIntDecl = nullptr;
@@ -1048,9 +1048,9 @@ FuncDecl *ASTContext::getGetBoolDecl(LazyResolver *resolver) const {
10481048
return decl;
10491049
}
10501050

1051-
FuncDecl *ASTContext::getMixForSynthesizedHashValueDecl() const {
1052-
if (Impl.MixForSynthesizedHashValueDecl)
1053-
return Impl.MixForSynthesizedHashValueDecl;
1051+
FuncDecl *ASTContext::getCombineHashValuesDecl() const {
1052+
if (Impl.CombineHashValuesDecl)
1053+
return Impl.CombineHashValuesDecl;
10541054

10551055
auto resolver = getLazyResolver();
10561056
auto intType = getIntDecl()->getDeclaredType();
@@ -1066,8 +1066,8 @@ FuncDecl *ASTContext::getMixForSynthesizedHashValueDecl() const {
10661066
};
10671067

10681068
auto decl = lookupLibraryIntrinsicFunc(
1069-
*this, "_mixForSynthesizedHashValue", resolver, callback);
1070-
Impl.MixForSynthesizedHashValueDecl = decl;
1069+
*this, "_combineHashValues", resolver, callback);
1070+
Impl.CombineHashValuesDecl = decl;
10711071
return decl;
10721072
}
10731073

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -745,34 +745,34 @@ static Expr* integerLiteralExpr(ASTContext &C, int64_t value) {
745745
return integerExpr;
746746
}
747747

748-
/// Returns a new assignment expression that mixes the hash value of an
748+
/// Returns a new assignment expression that combines the hash value of an
749749
/// expression into a variable.
750750
/// \p C The AST context.
751-
/// \p resultVar The variable into which the hash value will be mixed.
752-
/// \p exprToHash The expression whose hash value should be mixed in.
753-
/// \return The expression that mixes the hash value into the result variable.
754-
static Expr* mixInHashExpr_hashValue(ASTContext &C,
755-
VarDecl* resultVar,
756-
Expr *exprToHash) {
751+
/// \p resultVar The variable into which the hash value will be combined.
752+
/// \p exprToHash The expression whose hash value should be combined.
753+
/// \return The expression that combines the hash value into the variable.
754+
static Expr* combineHashValuesAssignmentExpr(ASTContext &C,
755+
VarDecl* resultVar,
756+
Expr *exprToHash) {
757757
// <exprToHash>.hashValue
758758
auto hashValueExpr = new (C) UnresolvedDotExpr(exprToHash, SourceLoc(),
759759
C.Id_hashValue, DeclNameLoc(),
760760
/*implicit*/ true);
761761

762-
// _mixForSynthesizedHashValue(result, <exprToHash>.hashValue)
763-
auto mixinFunc = C.getMixForSynthesizedHashValueDecl();
764-
auto mixinFuncExpr = new (C) DeclRefExpr(mixinFunc, DeclNameLoc(),
765-
/*implicit*/ true);
762+
// _combineHashValues(result, <exprToHash>.hashValue)
763+
auto combineFunc = C.getCombineHashValuesDecl();
764+
auto combineFuncExpr = new (C) DeclRefExpr(combineFunc, DeclNameLoc(),
765+
/*implicit*/ true);
766766
auto rhsResultExpr = new (C) DeclRefExpr(resultVar, DeclNameLoc(),
767767
/*implicit*/ true);
768-
auto mixinResultExpr = CallExpr::createImplicit(
769-
C, mixinFuncExpr, { rhsResultExpr, hashValueExpr }, {});
768+
auto combineResultExpr = CallExpr::createImplicit(
769+
C, combineFuncExpr, { rhsResultExpr, hashValueExpr }, {});
770770

771-
// result = _mixForSynthesizedHashValue(result, <exprToHash>.hashValue)
771+
// result = _combineHashValues(result, <exprToHash>.hashValue)
772772
auto lhsResultExpr = new (C) DeclRefExpr(resultVar, DeclNameLoc(),
773773
/*implicit*/ true);
774774
auto assignExpr = new (C) AssignExpr(lhsResultExpr, SourceLoc(),
775-
mixinResultExpr, /*implicit*/ true);
775+
combineResultExpr, /*implicit*/ true);
776776
return assignExpr;
777777
}
778778

@@ -853,9 +853,9 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) {
853853

854854
// If the enum has no associated values, we use the ordinal alone as the
855855
// hash value, because that is sufficient for a good distribution. If any
856-
// case do have associated values, then the ordinal is used as the first
857-
// term mixed into _mixForSynthesizedHashValue, and the final result after
858-
// mixing in the payload is passed to _mixInt to improve the distribution.
856+
// case does have associated values, then the ordinal is used as the first
857+
// term combined into _combineHashValues, and the final result after
858+
// combining the payload is passed to _mixInt to improve the distribution.
859859

860860
// result = <ordinal>
861861
{
@@ -868,13 +868,14 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) {
868868
}
869869

870870
if (!hasNoAssociatedValues) {
871-
// Generate a sequence of expressions that mix the payload's hash values
872-
// into result.
871+
// Generate a sequence of expressions that combine the payload's hash
872+
// values into result.
873873
for (auto payloadVar : payloadVars) {
874874
auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(),
875875
/*implicit*/ true);
876-
// result = _mixForSynthesizedHashValue(result, <payloadVar>.hashValue)
877-
auto mixExpr = mixInHashExpr_hashValue(C, resultVar, payloadVarRef);
876+
// result = _combineHashValues(result, <payloadVar>.hashValue)
877+
auto mixExpr = combineHashValuesAssignmentExpr(C, resultVar,
878+
payloadVarRef);
878879
mixExpressions.emplace_back(ASTNode(mixExpr));
879880
}
880881

@@ -953,7 +954,7 @@ deriveBodyHashable_struct_hashValue(AbstractFunctionDecl *hashValueDecl) {
953954
auto storedProperties =
954955
structDecl->getStoredProperties(/*skipInaccessible=*/true);
955956

956-
// For each stored property, generate a statement that mixes its hash value
957+
// For each stored property, generate a statement that combines its hash value
957958
// into the result.
958959
for (auto propertyDecl : storedProperties) {
959960
auto propertyRef = new (C) DeclRefExpr(propertyDecl, DeclNameLoc(),
@@ -962,8 +963,9 @@ deriveBodyHashable_struct_hashValue(AbstractFunctionDecl *hashValueDecl) {
962963
/*implicit*/ true);
963964
auto selfPropertyExpr = new (C) DotSyntaxCallExpr(propertyRef, SourceLoc(),
964965
selfRef);
965-
// result = _mixForSynthesizedHashValue(result, <property>.hashValue)
966-
auto mixExpr = mixInHashExpr_hashValue(C, resultVar, selfPropertyExpr);
966+
// result = _combineHashValues(result, <property>.hashValue)
967+
auto mixExpr = combineHashValuesAssignmentExpr(C, resultVar,
968+
selfPropertyExpr);
967969
statements.emplace_back(ASTNode(mixExpr));
968970
}
969971

@@ -1014,11 +1016,11 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl,
10141016
// result = 0
10151017
// case B(let a0):
10161018
// result = 1
1017-
// result = _mixForSynthesizedHashValue(result, a0.hashValue)
1019+
// result = _combineHashValues(result, a0.hashValue)
10181020
// case C(let a0, let a1):
10191021
// result = 2
1020-
// result = _mixForSynthesizedHashValue(result, a0.hashValue)
1021-
// result = _mixForSynthesizedHashValue(result, a1.hashValue)
1022+
// result = _combineHashValues(result, a0.hashValue)
1023+
// result = _combineHashValues(result, a1.hashValue)
10221024
// }
10231025
// result = _mixInt(result)
10241026
// return result
@@ -1030,8 +1032,8 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl,
10301032
// var y: String
10311033
// @derived var hashValue: Int {
10321034
// var result: Int = 0
1033-
// result = _mixForSynthesizedHashValue(result, x.hashValue)
1034-
// result = _mixForSynthesizedHashValue(result, y.hashValue)
1035+
// result = _combineHashValues(result, x.hashValue)
1036+
// result = _combineHashValues(result, y.hashValue)
10351037
// result = _mixInt(result)
10361038
// return result
10371039
// }

stdlib/public/core/Hashing.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,21 @@ func _squeezeHashValue(_ hashValue: Int, _ upperBound: Int) -> Int {
188188

189189
/// Returns a new value that combines the two given hash values.
190190
///
191+
/// Combining is performed using [a hash function][ref] described by T.C. Hoad
192+
/// and J. Zobel, which is also adopted in the Boost C++ libraries.
193+
///
191194
/// This function is used by synthesized implementations of `hashValue` to
192195
/// combine the hash values of individual `struct` fields and associated values
193196
/// of `enum`s. It is factored out into a standard library function so that the
194197
/// specific hashing logic can be refined without requiring major changes to the
195198
/// code that creates the synthesized AST nodes.
199+
///
200+
/// [ref]: http://goanna.cs.rmit.edu.au/~jz/fulltext/jasist-tch.pdf
196201
@_transparent
197202
public // @testable
198-
func _mixForSynthesizedHashValue(_ oldValue: Int, _ nextValue: Int) -> Int {
199-
return 31 &* oldValue &+ nextValue
203+
func _combineHashValues(_ firstValue: Int, _ secondValue: Int) -> Int {
204+
let magic = 0x9e3779b9 as UInt // Based on the golden ratio.
205+
var x = UInt(bitPattern: firstValue)
206+
x ^= UInt(bitPattern: secondValue) &+ magic &+ (x &<< 6) &+ (x &>> 2)
207+
return Int(bitPattern: x)
200208
}

0 commit comments

Comments
 (0)