Skip to content

[stdlib/Sema/AST] Adopt a commonly used implementation for combining hashes #13056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,8 @@ class ASTContext {
FuncDecl *getEqualIntDecl() const;

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

/// Retrieve the declaration of Swift._mixInt(Int) -> Int.
FuncDecl *getMixIntDecl() const;
Expand Down
14 changes: 7 additions & 7 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL)
/// func ==(Int, Int) -> Bool
FuncDecl *EqualIntDecl = nullptr;

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

/// func _mixInt(Int) -> Int
FuncDecl *MixIntDecl = nullptr;
Expand Down Expand Up @@ -1048,9 +1048,9 @@ FuncDecl *ASTContext::getGetBoolDecl(LazyResolver *resolver) const {
return decl;
}

FuncDecl *ASTContext::getMixForSynthesizedHashValueDecl() const {
if (Impl.MixForSynthesizedHashValueDecl)
return Impl.MixForSynthesizedHashValueDecl;
FuncDecl *ASTContext::getCombineHashValuesDecl() const {
if (Impl.CombineHashValuesDecl)
return Impl.CombineHashValuesDecl;

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

auto decl = lookupLibraryIntrinsicFunc(
*this, "_mixForSynthesizedHashValue", resolver, callback);
Impl.MixForSynthesizedHashValueDecl = decl;
*this, "_combineHashValues", resolver, callback);
Impl.CombineHashValuesDecl = decl;
return decl;
}

Expand Down
62 changes: 32 additions & 30 deletions lib/Sema/DerivedConformanceEquatableHashable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,34 +745,34 @@ static Expr* integerLiteralExpr(ASTContext &C, int64_t value) {
return integerExpr;
}

/// Returns a new assignment expression that mixes the hash value of an
/// Returns a new assignment expression that combines the hash value of an
/// expression into a variable.
/// \p C The AST context.
/// \p resultVar The variable into which the hash value will be mixed.
/// \p exprToHash The expression whose hash value should be mixed in.
/// \return The expression that mixes the hash value into the result variable.
static Expr* mixInHashExpr_hashValue(ASTContext &C,
VarDecl* resultVar,
Expr *exprToHash) {
/// \p resultVar The variable into which the hash value will be combined.
/// \p exprToHash The expression whose hash value should be combined.
/// \return The expression that combines the hash value into the variable.
static Expr* combineHashValuesAssignmentExpr(ASTContext &C,
VarDecl* resultVar,
Expr *exprToHash) {
// <exprToHash>.hashValue
auto hashValueExpr = new (C) UnresolvedDotExpr(exprToHash, SourceLoc(),
C.Id_hashValue, DeclNameLoc(),
/*implicit*/ true);

// _mixForSynthesizedHashValue(result, <exprToHash>.hashValue)
auto mixinFunc = C.getMixForSynthesizedHashValueDecl();
auto mixinFuncExpr = new (C) DeclRefExpr(mixinFunc, DeclNameLoc(),
/*implicit*/ true);
// _combineHashValues(result, <exprToHash>.hashValue)
auto combineFunc = C.getCombineHashValuesDecl();
auto combineFuncExpr = new (C) DeclRefExpr(combineFunc, DeclNameLoc(),
/*implicit*/ true);
auto rhsResultExpr = new (C) DeclRefExpr(resultVar, DeclNameLoc(),
/*implicit*/ true);
auto mixinResultExpr = CallExpr::createImplicit(
C, mixinFuncExpr, { rhsResultExpr, hashValueExpr }, {});
auto combineResultExpr = CallExpr::createImplicit(
C, combineFuncExpr, { rhsResultExpr, hashValueExpr }, {});

// result = _mixForSynthesizedHashValue(result, <exprToHash>.hashValue)
// result = _combineHashValues(result, <exprToHash>.hashValue)
auto lhsResultExpr = new (C) DeclRefExpr(resultVar, DeclNameLoc(),
/*implicit*/ true);
auto assignExpr = new (C) AssignExpr(lhsResultExpr, SourceLoc(),
mixinResultExpr, /*implicit*/ true);
combineResultExpr, /*implicit*/ true);
return assignExpr;
}

Expand Down Expand Up @@ -853,9 +853,9 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) {

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

// result = <ordinal>
{
Expand All @@ -868,13 +868,14 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) {
}

if (!hasNoAssociatedValues) {
// Generate a sequence of expressions that mix the payload's hash values
// into result.
// Generate a sequence of expressions that combine the payload's hash
// values into result.
for (auto payloadVar : payloadVars) {
auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(),
/*implicit*/ true);
// result = _mixForSynthesizedHashValue(result, <payloadVar>.hashValue)
auto mixExpr = mixInHashExpr_hashValue(C, resultVar, payloadVarRef);
// result = _combineHashValues(result, <payloadVar>.hashValue)
auto mixExpr = combineHashValuesAssignmentExpr(C, resultVar,
payloadVarRef);
mixExpressions.emplace_back(ASTNode(mixExpr));
}

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

// For each stored property, generate a statement that mixes its hash value
// For each stored property, generate a statement that combines its hash value
// into the result.
for (auto propertyDecl : storedProperties) {
auto propertyRef = new (C) DeclRefExpr(propertyDecl, DeclNameLoc(),
Expand All @@ -962,8 +963,9 @@ deriveBodyHashable_struct_hashValue(AbstractFunctionDecl *hashValueDecl) {
/*implicit*/ true);
auto selfPropertyExpr = new (C) DotSyntaxCallExpr(propertyRef, SourceLoc(),
selfRef);
// result = _mixForSynthesizedHashValue(result, <property>.hashValue)
auto mixExpr = mixInHashExpr_hashValue(C, resultVar, selfPropertyExpr);
// result = _combineHashValues(result, <property>.hashValue)
auto mixExpr = combineHashValuesAssignmentExpr(C, resultVar,
selfPropertyExpr);
statements.emplace_back(ASTNode(mixExpr));
}

Expand Down Expand Up @@ -1014,11 +1016,11 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl,
// result = 0
// case B(let a0):
// result = 1
// result = _mixForSynthesizedHashValue(result, a0.hashValue)
// result = _combineHashValues(result, a0.hashValue)
// case C(let a0, let a1):
// result = 2
// result = _mixForSynthesizedHashValue(result, a0.hashValue)
// result = _mixForSynthesizedHashValue(result, a1.hashValue)
// result = _combineHashValues(result, a0.hashValue)
// result = _combineHashValues(result, a1.hashValue)
// }
// result = _mixInt(result)
// return result
Expand All @@ -1030,8 +1032,8 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl,
// var y: String
// @derived var hashValue: Int {
// var result: Int = 0
// result = _mixForSynthesizedHashValue(result, x.hashValue)
// result = _mixForSynthesizedHashValue(result, y.hashValue)
// result = _combineHashValues(result, x.hashValue)
// result = _combineHashValues(result, y.hashValue)
// result = _mixInt(result)
// return result
// }
Expand Down
12 changes: 10 additions & 2 deletions stdlib/public/core/Hashing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,21 @@ func _squeezeHashValue(_ hashValue: Int, _ upperBound: Int) -> Int {

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