Skip to content

Commit eb21719

Browse files
committed
Hashable._hash(into:): Replace faux-pure _UnsafeHasher with a simple inout _Hasher parameter
Interestingly, this defeats an optimization that collapses the switch statement in the derived _hash(into:) of a simple enum into a single zext of the enum value. (See test/IRGen/enum_derived.swift)
1 parent 4724ab6 commit eb21719

24 files changed

+227
-245
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,8 @@ ERROR(broken_equatable_requirement,none,
21062106
"Equatable protocol is broken: unexpected requirement", ())
21072107
ERROR(broken_hashable_requirement,none,
21082108
"Hashable protocol is broken: unexpected requirement", ())
2109+
ERROR(broken_hashable_no_hasher,none,
2110+
"Hashable protocol is broken: _Hasher type not found", ())
21092111
ERROR(broken_errortype_requirement,none,
21102112
"Error protocol is broken: unexpected requirement", ())
21112113
ERROR(broken_int_hashable_conformance,none,

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ IDENTIFIER(alloc)
2626
IDENTIFIER(allocWithZone)
2727
IDENTIFIER(allZeros)
2828
IDENTIFIER(Any)
29-
IDENTIFIER(appending)
29+
IDENTIFIER(append)
3030
IDENTIFIER(ArrayLiteralElement)
3131
IDENTIFIER(atIndexedSubscript)
3232
IDENTIFIER_(bridgeToObjectiveC)

include/swift/AST/KnownStdlibTypes.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ KNOWN_STDLIB_TYPE_DECL(Sequence, NominalTypeDecl, 1)
5353
KNOWN_STDLIB_TYPE_DECL(Dictionary, NominalTypeDecl, 2)
5454
KNOWN_STDLIB_TYPE_DECL(AnyHashable, NominalTypeDecl, 0)
5555
KNOWN_STDLIB_TYPE_DECL(MutableCollection, ProtocolDecl, 1)
56-
KNOWN_STDLIB_TYPE_DECL_WITH_NAME(UnsafeHasher, "_UnsafeHasher", NominalTypeDecl, 0)
56+
KNOWN_STDLIB_TYPE_DECL_WITH_NAME(Hasher, "_Hasher", NominalTypeDecl, 0)
5757

5858
KNOWN_STDLIB_TYPE_DECL(AnyKeyPath, NominalTypeDecl, 0)
5959
KNOWN_STDLIB_TYPE_DECL(PartialKeyPath, NominalTypeDecl, 1)

lib/SILGen/SILGenExpr.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3584,8 +3584,7 @@ getOrCreateKeyPathEqualsAndHash(SILGenFunction &SGF,
35843584

35853585
SILValue hashCode;
35863586

3587-
// TODO: Combine hashes of the indexes using an _UnsafeHasher passed in as
3588-
// an extra parameter.
3587+
// TODO: Combine hashes of the indexes using an inout _Hasher
35893588
{
35903589
auto &index = indexes[0];
35913590

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 85 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ using namespace DerivedConformance;
3838
/// \p theEnum The enum whose elements and associated values should be checked.
3939
/// \p protocol The protocol being requested.
4040
/// \return True if all associated values of all elements of the enum conform.
41-
bool allAssociatedValuesConformToProtocol(TypeChecker &tc, EnumDecl *theEnum,
42-
ProtocolDecl *protocol) {
41+
static bool allAssociatedValuesConformToProtocol(TypeChecker &tc,
42+
EnumDecl *theEnum,
43+
ProtocolDecl *protocol) {
4344
auto declContext = theEnum->getDeclContext();
4445

4546
for (auto elt : theEnum->getAllElements()) {
@@ -76,9 +77,9 @@ bool allAssociatedValuesConformToProtocol(TypeChecker &tc, EnumDecl *theEnum,
7677
/// \p theStruct The struct whose stored properties should be checked.
7778
/// \p protocol The protocol being requested.
7879
/// \return True if all stored properties of the struct conform.
79-
bool allStoredPropertiesConformToProtocol(TypeChecker &tc,
80-
StructDecl *theStruct,
81-
ProtocolDecl *protocol) {
80+
static bool allStoredPropertiesConformToProtocol(TypeChecker &tc,
81+
StructDecl *theStruct,
82+
ProtocolDecl *protocol) {
8283
auto declContext = theStruct->getDeclContext();
8384

8485
auto storedProperties =
@@ -747,71 +748,83 @@ static Expr *integerLiteralExpr(ASTContext &C, int64_t value) {
747748

748749
/// Returns a new \c CallExpr representing
749750
///
750-
/// hasher.appending(hashable)
751+
/// hasher.append(hashable)
751752
///
752753
/// \param C The AST context to create the expression in.
753754
///
754-
/// \param hasher The base expression to make the call on.
755+
/// \param hasher The parameter decl to make the call on.
755756
///
756757
/// \param hashable The parameter to the call.
757-
static CallExpr *createHasherAppendingCall(ASTContext &C,
758-
Expr *hasher,
759-
Expr *hashable) {
760-
// hasher.appending(_:)
761-
DeclName name(C, C.Id_appending, {Identifier()});
762-
auto *appendingCall = new (C) UnresolvedDotExpr(hasher, SourceLoc(),
763-
name, DeclNameLoc(),
764-
/*implicit*/ true);
758+
static CallExpr *createHasherAppendCall(ASTContext &C,
759+
ParamDecl *hasher,
760+
Expr *hashable) {
761+
Expr *hasherExpr = new (C) DeclRefExpr(ConcreteDeclRef(hasher),
762+
DeclNameLoc(), /*implicit*/ true);
763+
DeclName name(C, C.Id_append, {Identifier()});
764+
// hasher.append(_:)
765+
auto *appendCall = new (C) UnresolvedDotExpr(hasherExpr, SourceLoc(),
766+
name, DeclNameLoc(),
767+
/*implicit*/ true);
765768

766-
// hasher.appending(hashable)
767-
return CallExpr::createImplicit(C, appendingCall, {hashable}, {Identifier()});
769+
// hasher.append(hashable)
770+
return CallExpr::createImplicit(C, appendCall, {hashable}, {Identifier()});
768771
}
769772

770773
static FuncDecl *
771774
deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl,
772775
NominalTypeDecl *typeDecl,
773776
void (*bodySynthesizer)(AbstractFunctionDecl *)) {
774-
// @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher
777+
// @derived func _hash(into hasher: inout _Hasher)
775778

776779
ASTContext &C = tc.Context;
777780
auto parentDC = cast<DeclContext>(parentDecl);
778781

779-
// Expected type: (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher
782+
// Expected type: (Self) -> (into: inout _Hasher) -> ()
780783
// Constructed as:
781784
// func type(input: Self,
782-
// output: func type(input: _UnsafeHasher,
783-
// output: _UnsafeHasher))
785+
// output: func type(input: inout _UnsafeHasher,
786+
// output: ()))
784787
// Created from the inside out:
785788

786-
Type hasherType = C.getUnsafeHasherDecl()->getDeclaredType();
789+
auto hasherDecl = C.getHasherDecl();
790+
if (!hasherDecl) {
791+
auto hashableProto = tc.Context.getProtocol(KnownProtocolKind::Hashable);
792+
tc.diagnose(hashableProto->getLoc(), diag::broken_hashable_no_hasher);
793+
return nullptr;
794+
}
795+
Type hasherType = hasherDecl->getDeclaredType();
787796

788797
// Params: self (implicit), hasher
789798
auto *selfDecl = ParamDecl::createSelf(SourceLoc(), parentDC);
790-
auto *hasherParam = new (C) ParamDecl(VarDecl::Specifier::Owned, SourceLoc(),
791-
SourceLoc(), C.Id_into, SourceLoc(),
792-
C.Id_hasher, hasherType, parentDC);
793-
hasherParam->setInterfaceType(hasherType);
799+
auto *hasherParamDecl = new (C) ParamDecl(VarDecl::Specifier::InOut,
800+
SourceLoc(),
801+
SourceLoc(), C.Id_into, SourceLoc(),
802+
C.Id_hasher, hasherType, parentDC);
803+
hasherParamDecl->setInterfaceType(hasherType);
794804

795805
ParameterList *params[] = {ParameterList::createWithoutLoc(selfDecl),
796-
ParameterList::createWithoutLoc(hasherParam)};
806+
ParameterList::createWithoutLoc(hasherParamDecl)};
807+
808+
// Return type: ()
809+
auto returnType = TupleType::getEmpty(C);
797810

798-
// Func name: _hash(into: _UnsafeHasher)
811+
// Func name: _hash(into: inout _Hasher) -> ()
799812
DeclName name(C, C.Id_hash, params[1]);
800813
auto *hashDecl = FuncDecl::create(C,
801814
SourceLoc(), StaticSpellingKind::None,
802815
SourceLoc(), name, SourceLoc(),
803816
/*Throws=*/false, SourceLoc(),
804817
nullptr, params,
805-
TypeLoc::withoutLoc(hasherType),
818+
TypeLoc::withoutLoc(returnType),
806819
parentDC);
807820
hashDecl->setImplicit();
808821
hashDecl->setBodySynthesizer(bodySynthesizer);
809822

810-
// Evaluate type of Self in (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher
823+
// Evaluate type of Self in (Self) -> (into: inout _Hasher) -> ()
811824
auto selfParam = computeSelfParam(hashDecl);
812-
auto inputTypeElt = TupleTypeElt(hasherType, C.Id_into);
813-
auto inputType = TupleType::get({inputTypeElt}, C);
814-
auto innerType = FunctionType::get(inputType, hasherType,
825+
auto inoutFlag = ParameterTypeFlags().withInOut(true);
826+
auto hasherParam = AnyFunctionType::Param(hasherType, C.Id_into, inoutFlag);
827+
auto innerType = FunctionType::get({hasherParam}, returnType,
815828
FunctionType::ExtInfo());
816829

817830
Type interfaceType;
@@ -820,7 +833,7 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl,
820833
interfaceType = GenericFunctionType::get(sig, {selfParam}, innerType,
821834
FunctionType::ExtInfo());
822835
} else {
823-
// (Self) -> innerType == (_UnsafeHasher) -> _UnsafeHasher
836+
// (Self) -> innerType == (inout _Hasher) -> ()
824837
interfaceType = FunctionType::get({selfParam}, innerType,
825838
FunctionType::ExtInfo());
826839
}
@@ -865,28 +878,31 @@ static void
865878
deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) {
866879
// enum SomeEnum {
867880
// case A, B, C
868-
// @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher {
881+
// @derived func _hash(into hasher: inout _Hasher) {
869882
// switch self {
870883
// case A:
871-
// return hasher.appending(0)
884+
// hasher.append(0)
872885
// case B:
873-
// return hasher.appending(1)
886+
// hasher.append(1)
874887
// case C:
875-
// return hasher.appending(2)
888+
// hasher.append(2)
876889
// }
877890
// }
878891
// }
879892
//
880893
// enum SomeEnumWithAssociatedValues {
881894
// case A, B(Int), C(String, Int)
882-
// @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher {
895+
// @derived func _hash(into hasher: inout _Hasher) {
883896
// switch self {
884897
// case A:
885-
// return hasher.appending(0)
898+
// hasher.append(0)
886899
// case B(let a0):
887-
// return hasher.appending(1).appending(a0)
900+
// hasher.append(1)
901+
// hasher.append(a0)
888902
// case C(let a0, let a1):
889-
// return hasher.appending(2).appending(a0).appending(a1)
903+
// hasher.append(2)
904+
// hasher.append(a0)
905+
// hasher.append(a1)
890906
// }
891907
// }
892908
// }
@@ -904,20 +920,12 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) {
904920
unsigned index = 0;
905921
SmallVector<ASTNode, 4> cases;
906922

907-
auto hasNoAssociatedValues = enumDecl->hasOnlyCasesWithoutAssociatedValues();
908-
909923
// For each enum element, generate a case statement that binds the associated
910924
// values so that they can be fed to the hasher.
911925
for (auto elt : enumDecl->getAllElements()) {
912926
// case .<elt>(let a0, let a1, ...):
913927
SmallVector<VarDecl*, 3> payloadVars;
914-
915-
// hasherExpr will hold the returned expression. It starts by the hasher
916-
// parameter, but we will add one or more chained method calls to it below.
917-
//
918-
// <hasherExpr> := hasher
919-
Expr* hasherExpr = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam),
920-
DeclNameLoc(), /*implicit*/ true);
928+
SmallVector<ASTNode, 3> statements;
921929

922930
auto payloadPattern = enumElementPayloadSubpattern(elt, 'a', hashIntoDecl,
923931
payloadVars);
@@ -929,32 +937,29 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) {
929937
auto labelItem = CaseLabelItem(/*IsDefault*/ false, pat, SourceLoc(),
930938
nullptr);
931939

932-
// If the enum has no associated values, we use the ordinal alone as the
933-
// hash value, because that is sufficient for a good distribution. If any
934-
// case does have associated values, then the ordinal is used as the first
935-
// term fed into the hasher.
940+
// If the enum has no associated values, we use the ordinal as the single
941+
// hash component, because that is sufficient for a good distribution. If
942+
// any case does have associated values, then the ordinal is used as the
943+
// first term fed into the hasher.
936944

937-
// <hasherExpr> := <hasherExpr>.appending(<ordinal>)
938945
{
946+
// Generate: hasher.append(<ordinal>)
939947
auto ordinalExpr = integerLiteralExpr(C, index++);
940-
hasherExpr = createHasherAppendingCall(C, hasherExpr, ordinalExpr);
948+
auto appendExpr = createHasherAppendCall(C, hasherParam, ordinalExpr);
949+
statements.emplace_back(ASTNode(appendExpr));
941950
}
942951

943-
if (!hasNoAssociatedValues) {
944-
// Generate a sequence of expressions that combine the payload's hash
945-
// values into result.
946-
for (auto payloadVar : payloadVars) {
947-
auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(),
948-
/*implicit*/ true);
949-
// <hasherExpr> := <hasherExpr>.appending(<payloadVar>)
950-
hasherExpr = createHasherAppendingCall(C, hasherExpr, payloadVarRef);
951-
}
952+
// Generate a sequence of statements that feed the payloads into hasher.
953+
for (auto payloadVar : payloadVars) {
954+
auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(),
955+
/*implicit*/ true);
956+
// Generate: hasher.append(<payloadVar>)
957+
auto appendExpr = createHasherAppendCall(C, hasherParam, payloadVarRef);
958+
statements.emplace_back(ASTNode(appendExpr));
952959
}
953960

954-
auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr);
955961
auto hasBoundDecls = !payloadVars.empty();
956-
auto body = BraceStmt::create(C, SourceLoc(),
957-
{ASTNode(returnStmt)}, SourceLoc());
962+
auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc());
958963
cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem, hasBoundDecls,
959964
SourceLoc(), body));
960965
}
@@ -965,7 +970,8 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) {
965970
auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), enumRef,
966971
SourceLoc(), cases, SourceLoc(), C);
967972

968-
auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(switchStmt)}, SourceLoc());
973+
auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(switchStmt)},
974+
SourceLoc());
969975
hashIntoDecl->setBody(body);
970976
}
971977

@@ -975,26 +981,21 @@ deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) {
975981
// struct SomeStruct {
976982
// var x: Int
977983
// var y: String
978-
// @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher {
979-
// return hasher.appending(x).appending(y)
984+
// @derived func _hash(into hasher: inout _Hasher) {
985+
// hasher.append(x)
986+
// hasher.append(y)
980987
// }
981988
// }
982989
auto parentDC = hashIntoDecl->getDeclContext();
983990
ASTContext &C = parentDC->getASTContext();
984991

985992
auto structDecl = parentDC->getAsStructOrStructExtensionContext();
993+
SmallVector<ASTNode, 6> statements;
986994
auto selfDecl = hashIntoDecl->getImplicitSelfDecl();
987995

988996
// Extract the decl for the hasher parameter.
989997
auto hasherParam = hashIntoDecl->getParameterList(1)->get(0);
990998

991-
// hasherExpr will hold the returned expression. It starts by the hasher
992-
// parameter, but we will add one or more chained method calls to it below.
993-
//
994-
// <hasherExpr> := hasher
995-
Expr* hasherExpr = new (C) DeclRefExpr(hasherParam, DeclNameLoc(),
996-
/*implicit*/ true);
997-
998999
auto storedProperties =
9991000
structDecl->getStoredProperties(/*skipInaccessible=*/true);
10001001

@@ -1006,12 +1007,12 @@ deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) {
10061007
/*implicit*/ true);
10071008
auto selfPropertyExpr = new (C) DotSyntaxCallExpr(propertyRef, SourceLoc(),
10081009
selfRef);
1009-
// <hasherExpr> := <hasherExpr>.appending(self.<property>)
1010-
hasherExpr = createHasherAppendingCall(C, hasherExpr, selfPropertyExpr);
1010+
// Generate: hasher.append(self.<property>)
1011+
auto appendExpr = createHasherAppendCall(C, hasherParam, selfPropertyExpr);
1012+
statements.emplace_back(ASTNode(appendExpr));
10111013
}
10121014

1013-
auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr);
1014-
auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(returnStmt)},
1015+
auto body = BraceStmt::create(C, SourceLoc(), statements,
10151016
SourceLoc(), /*implicit*/ true);
10161017
hashIntoDecl->setBody(body);
10171018
}
@@ -1223,7 +1224,6 @@ ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc,
12231224
return hashValueDecl;
12241225
}
12251226

1226-
tc.diagnose(requirement->getLoc(),
1227-
diag::broken_hashable_requirement);
1227+
tc.diagnose(requirement->getLoc(), diag::broken_hashable_requirement);
12281228
return nullptr;
12291229
}

stdlib/public/core/AnyHashable.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ internal protocol _AnyHashableBox {
4848
/// no comparison is possible. Otherwise, contains the result of `==`.
4949
func _isEqual(to: _AnyHashableBox) -> Bool?
5050
var _hashValue: Int { get }
51-
func _hash(_into hasher: _UnsafeHasher) -> _UnsafeHasher
51+
func _hash(_into hasher: inout _Hasher)
5252

5353
var _base: Any { get }
5454
func _downCastConditional<T>(into result: UnsafeMutablePointer<T>) -> Bool
@@ -96,8 +96,8 @@ internal struct _ConcreteHashableBox<Base : Hashable> : _AnyHashableBox {
9696

9797
@_inlineable // FIXME(sil-serialize-all)
9898
@_versioned // FIXME(sil-serialize-all)
99-
func _hash(_into hasher: _UnsafeHasher) -> _UnsafeHasher {
100-
return _baseHashable._hash(into: hasher)
99+
func _hash(_into hasher: inout _Hasher) {
100+
_baseHashable._hash(into: &hasher)
101101
}
102102

103103
@_inlineable // FIXME(sil-serialize-all)
@@ -304,8 +304,8 @@ extension AnyHashable : Hashable {
304304
}
305305

306306
@_inlineable // FIXME(sil-serialize-all)
307-
public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher {
308-
return _box._hash(_into: hasher)
307+
public func _hash(into hasher: inout _Hasher) {
308+
_box._hash(_into: &hasher)
309309
}
310310
}
311311

0 commit comments

Comments
 (0)