diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index 0b417008822dd..856d4c48c4e97 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -400,8 +400,8 @@ class ASTContext { ProtocolDecl *getErrorDecl() const; CanType getExceptionType() const; -#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \ - /** Retrieve the declaration of Swift.NAME. */ \ +#define KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) \ + /** Retrieve the declaration of Swift.IDSTR. */ \ DECL_CLASS *get##NAME##Decl() const; #include "swift/AST/KnownStdlibTypes.def" @@ -471,12 +471,8 @@ class ASTContext { /// Retrieve the declaration of Swift.==(Int, Int) -> Bool. FuncDecl *getEqualIntDecl() const; - /// Retrieve the declaration of - /// Swift._combineHashValues(Int, Int) -> Int. - FuncDecl *getCombineHashValuesDecl() const; - - /// Retrieve the declaration of Swift._mixInt(Int) -> Int. - FuncDecl *getMixIntDecl() const; + /// Retrieve the declaration of Swift._hashValue(for: H) -> Int. + FuncDecl *getHashValueForDecl() const; /// Retrieve the declaration of Array.append(element:) FuncDecl *getArrayAppendElementDecl() const; diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 108b7fc39ae38..9863d6f2b2eaa 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -26,6 +26,7 @@ IDENTIFIER(alloc) IDENTIFIER(allocWithZone) IDENTIFIER(allZeros) IDENTIFIER(Any) +IDENTIFIER(appending) IDENTIFIER(ArrayLiteralElement) IDENTIFIER(atIndexedSubscript) IDENTIFIER_(bridgeToObjectiveC) @@ -54,14 +55,18 @@ IDENTIFIER(encoder) IDENTIFIER(error) IDENTIFIER(forKeyedSubscript) IDENTIFIER(Foundation) +IDENTIFIER(for) IDENTIFIER(forKey) IDENTIFIER(from) IDENTIFIER(fromRaw) +IDENTIFIER_(hash) +IDENTIFIER(hasher) IDENTIFIER(hashValue) IDENTIFIER(init) IDENTIFIER(initialize) IDENTIFIER(initStorage) IDENTIFIER(initialValue) +IDENTIFIER(into) IDENTIFIER(intValue) IDENTIFIER(Key) IDENTIFIER(KeyedDecodingContainer) diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index d450b13479537..33fc5bd5fe37b 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -15,16 +15,19 @@ // //===----------------------------------------------------------------------===// -#ifndef KNOWN_STDLIB_TYPE_DECL -/// KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) +#ifndef KNOWN_STDLIB_TYPE_DECL_WITH_NAME +/// KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) /// /// The macro is expanded for each known standard library type. NAME is /// bound to the unqualified name of the type. DECL_CLASS is bound to the /// Decl subclass it is expected to be an instance of. NUM_GENERIC_PARAMS is /// bound to the number of generic parameters the type is expected to have. -#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) +#define KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) #endif +#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \ + KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, #NAME, DECL_CLASS, NUM_GENERIC_PARAMS) + KNOWN_STDLIB_TYPE_DECL(Bool, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(Int, NominalTypeDecl, 0) @@ -50,6 +53,7 @@ KNOWN_STDLIB_TYPE_DECL(Sequence, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(Dictionary, NominalTypeDecl, 2) KNOWN_STDLIB_TYPE_DECL(AnyHashable, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(MutableCollection, ProtocolDecl, 1) +KNOWN_STDLIB_TYPE_DECL_WITH_NAME(UnsafeHasher, "_UnsafeHasher", NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(AnyKeyPath, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(PartialKeyPath, NominalTypeDecl, 1) @@ -79,3 +83,4 @@ KNOWN_STDLIB_TYPE_DECL(KeyedDecodingContainer, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(RangeReplaceableCollection, ProtocolDecl, 1) #undef KNOWN_STDLIB_TYPE_DECL +#undef KNOWN_STDLIB_TYPE_DECL_WITH_NAME diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 659a6ec35d8cc..65b1b39e8ba83 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -135,7 +135,7 @@ struct ASTContext::Implementation { /// The AnyObject type. CanType AnyObjectType; -#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \ +#define KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) \ /** The declaration of Swift.NAME. */ \ DECL_CLASS *NAME##Decl = nullptr; #include "swift/AST/KnownStdlibTypes.def" @@ -189,12 +189,9 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL) /// func ==(Int, Int) -> Bool FuncDecl *EqualIntDecl = nullptr; - /// func _combineHashValues(Int, Int) -> Int - FuncDecl *CombineHashValuesDecl = nullptr; + /// func _hashValue(for: H) -> Int + FuncDecl *HashValueForDecl = nullptr; - /// func _mixInt(Int) -> Int - FuncDecl *MixIntDecl = nullptr; - /// func append(Element) -> void FuncDecl *ArrayAppendElementDecl = nullptr; @@ -616,11 +613,11 @@ FuncDecl *ASTContext::getPlusFunctionOnString() const { return Impl.PlusFunctionOnString; } -#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \ +#define KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) \ DECL_CLASS *ASTContext::get##NAME##Decl() const { \ if (!Impl.NAME##Decl) \ Impl.NAME##Decl = dyn_cast_or_null( \ - findStdlibType(*this, #NAME, NUM_GENERIC_PARAMS)); \ + findStdlibType(*this, IDSTR, NUM_GENERIC_PARAMS)); \ return Impl.NAME##Decl; \ } #include "swift/AST/KnownStdlibTypes.def" @@ -989,44 +986,29 @@ FuncDecl *ASTContext::getGetBoolDecl(LazyResolver *resolver) const { return decl; } -FuncDecl *ASTContext::getCombineHashValuesDecl() const { - if (Impl.CombineHashValuesDecl) - return Impl.CombineHashValuesDecl; - - auto resolver = getLazyResolver(); - auto intType = getIntDecl()->getDeclaredType(); - - auto callback = [&](Type inputType, Type resultType) { - // Look for the signature (Int, Int) -> Int - auto tupleType = dyn_cast(inputType.getPointer()); - assert(tupleType); - return tupleType->getNumElements() == 2 && - tupleType->getElementType(0)->isEqual(intType) && - tupleType->getElementType(1)->isEqual(intType) && - resultType->isEqual(intType); - }; - - auto decl = lookupLibraryIntrinsicFunc( - *this, "_combineHashValues", resolver, callback); - Impl.CombineHashValuesDecl = decl; - return decl; -} - -FuncDecl *ASTContext::getMixIntDecl() const { - if (Impl.MixIntDecl) - return Impl.MixIntDecl; +FuncDecl *ASTContext::getHashValueForDecl() const { + if (Impl.HashValueForDecl) + return Impl.HashValueForDecl; - auto resolver = getLazyResolver(); - auto intType = getIntDecl()->getDeclaredType(); - - auto callback = [&](Type inputType, Type resultType) { - // Look for the signature (Int) -> Int - return inputType->isEqual(intType) && resultType->isEqual(intType); - }; - - auto decl = lookupLibraryIntrinsicFunc(*this, "_mixInt", resolver, callback); - Impl.MixIntDecl = decl; - return decl; + SmallVector results; + lookupInSwiftModule("_hashValue", results); + for (auto result : results) { + auto *fd = dyn_cast(result); + if (!fd) + continue; + auto paramLists = fd->getParameterLists(); + if (paramLists.size() != 1 || paramLists[0]->size() != 1) + continue; + auto paramDecl = paramLists[0]->get(0); + if (paramDecl->getArgumentName() != Id_for) + continue; + auto genericParams = fd->getGenericParams(); + if (!genericParams || genericParams->size() != 1) + continue; + Impl.HashValueForDecl = fd; + return fd; + } + return nullptr; } FuncDecl *ASTContext::getArrayAppendElementDecl() const { diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 72d725b732090..0868bed72b415 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -3583,8 +3583,8 @@ getOrCreateKeyPathEqualsAndHash(SILGenModule &SGM, SILValue hashCode; - // TODO: Combine hashes of the indexes. There isn't a great hash combining - // interface in the standard library to do this yet. + // TODO: Combine hashes of the indexes using an _UnsafeHasher passed in as + // an extra parameter. { auto &index = indexes[0]; diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index 8cb788c045e60..d7027ece5a310 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -23,6 +23,7 @@ #include "swift/AST/Module.h" #include "swift/AST/Pattern.h" #include "swift/AST/ParameterList.h" +#include "swift/AST/ProtocolConformance.h" #include "swift/AST/Types.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/SmallString.h" @@ -734,7 +735,7 @@ ValueDecl *DerivedConformance::deriveEquatable(TypeChecker &tc, /// \p C The AST context. /// \p value The integer value. /// \return The integer literal expression. -static Expr* integerLiteralExpr(ASTContext &C, int64_t value) { +static Expr *integerLiteralExpr(ASTContext &C, int64_t value) { llvm::SmallString<8> integerVal; APInt(32, value).toString(integerVal, 10, /*signed*/ false); auto integerStr = C.AllocateCopy(integerVal); @@ -744,66 +745,173 @@ static Expr* integerLiteralExpr(ASTContext &C, int64_t value) { return integerExpr; } -/// 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 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) { - // .hashValue - auto hashValueExpr = new (C) UnresolvedDotExpr(exprToHash, SourceLoc(), - C.Id_hashValue, DeclNameLoc(), - /*implicit*/ true); +/// Returns a new \c CallExpr representing +/// +/// hasher.appending(hashable) +/// +/// \param C The AST context to create the expression in. +/// +/// \param hasher The base expression to make the call on. +/// +/// \param hashable The parameter to the call. +static CallExpr *createHasherAppendingCall(ASTContext &C, + Expr *hasher, + Expr *hashable) { + // hasher.appending(_:) + DeclName name(C, C.Id_appending, {Identifier()}); + auto *appendingCall = new (C) UnresolvedDotExpr(hasher, SourceLoc(), + name, DeclNameLoc(), + /*implicit*/ true); + + // hasher.appending(hashable) + return CallExpr::createImplicit(C, appendingCall, {hashable}, {Identifier()}); +} - // _combineHashValues(result, .hashValue) - auto combineFunc = C.getCombineHashValuesDecl(); - auto combineFuncExpr = new (C) DeclRefExpr(combineFunc, DeclNameLoc(), - /*implicit*/ true); - auto rhsResultExpr = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto combineResultExpr = CallExpr::createImplicit( - C, combineFuncExpr, { rhsResultExpr, hashValueExpr }, {}); +static FuncDecl * +deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, + NominalTypeDecl *typeDecl, + void (*bodySynthesizer)(AbstractFunctionDecl *)) { + // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher - // result = _combineHashValues(result, .hashValue) - auto lhsResultExpr = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto assignExpr = new (C) AssignExpr(lhsResultExpr, SourceLoc(), - combineResultExpr, /*implicit*/ true); - return assignExpr; + ASTContext &C = tc.Context; + auto parentDC = cast(parentDecl); + + // Expected type: (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher + // Constructed as: + // func type(input: Self, + // output: func type(input: _UnsafeHasher, + // output: _UnsafeHasher)) + // Created from the inside out: + + Type hasherType = C.getUnsafeHasherDecl()->getDeclaredType(); + + // Params: self (implicit), hasher + auto *selfDecl = ParamDecl::createSelf(SourceLoc(), parentDC); + auto *hasherParam = new (C) ParamDecl(VarDecl::Specifier::Owned, SourceLoc(), + SourceLoc(), C.Id_into, SourceLoc(), + C.Id_hasher, hasherType, parentDC); + hasherParam->setInterfaceType(hasherType); + + ParameterList *params[] = {ParameterList::createWithoutLoc(selfDecl), + ParameterList::createWithoutLoc(hasherParam)}; + + // Func name: _hash(into: _UnsafeHasher) + DeclName name(C, C.Id_hash, params[1]); + auto *hashDecl = FuncDecl::create(C, + SourceLoc(), StaticSpellingKind::None, + SourceLoc(), name, SourceLoc(), + /*Throws=*/false, SourceLoc(), + nullptr, params, + TypeLoc::withoutLoc(hasherType), + parentDC); + hashDecl->setImplicit(); + hashDecl->setBodySynthesizer(bodySynthesizer); + + // Evaluate type of Self in (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher + auto selfParam = computeSelfParam(hashDecl); + auto inputTypeElt = TupleTypeElt(hasherType, C.Id_into); + auto inputType = TupleType::get({inputTypeElt}, C); + auto innerType = FunctionType::get(inputType, hasherType, + FunctionType::ExtInfo()); + + Type interfaceType; + if (auto sig = parentDC->getGenericSignatureOfContext()) { + hashDecl->setGenericEnvironment(parentDC->getGenericEnvironmentOfContext()); + interfaceType = GenericFunctionType::get(sig, {selfParam}, innerType, + FunctionType::ExtInfo()); + } else { + // (Self) -> innerType == (_UnsafeHasher) -> _UnsafeHasher + interfaceType = FunctionType::get({selfParam}, innerType, + FunctionType::ExtInfo()); + } + hashDecl->setInterfaceType(interfaceType); + hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); + + // If we aren't synthesizing into an imported/derived type, the derived conformance is + // either from the type itself or an extension, in which case we will emit the + // declaration normally. + // + // We're checking for a source file here rather than the presence of a Clang + // node because otherwise we wouldn't catch synthesized Hashable structs, like + // SE-0112's imported Error types. + if (!isa(parentDC->getModuleScopeContext())) { + C.addExternalDecl(hashDecl); + // Assume the new function is already typechecked; TypeChecker::validateDecl + // would otherwise reject it. + hashDecl->setValidationStarted(); + } + + cast(parentDecl)->addMember(hashDecl); + return hashDecl; } +/// Derive the body for the _hash(into:) method when hashValue has a +/// user-supplied implementation. static void -deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { - auto parentDC = hashValueDecl->getDeclContext(); +deriveBodyHashable_compat_hashInto(AbstractFunctionDecl *hashIntoDecl) { + // func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // return hasher.appending(self.hashValue) + // } + auto parentDC = hashIntoDecl->getDeclContext(); + ASTContext &C = parentDC->getASTContext(); + + auto selfDecl = hashIntoDecl->getImplicitSelfDecl(); + auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(), + /*implicit*/ true); + auto hashValueExpr = new (C) UnresolvedDotExpr(selfRef, SourceLoc(), + C.Id_hashValue, DeclNameLoc(), + /*implicit*/ true); + auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); + auto hasherRef = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam), + DeclNameLoc(), /*implicit*/ true); + auto hasherExpr = createHasherAppendingCall(C, hasherRef, hashValueExpr); + + auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); + auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(returnStmt)}, + SourceLoc(), /*implicit*/ true); + hashIntoDecl->setBody(body); +} + +/// Derive the body for the '_hash(into:)' method for an enum. +static void +deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { + // enum SomeEnum { + // case A, B, C + // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // switch self { + // case A: + // return hasher.appending(0) + // case B: + // return hasher.appending(1) + // case C: + // return hasher.appending(2) + // } + // } + // } + // + // enum SomeEnumWithAssociatedValues { + // case A, B(Int), C(String, Int) + // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // switch self { + // case A: + // return hasher.appending(0) + // case B(let a0): + // return hasher.appending(1).appending(a0) + // case C(let a0, let a1): + // return hasher.appending(2).appending(a0).appending(a1) + // } + // } + // } + auto parentDC = hashIntoDecl->getDeclContext(); ASTContext &C = parentDC->getASTContext(); auto enumDecl = parentDC->getAsEnumOrEnumExtensionContext(); - SmallVector statements; - auto selfDecl = hashValueDecl->getImplicitSelfDecl(); + auto selfDecl = hashIntoDecl->getImplicitSelfDecl(); Type enumType = selfDecl->getType(); - Type intType = C.getIntDecl()->getDeclaredType(); - auto resultVar = new (C) VarDecl(/*IsStatic*/ false, VarDecl::Specifier::Var, - /*IsCaptureList*/ false, SourceLoc(), - C.getIdentifier("result"), intType, - hashValueDecl); - resultVar->setInterfaceType(intType); - resultVar->setImplicit(); - - // var result - Pattern *resultPat = new (C) NamedPattern(resultVar, /*implicit*/ true); - resultPat->setType(intType); - resultPat = new (C) TypedPattern(resultPat, TypeLoc::withoutLoc(intType)); - resultPat->setType(intType); - auto resultBind = PatternBindingDecl::create(C, SourceLoc(), - StaticSpellingKind::None, - SourceLoc(), - resultPat, nullptr, - hashValueDecl); + // Extract the decl for the hasher parameter. + auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); unsigned index = 0; SmallVector cases; @@ -811,13 +919,19 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { auto hasNoAssociatedValues = enumDecl->hasOnlyCasesWithoutAssociatedValues(); // For each enum element, generate a case statement that binds the associated - // values so that their hash values can be obtained. + // values so that they can be fed to the hasher. for (auto elt : enumDecl->getAllElements()) { // case .(let a0, let a1, ...): SmallVector payloadVars; - SmallVector combineExprs; - auto payloadPattern = enumElementPayloadSubpattern(elt, 'a', hashValueDecl, + // hasherExpr will hold the returned expression. It starts by the hasher + // parameter, but we will add one or more chained method calls to it below. + // + // := hasher + Expr* hasherExpr = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam), + DeclNameLoc(), /*implicit*/ true); + + auto payloadPattern = enumElementPayloadSubpattern(elt, 'a', hashIntoDecl, payloadVars); auto pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType), SourceLoc(), SourceLoc(), @@ -830,17 +944,12 @@ 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 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. + // term fed into the hasher. - // result = + // := .appending() { auto ordinalExpr = integerLiteralExpr(C, index++); - auto resultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto assignExpr = new (C) AssignExpr(resultRef, SourceLoc(), - ordinalExpr, /*implicit*/ true); - combineExprs.emplace_back(ASTNode(assignExpr)); + hasherExpr = createHasherAppendingCall(C, hasherExpr, ordinalExpr); } if (!hasNoAssociatedValues) { @@ -849,15 +958,15 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { for (auto payloadVar : payloadVars) { auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), /*implicit*/ true); - // result = _combineHashValues(result, .hashValue) - auto combineExpr = combineHashValuesAssignmentExpr(C, resultVar, - payloadVarRef); - combineExprs.emplace_back(ASTNode(combineExpr)); + // := .appending() + hasherExpr = createHasherAppendingCall(C, hasherExpr, payloadVarRef); } } + auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); auto hasBoundDecls = !payloadVars.empty(); - auto body = BraceStmt::create(C, SourceLoc(), combineExprs, SourceLoc()); + auto body = BraceStmt::create(C, SourceLoc(), + {ASTNode(returnStmt)}, SourceLoc()); cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem, hasBoundDecls, SourceLoc(), body)); } @@ -868,66 +977,40 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), enumRef, SourceLoc(), cases, SourceLoc(), C); - statements.push_back(resultBind); - statements.push_back(switchStmt); - - // generate: return result - auto resultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true, - AccessSemantics::Ordinary, intType); - auto returnStmt = new (C) ReturnStmt(SourceLoc(), resultRef); - statements.push_back(returnStmt); - - auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); - hashValueDecl->setBody(body); + auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(switchStmt)}, SourceLoc()); + hashIntoDecl->setBody(body); } -/// Derive the body for the 'hashValue' getter for a struct. +/// Derive the body for the '_hash(into:)' method for a struct. static void -deriveBodyHashable_struct_hashValue(AbstractFunctionDecl *hashValueDecl) { - auto parentDC = hashValueDecl->getDeclContext(); +deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { + // struct SomeStruct { + // var x: Int + // var y: String + // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // return hasher.appending(x).appending(y) + // } + // } + auto parentDC = hashIntoDecl->getDeclContext(); ASTContext &C = parentDC->getASTContext(); auto structDecl = parentDC->getAsStructOrStructExtensionContext(); - SmallVector statements; - auto selfDecl = hashValueDecl->getImplicitSelfDecl(); + auto selfDecl = hashIntoDecl->getImplicitSelfDecl(); - Type intType = C.getIntDecl()->getDeclaredType(); + // Extract the decl for the hasher parameter. + auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); - auto resultVar = new (C) VarDecl(/*IsStatic*/ false, VarDecl::Specifier::Var, - /*IsCaptureList*/ false, SourceLoc(), - C.getIdentifier("result"), intType, - hashValueDecl); - resultVar->setInterfaceType(intType); - resultVar->setImplicit(); - - // var result: Int - Pattern *resultPat = new (C) NamedPattern(resultVar, /*implicit*/ true); - resultPat->setType(intType); - resultPat = new (C) TypedPattern(resultPat, TypeLoc::withoutLoc(intType)); - resultPat->setType(intType); - auto resultBind = PatternBindingDecl::create(C, SourceLoc(), - StaticSpellingKind::None, - SourceLoc(), - resultPat, nullptr, - hashValueDecl); - statements.push_back(resultBind); - - // result = 0 - { - auto resultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto assignExpr = new (C) AssignExpr(resultRef, SourceLoc(), - integerLiteralExpr(C, 0), + // hasherExpr will hold the returned expression. It starts by the hasher + // parameter, but we will add one or more chained method calls to it below. + // + // := hasher + Expr* hasherExpr = new (C) DeclRefExpr(hasherParam, DeclNameLoc(), /*implicit*/ true); - statements.emplace_back(ASTNode(assignExpr)); - } auto storedProperties = structDecl->getStoredProperties(/*skipInaccessible=*/true); - // For each stored property, generate a statement that combines its hash value - // into the result. + // Feed each stored property into the hasher. for (auto propertyDecl : storedProperties) { auto propertyRef = new (C) DeclRefExpr(propertyDecl, DeclNameLoc(), /*implicit*/ true); @@ -935,74 +1018,44 @@ deriveBodyHashable_struct_hashValue(AbstractFunctionDecl *hashValueDecl) { /*implicit*/ true); auto selfPropertyExpr = new (C) DotSyntaxCallExpr(propertyRef, SourceLoc(), selfRef); - // result = _combineHashValues(result, .hashValue) - auto combineExpr = combineHashValuesAssignmentExpr(C, resultVar, - selfPropertyExpr); - statements.emplace_back(ASTNode(combineExpr)); + // := .appending(self.) + hasherExpr = createHasherAppendingCall(C, hasherExpr, selfPropertyExpr); } - { - // return result - auto resultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true, - AccessSemantics::Ordinary, intType); - auto returnStmt = new (C) ReturnStmt(SourceLoc(), resultRef); - statements.push_back(returnStmt); - } + auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); + auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(returnStmt)}, + SourceLoc(), /*implicit*/ true); + hashIntoDecl->setBody(body); +} - auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); +/// Derive the body for the 'hashValue' getter. +static void +deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { + auto parentDC = hashValueDecl->getDeclContext(); + ASTContext &C = parentDC->getASTContext(); + + // return _hashValue(for: self) + auto *hashFunc = C.getHashValueForDecl(); + auto hashExpr = new (C) DeclRefExpr(hashFunc, DeclNameLoc(), + /*implicit*/ true); + auto selfDecl = hashValueDecl->getImplicitSelfDecl(); + auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(), + /*implicit*/ true); + auto callExpr = CallExpr::createImplicit(C, hashExpr, + { selfRef }, { C.Id_for }); + auto returnStmt = new (C) ReturnStmt(SourceLoc(), callExpr); + + auto body = BraceStmt::create(C, SourceLoc(), {returnStmt}, SourceLoc(), + /*implicit*/ true); hashValueDecl->setBody(body); } -/// Derive a 'hashValue' implementation for an enum. +/// Derive a 'hashValue' implementation. static ValueDecl * deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl, - NominalTypeDecl *typeDecl, - void (*bodySynthesizer)(AbstractFunctionDecl *)) { - // enum SomeEnum { - // case A, B, C - // @derived var hashValue: Int { - // var result: Int - // switch self { - // case A: - // result = 0 - // case B: - // result = 1 - // case C: - // result = 2 - // } - // return result - // } - // } - // - // enum SomeEnumWithAssociatedValues { - // case A, B(Int), C(String, Int) - // @derived var hashValue: Int { - // var result: Int - // switch self { - // case A: - // result = 0 - // case B(let a0): - // result = 1 - // result = _combineHashValues(result, a0.hashValue) - // case C(let a0, let a1): - // result = 2 - // result = _combineHashValues(result, a0.hashValue) - // result = _combineHashValues(result, a1.hashValue) - // } - // return result - // } - // } - // - // struct SomeStruct { - // var x: Int - // var y: String - // @derived var hashValue: Int { - // var result = 0 - // result = _combineHashValues(result, x.hashValue) - // result = _combineHashValues(result, y.hashValue) - // return result - // } + NominalTypeDecl *typeDecl) { + // @derived var hashValue: Int { + // return _hashValue(for: self) // } ASTContext &C = tc.Context; @@ -1045,7 +1098,7 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl, /*GenericParams=*/nullptr, params, TypeLoc::withoutLoc(intType), parentDC); getterDecl->setImplicit(); - getterDecl->setBodySynthesizer(bodySynthesizer); + getterDecl->setBodySynthesizer(&deriveBodyHashable_hashValue); // Compute the type of hashValue(). Type methodType = FunctionType::get(TupleType::getEmpty(tc.Context), intType); @@ -1097,40 +1150,103 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl, return hashValueDecl; } +static ValueDecl * +getHashValueRequirement(ASTContext &C) { + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + for (auto member: hashableProto->getMembers()) { + if (auto fd = dyn_cast(member)) { + if (fd->getBaseName() == C.Id_hashValue) + return fd; + } + } + return nullptr; +} + +static ProtocolConformance * +getHashableConformance(Decl *parentDecl) { + ASTContext &C = parentDecl->getASTContext(); + auto DC = cast(parentDecl); + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + for (auto conformance: DC->getLocalConformances()) { + if (conformance->getProtocol() == hashableProto) { + return conformance; + } + } + return nullptr; +} + bool DerivedConformance::canDeriveHashable(TypeChecker &tc, NominalTypeDecl *type, ValueDecl *requirement) { - auto hashableProto = tc.Context.getProtocol(KnownProtocolKind::Hashable); - return canDeriveConformance(tc, type, hashableProto); + if (!isa(type) && !isa(type) && !isa(type)) + return false; + // FIXME: This is not actually correct. We cannot promise to always + // provide a witness here in all cases. Unfortunately, figuring out + // whether this is actually possible requires a parent decl context. + // When the answer is no, DerivedConformance::deriveHashable will output + // its own diagnostics. + return true; } ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc, Decl *parentDecl, NominalTypeDecl *type, ValueDecl *requirement) { - // Conformance can't be synthesized in an extension; we allow it as a special - // case for enums with no associated values to preserve source compatibility. - auto theEnum = dyn_cast(type); - if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) && - type != parentDecl) { - auto hashableProto = tc.Context.getProtocol(KnownProtocolKind::Hashable); - auto hashableType = hashableProto->getDeclaredType(); - tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, - hashableType); - return nullptr; + ASTContext &C = parentDecl->getASTContext(); + + // var hashValue: Int + if (requirement->getBaseName() == C.Id_hashValue) { + // We always allow hashValue to be synthesized; invalid cases are diagnosed + // during _hash(into:) synthesis. + return deriveHashable_hashValue(tc, parentDecl, type); } - // Build the necessary decl. - if (requirement->getBaseName() == "hashValue") { - if (theEnum) - return deriveHashable_hashValue(tc, parentDecl, theEnum, - &deriveBodyHashable_enum_hashValue); - else if (auto theStruct = dyn_cast(type)) - return deriveHashable_hashValue(tc, parentDecl, theStruct, - &deriveBodyHashable_struct_hashValue); - else - llvm_unreachable("todo"); + // Hashable._hash(into:) + if (requirement->getBaseName() == C.Id_hash) { + // Start by resolving hashValue conformance. + auto hashValueReq = getHashValueRequirement(C); + auto conformance = getHashableConformance(parentDecl); + auto hashValueDecl = conformance->getWitnessDecl(hashValueReq, &tc); + + if (hashValueDecl->isImplicit()) { + // Neither hashValue nor _hash(into:) is explicitly defined; we need to do + // a full Hashable derivation. + + // Refuse to synthesize Hashable if type isn't a struct or enum, or if it + // has non-Hashable stored properties/associated values. + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + if (!canDeriveConformance(tc, type, hashableProto)) { + tc.diagnose(parentDecl->getLoc(), diag::type_does_not_conform, + type->getDeclaredType(), hashableProto->getDeclaredType()); + return nullptr; + } + // Hashable can't be fully synthesized in an extension; we allow it as a + // special case for enums with no associated values to preserve source + // compatibility. + auto theEnum = dyn_cast(type); + if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) && + type != parentDecl) { + tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, + hashableProto->getDeclaredType()); + return nullptr; + } + if (theEnum) + return deriveHashable_hashInto(tc, parentDecl, theEnum, + &deriveBodyHashable_enum_hashInto); + else if (auto theStruct = dyn_cast(type)) + return deriveHashable_hashInto(tc, parentDecl, theStruct, + &deriveBodyHashable_struct_hashInto); + else + llvm_unreachable("Attempt to derive Hashable for a type other " + "than a struct or enum"); + } else { + // We can always derive _hash(into:) if hashValue has an explicit + // implementation. + return deriveHashable_hashInto(tc, parentDecl, type, + &deriveBodyHashable_compat_hashInto); + } } + tc.diagnose(requirement->getLoc(), diag::broken_hashable_requirement); return nullptr; diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 610db73e59c8d..22108b6c8e17f 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -31,6 +31,13 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, if (!knownProtocol) return false; + if (*knownProtocol == KnownProtocolKind::Hashable) { + // We can always complete a partial Hashable implementation, and we can + // synthesize a full Hashable implementation for structs and enums with + // Hashable components. + return canDeriveHashable(tc, nominal, protocol); + } + if (auto *enumDecl = dyn_cast(nominal)) { switch (*knownProtocol) { // The presence of a raw type is an explicit declaration that @@ -38,12 +45,10 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, case KnownProtocolKind::RawRepresentable: return enumDecl->hasRawType(); - // Enums without associated values can implicitly derive Equatable and - // Hashable conformance. + // Enums without associated values can implicitly derive Equatable + // conformance. case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, enumDecl, protocol); - case KnownProtocolKind::Hashable: - return canDeriveHashable(tc, enumDecl, protocol); // @objc enums can explicitly derive their _BridgedNSError conformance. case KnownProtocolKind::BridgedNSError: @@ -87,13 +92,11 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, return true; } - // Structs can explicitly derive Equatable and Hashable conformance. + // Structs can explicitly derive Equatable conformance. if (auto structDecl = dyn_cast(nominal)) { switch (*knownProtocol) { case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, structDecl, protocol); - case KnownProtocolKind::Hashable: - return canDeriveHashable(tc, structDecl, protocol); default: return false; } @@ -162,6 +165,13 @@ ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc, return getRequirement(KnownProtocolKind::Encodable); } + // Hashable._hash(into: _UnsafeHasher) -> _UnsafeHasher + if (name.isCompoundName() && name.getBaseName() == ctx.Id_hash) { + auto argumentNames = name.getArgumentNames(); + if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_into) + return getRequirement(KnownProtocolKind::Hashable); + } + return nullptr; } diff --git a/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb b/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb index dacabd5a8e202..2e0284ff362ba 100644 --- a/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb +++ b/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb @@ -1089,6 +1089,15 @@ struct PersistentState { static var runNoTestsWasCalled: Bool = false static var ranSomething: Bool = false static var complaintInstalled = false + static var hashingKeyOverridden = false + + static func overrideHashingKey() { + if !hashingKeyOverridden { + // FIXME(hasher): This has to run before creating the first Set/Dictionary + _Hashing.secretKey = (0, 0) + hashingKeyOverridden = true + } + } static func complainIfNothingRuns() { if !complaintInstalled { @@ -1200,6 +1209,7 @@ func stopTrackingObjects(_: UnsafePointer) -> Int public final class TestSuite { public init(_ name: String) { + PersistentState.overrideHashingKey() self.name = name _precondition( _testNameToIndex[name] == nil, diff --git a/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift b/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift index 29b779905ef77..9d0eac7313779 100644 --- a/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift +++ b/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift @@ -17,6 +17,9 @@ extension _CFObject { public var hashValue: Int { return Int(bitPattern: CFHash(self)) } + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(self.hashValue) + } public static func ==(left: Self, right: Self) -> Bool { return CFEqual(left, right) } diff --git a/stdlib/public/core/AnyHashable.swift b/stdlib/public/core/AnyHashable.swift index ef3087b7c95eb..79568acdc2ac7 100644 --- a/stdlib/public/core/AnyHashable.swift +++ b/stdlib/public/core/AnyHashable.swift @@ -48,6 +48,7 @@ internal protocol _AnyHashableBox { /// no comparison is possible. Otherwise, contains the result of `==`. func _isEqual(to: _AnyHashableBox) -> Bool? var _hashValue: Int { get } + func _hash(_into hasher: _UnsafeHasher) -> _UnsafeHasher var _base: Any { get } func _downCastConditional(into result: UnsafeMutablePointer) -> Bool @@ -93,6 +94,12 @@ internal struct _ConcreteHashableBox : _AnyHashableBox { return _baseHashable.hashValue } + @_inlineable // FIXME(sil-serialize-all) + @_versioned // FIXME(sil-serialize-all) + func _hash(_into hasher: _UnsafeHasher) -> _UnsafeHasher { + return _baseHashable._hash(into: hasher) + } + @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) internal var _base: Any { @@ -295,6 +302,11 @@ extension AnyHashable : Hashable { public var hashValue: Int { return _box._hashValue } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return _box._hash(_into: hasher) + } } extension AnyHashable : CustomStringConvertible { diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index 6ab1c1780cb5b..449588124545d 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -159,6 +159,12 @@ extension Bool : Equatable, Hashable { return self ? 1 : 0 } + @_inlineable // FIXME(sil-serialize-all) + @_transparent + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending((self ? 1 : 0) as UInt8) + } + @_inlineable // FIXME(sil-serialize-all) @_transparent public static func == (lhs: Bool, rhs: Bool) -> Bool { diff --git a/stdlib/public/core/CTypes.swift b/stdlib/public/core/CTypes.swift index c501978234f82..de34a38f43133 100644 --- a/stdlib/public/core/CTypes.swift +++ b/stdlib/public/core/CTypes.swift @@ -162,7 +162,12 @@ extension OpaquePointer: Hashable { /// program runs. @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { - return Int(Builtin.ptrtoint_Word(_rawValue)) + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(Int(Builtin.ptrtoint_Word(_rawValue))) } } diff --git a/stdlib/public/core/DoubleWidth.swift.gyb b/stdlib/public/core/DoubleWidth.swift.gyb index b7474bbc85635..cb69767f00164 100644 --- a/stdlib/public/core/DoubleWidth.swift.gyb +++ b/stdlib/public/core/DoubleWidth.swift.gyb @@ -151,10 +151,12 @@ extension DoubleWidth : Comparable { extension DoubleWidth : Hashable { @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { - var result = 0 - result = _combineHashValues(result, _storage.high.hashValue) - result = _combineHashValues(result, _storage.low.hashValue) - return result + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(low).appending(high) } } diff --git a/stdlib/public/core/DropWhile.swift b/stdlib/public/core/DropWhile.swift index 741808d3d53dd..6814bc8f76e36 100644 --- a/stdlib/public/core/DropWhile.swift +++ b/stdlib/public/core/DropWhile.swift @@ -190,6 +190,10 @@ extension LazyDropWhileCollection.Index: Hashable where Base.Index: Hashable { public var hashValue: Int { return base.hashValue } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(base) + } } extension LazyDropWhileCollection: Collection { diff --git a/stdlib/public/core/Flatten.swift b/stdlib/public/core/Flatten.swift index ea8baded5aacd..659da19f917ee 100644 --- a/stdlib/public/core/Flatten.swift +++ b/stdlib/public/core/Flatten.swift @@ -233,7 +233,13 @@ extension FlattenCollection.Index : Comparable { extension FlattenCollection.Index : Hashable where Base.Index : Hashable, Base.Element.Index : Hashable { public var hashValue: Int { - return _combineHashValues(_inner?.hashValue ?? 0, _outer.hashValue) + return _hashValue(for: self) + } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + let h = hasher.appending(_outer) + guard let inner = _inner else { return h } + return h.appending(inner) } } diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index 646e512850005..19fe8aefa1c64 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -1508,26 +1508,26 @@ extension ${Self} : Hashable { /// your program. Do not save hash values to use during a future execution. @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + var v = self if isZero { // To satisfy the axiom that equality implies hash equality, we need to // finesse the hash value of -0.0 to match +0.0. - return 0 - } else { - %if bits <= word_bits: - return Int(bitPattern: UInt(bitPattern)) - %elif bits == 64: # Double -> 32-bit Int - return Int(truncatingIfNeeded: bitPattern &>> 32) ^ - Int(truncatingIfNeeded: bitPattern) - %elif word_bits == 32: # Float80 -> 32-bit Int - return Int(truncatingIfNeeded: significandBitPattern &>> 32) ^ - Int(truncatingIfNeeded: significandBitPattern) ^ - Int(_representation.signAndExponent) - %else: # Float80 -> 64-bit Int - return Int(bitPattern: UInt(significandBitPattern)) ^ - Int(_representation.signAndExponent) - %end + v = 0 } + %if bits == 80: + return hasher + .appending(v._representation.signAndExponent) + .appending(v.significandBitPattern) + %else: + return hasher.appending(v.bitPattern) + %end } + } extension ${Self} { diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 98fd5ffb70390..cdd0cb065968a 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -108,6 +108,19 @@ public protocol Hashable : Equatable { /// Hash values are not guaranteed to be equal across different executions of /// your program. Do not save hash values to use during a future execution. var hashValue: Int { get } + + /// Feed bits to be hashed into the hash function represented by `hasher`. + /// + /// If this requirement is not explicitly implemented, the compiler + /// automatically synthesizes an implementation for it. + func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher +} + +// Called by synthesized `hashValue` implementations. +@inline(__always) +public func _hashValue(for value: H) -> Int { + var value = value + return withUnsafePointer(to: &value) { _UnsafeHasher.hashValue(for: $0) } } // Called by the SwiftValue implementation. diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index 8045b6a43d565..5377817911baa 100644 --- a/stdlib/public/core/HashedCollections.swift.gyb +++ b/stdlib/public/core/HashedCollections.swift.gyb @@ -792,11 +792,16 @@ extension Set : Hashable { @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { // FIXME(ABI)#177: Cache Set hashValue - var result: Int = _mixInt(0) + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + var hash = 0 for member in self { - result ^= _mixInt(member.hashValue) + hash ^= _hashValue(for: member) } - return result + return hasher.appending(hash) } } @@ -3984,7 +3989,7 @@ extension _Native${Self}Buffer @_versioned @inline(__always) // For performance reasons. internal func _bucket(_ k: Key) -> Int { - return _squeezeHashValue(k.hashValue, bucketCount) + return _hashValue(for: k) & _bucketMask } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/Hashing.swift b/stdlib/public/core/Hashing.swift index 02acd8e2b5551..bac559aa20b41 100644 --- a/stdlib/public/core/Hashing.swift +++ b/stdlib/public/core/Hashing.swift @@ -25,7 +25,7 @@ import SwiftShims @_fixed_layout // FIXME(sil-serialize-all) public // @testable -struct _Hashing { +enum _Hashing { // FIXME(ABI)#41 : make this an actual public API. @_inlineable // FIXME(sil-serialize-all) public // SPI @@ -48,8 +48,9 @@ struct _Hashing { @_fixed_layout // FIXME(sil-serialize-all) public // @testable -struct _HashingDetail { +enum _HashingDetail { + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) public // @testable static var fixedSeedOverride: UInt64 { @@ -64,6 +65,7 @@ struct _HashingDetail { } } + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_versioned @_transparent @@ -74,6 +76,7 @@ struct _HashingDetail { return _HashingDetail.fixedSeedOverride == 0 ? seed : fixedSeedOverride } + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_versioned @_transparent @@ -98,6 +101,7 @@ struct _HashingDetail { // their inputs and just exhibit avalanche effect. // +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -112,6 +116,7 @@ func _mixUInt32(_ value: UInt32) -> UInt32 { return UInt32((extendedResult >> 3) & 0xffff_ffff) } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -119,6 +124,7 @@ func _mixInt32(_ value: Int32) -> Int32 { return Int32(bitPattern: _mixUInt32(UInt32(bitPattern: value))) } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -130,6 +136,7 @@ func _mixUInt64(_ value: UInt64) -> UInt64 { return _HashingDetail.hash16Bytes(seed &+ (low << 3), high) } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -137,6 +144,7 @@ func _mixInt64(_ value: Int64) -> Int64 { return Int64(bitPattern: _mixUInt64(UInt64(bitPattern: value))) } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -148,6 +156,7 @@ func _mixUInt(_ value: UInt) -> UInt { #endif } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -159,35 +168,6 @@ func _mixInt(_ value: Int) -> Int { #endif } -/// Given a hash value, returns an integer value in the range of -/// 0..<`upperBound` that corresponds to a hash value. -/// -/// The `upperBound` must be positive and a power of 2. -/// -/// This function is superior to computing the remainder of `hashValue` by -/// the range length. Some types have bad hash functions; sometimes simple -/// patterns in data sets create patterns in hash values and applying the -/// remainder operation just throws away even more information and invites -/// even more hash collisions. This effect is especially bad because the -/// range is a power of two, which means to throws away high bits of the hash -/// (which would not be a problem if the hash was known to be good). This -/// function mixes the bits in the hash value to compensate for such cases. -/// -/// Of course, this function is a compressing function, and applying it to a -/// hash value does not change anything fundamentally: collisions are still -/// possible, and it does not prevent malicious users from constructing data -/// sets that will exhibit pathological collisions. -@_inlineable // FIXME(sil-serialize-all) -public // @testable -func _squeezeHashValue(_ hashValue: Int, _ upperBound: Int) -> Int { - _sanityCheck(_isPowerOf2(upperBound)) - let mixedHashValue = _mixInt(hashValue) - - // As `upperBound` is a power of two we can do a bitwise-and to calculate - // mixedHashValue % upperBound. - return mixedHashValue & (upperBound &- 1) -} - /// Returns a new value that combines the two given hash values. /// /// Combining is performed using [a hash function][ref] described by T.C. Hoad @@ -214,3 +194,121 @@ func _combineHashValues(_ firstValue: Int, _ secondValue: Int) -> Int { x ^= UInt(bitPattern: secondValue) &+ magic &+ (x &<< 6) &+ (x &>> 2) return Int(bitPattern: x) } + +/// An unsafe wrapper around a stateful hash function, presenting a faux purely +/// functional interface to eliminate ARC overhead. +/// +/// This is not a true value type; calling `appending` or `finalized` actually +/// mutates `self`'s state. +@_fixed_layout +public struct _UnsafeHasher { + @_versioned + internal let _rawState: UnsafeMutableRawPointer + + internal var _state: UnsafeMutablePointer<_Hasher> { + @inline(__always) + get { return _rawState.assumingMemoryBound(to: _Hasher.self) } + } + + @inline(__always) + @_versioned + internal init(_ state: UnsafeMutablePointer<_Hasher>) { + self._rawState = UnsafeMutableRawPointer(state) + } + + @_versioned + // not @_inlineable + @effects(readonly) // FIXME(hasher): Unjustified + static func hashValue(for pointer: UnsafePointer) -> Int { + var hasher = _Hasher() + return withUnsafeMutablePointer(to: &hasher) { p in + return pointer.pointee._hash(into: _UnsafeHasher(p))._finalized() + } + } + + @_versioned + // not @_inlineable + @effects(readonly) + internal func appending(bits value: UInt) -> _UnsafeHasher { + // The effects attribute is a lie; however, it enables the compiler to + // eliminate unnecessary retain/releases protecting Hashable state around + // calls to `_Hasher.append(_:)`. + // + // We don't have a way to describe the side-effects of an opaque function -- + // if it doesn't have an @effects attribute, the compiler has no choice but + // to assume it may mutate the hashable we're visiting. We know that won't + // be the case (the stdlib owns the hash function), but the only way to tell + // this to the compiler is to pretend the state update is pure. + _state.pointee.append(value) + return self + } + + @_versioned + // not @_inlineable + @effects(readonly) // See comment in appending(_: UInt) + internal func appending(bits value: UInt32) -> _UnsafeHasher { + _state.pointee.append(value) + return self + } + + @_versioned + // not @_inlineable + @effects(readonly) // See comment in appending(_: UInt) + internal func appending(bits value: UInt64) -> _UnsafeHasher { + _state.pointee.append(value) + return self + } + + @_inlineable + @inline(__always) + public func appending(_ value: H) -> _UnsafeHasher { + return value._hash(into: self) + } + + @inline(__always) + internal func _finalized() -> Int { + return Int(_truncatingBits: _state.pointee.finalize()._lowWord) + } +} + +// FIXME(hasher): This is purely for benchmarking; to be removed. +internal struct _LegacyHasher { + internal var _hash: Int + + @inline(__always) + internal init() { + _hash = 0 + } + + @inline(__always) + internal mutating func append(_ value: Int) { + _hash = (_hash == 0 ? value : _combineHashValues(_hash, value)) + } + + @inline(__always) + internal mutating func append(_ value: UInt) { + append(Int(bitPattern: value)) + } + + @inline(__always) + internal mutating func append(_ value: UInt32) { + append(Int(truncatingIfNeeded: value)) + } + + @inline(__always) + internal mutating func append(_ value: UInt64) { + if UInt64.bitWidth > Int.bitWidth { + append(Int(truncatingIfNeeded: value ^ (value &>> 32))) + } else { + append(Int(truncatingIfNeeded: value)) + } + } + + @inline(__always) + internal mutating func finalize() -> UInt64 { + return UInt64( + _truncatingBits: UInt(bitPattern: _mixInt(_hash))._lowWord) + } +} + +internal typealias _Hasher = _SipHash13 diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 4f69a62527292..3f42031ecc0cd 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3610,21 +3610,27 @@ extension ${Self} : Hashable { public var hashValue: Int { @inline(__always) get { -% if bits <= word_bits and signed: - // Sign extend the value. - return Int(self) -% elif bits <= word_bits and not signed: - // Sign extend the value. - return Int(${OtherSelf}(bitPattern: self)) -% elif bits == word_bits * 2: - // We have twice as many bits as we need to return. - return - Int(truncatingIfNeeded: self) ^ - Int(truncatingIfNeeded: self &>> 32) -% else: - _Unimplemented() -% end + return _hashValue(for: self) + } + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // FIXME(hasher): To correctly bridge `Set`s/`Dictionary`s containing + // `AnyHashable`-boxed integers, all integer values are currently required + // to hash exactly the same way as the corresponding (U)Int64 value. To fix + // this, we should introduce a custom AnyHashable box for integer values + // that sign-extends values to 64 bits. + % if bits <= word_bits: + return hasher.appending(bits: _lowWord) + % elif bits == 2 * word_bits: + if let word = ${"" if signed else "U"}Int(exactly: self) { + return hasher.appending(bits: word._lowWord) } + return hasher.appending(bits: UInt64(_value)) + % else: + fatalError("Unsupported integer width") + % end } } diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 70dace66da613..5fbd423beb191 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -49,21 +49,27 @@ public class AnyKeyPath: Hashable, _AppendKeyPath { @_inlineable // FIXME(sil-serialize-all) final public var hashValue: Int { - var hash = 0 - withBuffer { + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return withBuffer { + var hasher = hasher var buffer = $0 while true { let (component, type) = buffer.next() - hash ^= _mixInt(component.value.hashValue) + hasher = hasher.appending(component.value) if let type = type { - hash ^= _mixInt(unsafeBitCast(type, to: Int.self)) + hasher = hasher.appending(unsafeBitCast(type, to: Int.self)) } else { break } } + return hasher } - return hash } + @_inlineable // FIXME(sil-serialize-all) public static func ==(a: AnyKeyPath, b: AnyKeyPath) -> Bool { // Fast-path identical objects @@ -452,11 +458,15 @@ internal struct ComputedPropertyID: Hashable { @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) internal var hashValue: Int { - var hash = 0 - hash ^= _mixInt(value) - hash ^= _mixInt(isStoredProperty ? 13 : 17) - hash ^= _mixInt(isTableOffset ? 19 : 23) - return hash + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher + .appending(value) + .appending(isStoredProperty) + .appending(isTableOffset) } } @@ -473,6 +483,7 @@ internal struct ComputedArgumentWitnesses { (_ xInstanceArguments: UnsafeRawPointer, _ yInstanceArguments: UnsafeRawPointer, _ size: Int) -> Bool + // FIXME(hashing) Pass in, append to and return _UnsafeHasher instead internal typealias Hash = @convention(thin) (_ instanceArguments: UnsafeRawPointer, _ size: Int) -> Int @@ -584,46 +595,50 @@ internal enum KeyPathComponent: Hashable { @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) internal var hashValue: Int { - var hash: Int = 0 - func mixHashFromArgument(_ argument: KeyPathComponent.ArgumentRef?) { + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + @_versioned // FIXME(sil-serialize-all) + internal func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + var hasher = hasher + func appendHashFromArgument( + _ argument: KeyPathComponent.ArgumentRef? + ) { if let argument = argument { - let addedHash = argument.witnesses.pointee.hash( + let hash = argument.witnesses.pointee.hash( argument.data.baseAddress.unsafelyUnwrapped, argument.data.count) // Returning 0 indicates that the arguments should not impact the // hash value of the overall key path. - if addedHash != 0 { - hash ^= _mixInt(addedHash) + // FIXME(hasher): hash witness should just return hasher directly + if hash != 0 { + hasher = hasher.appending(hash) } } } switch self { case .struct(offset: let a): - hash ^= _mixInt(0) - hash ^= _mixInt(a) + hasher = hasher.appending(0).appending(a) case .class(offset: let b): - hash ^= _mixInt(1) - hash ^= _mixInt(b) + hasher = hasher.appending(1).appending(b) case .optionalChain: - hash ^= _mixInt(2) + hasher = hasher.appending(2) case .optionalForce: - hash ^= _mixInt(3) + hasher = hasher.appending(3) case .optionalWrap: - hash ^= _mixInt(4) + hasher = hasher.appending(4) case .get(id: let id, get: _, argument: let argument): - hash ^= _mixInt(5) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher = hasher.appending(5).appending(id) + appendHashFromArgument(argument) case .mutatingGetSet(id: let id, get: _, set: _, argument: let argument): - hash ^= _mixInt(6) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher = hasher.appending(6).appending(id) + appendHashFromArgument(argument) case .nonmutatingGetSet(id: let id, get: _, set: _, argument: let argument): - hash ^= _mixInt(7) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher = hasher.appending(7).appending(id) + appendHashFromArgument(argument) } - return hash + return hasher } } diff --git a/stdlib/public/core/PrefixWhile.swift b/stdlib/public/core/PrefixWhile.swift index 92a627db5f96b..d801fb37cd6e2 100644 --- a/stdlib/public/core/PrefixWhile.swift +++ b/stdlib/public/core/PrefixWhile.swift @@ -208,11 +208,15 @@ extension LazyPrefixWhileCollection.Index: Comparable { extension LazyPrefixWhileCollection.Index: Hashable where Base.Index: Hashable { public var hashValue: Int { + return _hashValue(for: self) + } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { switch _value { case .index(let value): - return value.hashValue + return hasher.appending(value) case .pastEnd: - return .max + return hasher.appending(Int.max) } } } diff --git a/stdlib/public/core/Reverse.swift b/stdlib/public/core/Reverse.swift index ad08943de8241..97d16ed79be75 100644 --- a/stdlib/public/core/Reverse.swift +++ b/stdlib/public/core/Reverse.swift @@ -197,6 +197,10 @@ extension ReversedCollection.Index: Hashable where Base.Index: Hashable { public var hashValue: Int { return base.hashValue } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(base) + } } extension ReversedCollection: BidirectionalCollection { diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index 558cca3bef75b..ff97ef3658f63 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -19,63 +19,20 @@ /// * Daniel J. Bernstein //===----------------------------------------------------------------------===// +%{ +# Number of bits in the Builtin.Word type +word_bits = int(CMAKE_SIZEOF_VOID_P) * 8 +}% + @_fixed_layout // FIXME(sil-serialize-all) @_versioned internal enum _SipHashDetail { - @_inlineable // FIXME(sil-serialize-all) - @_versioned - @inline(__always) - internal static func _rotate(_ x: UInt64, leftBy amount: Int) -> UInt64 { - return (x &<< UInt64(amount)) | (x &>> UInt64(64 - amount)) - } - - @_inlineable // FIXME(sil-serialize-all) - @_versioned - @inline(__always) - internal static func _loadUnalignedUInt64LE( - from p: UnsafeRawPointer - ) -> UInt64 { - // FIXME(integers): split into multiple expressions to speed up the - // typechecking - var result = - UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) - result |= - (UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) &<< (8 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) &<< (16 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) &<< (24 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) &<< (32 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) &<< (40 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) &<< (48 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 7, as: UInt8.self)) &<< (56 as UInt64)) - return result - } - - @_inlineable // FIXME(sil-serialize-all) @_versioned @inline(__always) - internal static func _loadPartialUnalignedUInt64LE( - from p: UnsafeRawPointer, - byteCount: Int - ) -> UInt64 { - _sanityCheck((0..<8).contains(byteCount)) - var result: UInt64 = 0 - if byteCount >= 1 { result |= UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) } - if byteCount >= 2 { result |= UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) &<< (8 as UInt64) } - if byteCount >= 3 { result |= UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) &<< (16 as UInt64) } - if byteCount >= 4 { result |= UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) &<< (24 as UInt64) } - if byteCount >= 5 { result |= UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) &<< (32 as UInt64) } - if byteCount >= 6 { result |= UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) &<< (40 as UInt64) } - if byteCount >= 7 { result |= UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) &<< (48 as UInt64) } - return result + internal static func _rotate(_ x: UInt64, leftBy amount: UInt64) -> UInt64 { + return (x &<< amount) | (x &>> (64 - amount)) } - @_inlineable // FIXME(sil-serialize-all) @_versioned @inline(__always) internal static func _sipRound( @@ -102,7 +59,7 @@ internal enum _SipHashDetail { } % for (c_rounds, d_rounds) in [(2, 4), (1, 3)]: -% Self = '_SipHash{}{}Context'.format(c_rounds, d_rounds) +% Self = '_SipHash{}{}'.format(c_rounds, d_rounds) @_fixed_layout // FIXME(sil-serialize-all) public // @testable @@ -120,19 +77,14 @@ struct ${Self} { @_versioned internal var v3: UInt64 = 0x7465646279746573 + /// This value holds the byte count and the pending bytes that haven't been + /// compressed yet, in the format that the finalization step needs. (The least + /// significant 56 bits hold the trailing bytes, while the most significant 8 + /// bits hold the count of bytes appended so far, mod 256.) @_versioned - internal var hashedByteCount: UInt64 = 0 - - @_versioned - internal var dataTail: UInt64 = 0 - - @_versioned - internal var dataTailByteCount: Int = 0 + internal var tailAndByteCount: UInt64 = 0 - @_versioned - internal var finalizedHash: UInt64? - - @_inlineable // FIXME(sil-serialize-all) + @inline(__always) public init(key: (UInt64, UInt64)) { v3 ^= key.1 v2 ^= key.0 @@ -140,161 +92,115 @@ struct ${Self} { v0 ^= key.0 } - // FIXME(ABI)#62 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) - public // @testable - mutating func append(_ data: UnsafeRawPointer, byteCount: Int) { - _append_alwaysInline(data, byteCount: byteCount) + @inline(__always) + public init() { + self.init(key: _Hashing.secretKey) } - // FIXME(ABI)#63 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) @_versioned - @inline(__always) - internal mutating func _append_alwaysInline( - _ data: UnsafeRawPointer, - byteCount: Int - ) { - precondition(finalizedHash == nil) - _sanityCheck((0..<8).contains(dataTailByteCount)) - - let dataEnd = data + byteCount - - var data = data - var byteCount = byteCount - if dataTailByteCount != 0 { - let restByteCount = min( - MemoryLayout.size - dataTailByteCount, - byteCount) - let rest = _SipHashDetail._loadPartialUnalignedUInt64LE( - from: data, - byteCount: restByteCount) - dataTail |= rest &<< UInt64(dataTailByteCount * 8) - dataTailByteCount += restByteCount - data += restByteCount - byteCount -= restByteCount - } - - if dataTailByteCount == MemoryLayout.size { - _appendDirectly(dataTail) - dataTail = 0 - dataTailByteCount = 0 - } else if dataTailByteCount != 0 { - _sanityCheck(data == dataEnd) - return - } - - let endOfWords = - data + byteCount - (byteCount % MemoryLayout.size) - while data != endOfWords { - _appendDirectly(_SipHashDetail._loadUnalignedUInt64LE(from: data)) - data += 8 - // No need to update `byteCount`, it is not used beyond this point. + internal var byteCount: UInt64 { + @inline(__always) + get { + return tailAndByteCount &>> 56 } + } - if data != dataEnd { - dataTailByteCount = dataEnd - data - dataTail = _SipHashDetail._loadPartialUnalignedUInt64LE( - from: data, - byteCount: dataTailByteCount) + @_versioned + internal var tail: UInt64 { + @inline(__always) + get { + return tailAndByteCount & ~(0xFF &<< 56) } } - /// This function mixes in the given word directly into the state, - /// ignoring `dataTail`. - @_inlineable // FIXME(sil-serialize-all) - @_versioned @inline(__always) - internal mutating func _appendDirectly(_ m: UInt64) { + @_versioned + internal mutating func _compress(_ m: UInt64) { v3 ^= m for _ in 0..<${c_rounds} { _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) } v0 ^= m - hashedByteCount += 8 } -% for data_type in ['UInt', 'Int', 'UInt64', 'Int64', 'UInt32', 'Int32']: - @_inlineable // FIXME(sil-serialize-all) - public // @testable - mutating func append(_ data: ${data_type}) { - var data = data - _append_alwaysInline(&data, byteCount: MemoryLayout.size(ofValue: data)) + @inline(__always) + public mutating func append(_ value: Int) { + append(UInt(bitPattern: value)) } -% end - @_inlineable // FIXME(sil-serialize-all) - public // @testable - mutating func finalizeAndReturnHash() -> UInt64 { - return _finalizeAndReturnHash_alwaysInline() + @inline(__always) + public mutating func append(_ value: UInt) { + % if word_bits == 64: + append(UInt64(_truncatingBits: value._lowWord)) + % elif word_bits == 32: + append(UInt32(_truncatingBits: value._lowWord)) + % else: + fatalError("Unsupported word width") + % end } - @_inlineable // FIXME(sil-serialize-all) - @_versioned @inline(__always) - internal mutating func _finalizeAndReturnHash_alwaysInline() -> UInt64 { - if let finalizedHash = finalizedHash { - return finalizedHash - } - - _sanityCheck((0..<8).contains(dataTailByteCount)) - - hashedByteCount += UInt64(dataTailByteCount) - let b: UInt64 = (hashedByteCount << 56) | dataTail - - v3 ^= b - for _ in 0..<${c_rounds} { - _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) - } - v0 ^= b - - v2 ^= 0xff + public mutating func append(_ value: Int32) { + append(UInt32(bitPattern: value)) + } - for _ in 0..<${d_rounds} { - _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) + @inline(__always) + public mutating func append(_ value: UInt32) { + let m = UInt64(_truncatingBits: value._lowWord) + if byteCount & 4 == 0 { + _sanityCheck(byteCount & 7 == 0 && tail == 0) + tailAndByteCount = (tailAndByteCount | m) &+ (4 &<< 56) + } else { + _sanityCheck(byteCount & 3 == 0) + _compress((m &<< 32) | tail) + tailAndByteCount = (byteCount &+ 4) &<< 56 } + } - finalizedHash = v0 ^ v1 ^ v2 ^ v3 - return finalizedHash! + @inline(__always) + public mutating func append(_ value: Int64) { + append(UInt64(bitPattern: value)) } - @_inlineable // FIXME(sil-serialize-all) - @_versioned // FIXME(sil-serialize-all) - internal mutating func _finalizeAndReturnIntHash() -> Int { - let hash: UInt64 = finalizeAndReturnHash() -#if arch(i386) || arch(arm) - return Int(truncatingIfNeeded: hash) -#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) - return Int(Int64(bitPattern: hash)) -#endif + @inline(__always) + public mutating func append(_ m: UInt64) { + if byteCount & 4 == 0 { + _sanityCheck(byteCount & 7 == 0 && tail == 0) + _compress(m) + tailAndByteCount = tailAndByteCount &+ (8 &<< 56) + } else { + _sanityCheck(byteCount & 3 == 0) + _compress((m &<< 32) | tail) + tailAndByteCount = ((byteCount &+ 8) &<< 56) | (m &>> 32) + } } - // FIXME(ABI)#64 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) - public // @testable - static func hash( - data: UnsafeRawPointer, - dataByteCount: Int, - key: (UInt64, UInt64) + @inline(__always) + public mutating func finalize( + tailBytes: UInt64, + tailByteCount: Int ) -> UInt64 { - return ${Self}._hash_alwaysInline( - data: data, - dataByteCount: dataByteCount, - key: key) + _sanityCheck(tailByteCount >= 0) + _sanityCheck(tailByteCount < 8 - (byteCount & 7)) + _sanityCheck(tailBytes >> (tailByteCount << 3) == 0) + let count = UInt64(_truncatingBits: tailByteCount._lowWord) + let currentByteCount = byteCount & 7 + tailAndByteCount |= (tailBytes &<< (currentByteCount &<< 3)) + tailAndByteCount = tailAndByteCount &+ (count &<< 56) + return finalize() } - // FIXME(ABI)#65 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) @inline(__always) - public // @testable - static func _hash_alwaysInline( - data: UnsafeRawPointer, - dataByteCount: Int, - key: (UInt64, UInt64) - ) -> UInt64 { - var context = ${Self}(key: key) - context._append_alwaysInline(data, byteCount: dataByteCount) - return context._finalizeAndReturnHash_alwaysInline() + public mutating func finalize() -> UInt64 { + _compress(tailAndByteCount) + + v2 ^= 0xff + + for _ in 0..<${d_rounds} { + _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) + } + + return (v0 ^ v1 ^ v2 ^ v3) } } % end diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 7607c9bba783a..d7dc7fe85e5b1 100644 --- a/stdlib/public/core/StringHashable.swift +++ b/stdlib/public/core/StringHashable.swift @@ -37,34 +37,33 @@ extension Unicode { // @_inlineable // FIXME(sil-serialize-all) // @_versioned // FIXME(sil-serialize-all) internal static func hashASCII( - _ string: UnsafeBufferPointer - ) -> Int { + _ string: UnsafeBufferPointer, + into hasher: inout _Hasher + ) { let collationTable = _swift_stdlib_unicode_getASCIICollationTable() - var hasher = _SipHash13Context(key: _Hashing.secretKey) for c in string { _precondition(c <= 127) let element = collationTable[Int(c)] // Ignore zero valued collation elements. They don't participate in the // ordering relation. if element != 0 { - hasher.append(element) + hasher.append(Int(truncatingIfNeeded: element)) } } - return hasher._finalizeAndReturnIntHash() } // FIXME: cannot be marked @_versioned. See // @_inlineable // FIXME(sil-serialize-all) // @_versioned // FIXME(sil-serialize-all) internal static func hashUTF16( - _ string: UnsafeBufferPointer - ) -> Int { + _ string: UnsafeBufferPointer, + into hasher: inout _Hasher + ) { let collationIterator = _swift_stdlib_unicodeCollationIterator_create( string.baseAddress!, UInt32(string.count)) defer { _swift_stdlib_unicodeCollationIterator_delete(collationIterator) } - var hasher = _SipHash13Context(key: _Hashing.secretKey) while true { var hitEnd = false let element = @@ -75,10 +74,9 @@ extension Unicode { // Ignore zero valued collation elements. They don't participate in the // ordering relation. if element != 0 { - hasher.append(element) + hasher.append(Int(truncatingIfNeeded: element)) } } - return hasher._finalizeAndReturnIntHash() } } @@ -100,7 +98,9 @@ extension _UnmanagedString where CodeUnit == UInt8 { // Swift.String.hashValue and NSString.hash being the same. return stringHashOffset ^ hash #else - return Unicode.hashASCII(self.buffer) + var hasher = _Hasher() + Unicode.hashASCII(self.buffer, into: &hasher) + return Int(truncatingIfNeeded: hasher.finalize()) #endif // _runtime(_ObjC) } } @@ -118,7 +118,9 @@ extension _UnmanagedString where CodeUnit == UTF16.CodeUnit { // Swift.String.hashValue and NSString.hash being the same. return stringHashOffset ^ hash #else - return Unicode.hashUTF16(self.buffer) + var hasher = _Hasher() + Unicode.hashUTF16(self.buffer, into: &hasher) + return Int(truncatingIfNeeded: hasher.finalize()) #endif // _runtime(_ObjC) } } @@ -139,7 +141,11 @@ extension _UnmanagedOpaqueString { defer { p.deallocate(capacity: count) } let buffer = UnsafeMutableBufferPointer(start: p, count: count) _copy(into: buffer) - return Unicode.hashUTF16(UnsafeBufferPointer(buffer)) + var hasher = _Hasher() + Unicode.hashUTF16( + UnsafeBufferPointer(start: p, count: count), + into: &hasher) + return Int(truncatingIfNeeded: hasher.finalize()) #endif } } diff --git a/stdlib/public/core/UnsafePointer.swift.gyb b/stdlib/public/core/UnsafePointer.swift.gyb index 05ae11e627913..e13e55049742a 100644 --- a/stdlib/public/core/UnsafePointer.swift.gyb +++ b/stdlib/public/core/UnsafePointer.swift.gyb @@ -902,10 +902,15 @@ extension ${Self}: Hashable { /// program runs. @_inlineable public var hashValue: Int { - return Int(bitPattern: self) + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(Int(bitPattern: self)) } } - + extension ${Self}: Strideable { /// Returns a pointer to the next consecutive instance. /// diff --git a/test/IDE/complete_enum_elements.swift b/test/IDE/complete_enum_elements.swift index 21b573010f855..486cefb176f96 100644 --- a/test/IDE/complete_enum_elements.swift +++ b/test/IDE/complete_enum_elements.swift @@ -86,11 +86,13 @@ enum FooEnum { // FOO_ENUM_NO_DOT: Begin completions // FOO_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Foo1[#FooEnum#]{{; name=.+$}} // FOO_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Foo2[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_NO_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: ._hash({#self: FooEnum#})[#(into: _UnsafeHasher) -> _UnsafeHasher#]; name=_hash(FooEnum) // FOO_ENUM_NO_DOT-NEXT: End completions // FOO_ENUM_DOT: Begin completions // FOO_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Foo1[#FooEnum#]{{; name=.+$}} // FOO_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Foo2[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: _hash({#self: FooEnum#})[#(into: _UnsafeHasher) -> _UnsafeHasher#]; name=_hash(FooEnum) // FOO_ENUM_DOT-NEXT: End completions // FOO_ENUM_DOT_ELEMENTS: Begin completions, 2 items @@ -231,17 +233,19 @@ enum QuxEnum : Int { // QUX_ENUM_TYPE_CONTEXT-DAG: Decl[EnumElement]/ExprSpecific: .Qux2[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_TYPE_CONTEXT: End completions -// QUX_ENUM_NO_DOT: Begin completions, 4 items +// QUX_ENUM_NO_DOT: Begin completions, 5 items // QUX_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Qux1[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Qux2[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_NO_DOT-NEXT: Decl[TypeAlias]/CurrNominal: .RawValue[#Int#]{{; name=.+$}} +// QUX_ENUM_NO_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: ._hash({#self: QuxEnum#})[#(into: _UnsafeHasher) -> _UnsafeHasher#]; name=_hash(QuxEnum) // QUX_ENUM_NO_DOT-NEXT: Decl[Constructor]/CurrNominal: ({#rawValue: Int#})[#QuxEnum?#]{{; name=.+$}} // QUX_ENUM_NO_DOT-NEXT: End completions -// QUX_ENUM_DOT: Begin completions, 4 items +// QUX_ENUM_DOT: Begin completions, 5 items // QUX_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Qux1[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Qux2[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_DOT-NEXT: Decl[TypeAlias]/CurrNominal: RawValue[#Int#]{{; name=.+$}} +// QUX_ENUM_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: _hash({#self: QuxEnum#})[#(into: _UnsafeHasher) -> _UnsafeHasher#]; name=_hash(QuxEnum) // QUX_ENUM_DOT-NEXT: Decl[Constructor]/CurrNominal: init({#rawValue: Int#})[#QuxEnum?#]{{; name=.+$}} // QUX_ENUM_DOT-NEXT: End completions diff --git a/test/IDE/print_ast_tc_decls.swift b/test/IDE/print_ast_tc_decls.swift index 11b19f17bd11b..c844726d45ceb 100644 --- a/test/IDE/print_ast_tc_decls.swift +++ b/test/IDE/print_ast_tc_decls.swift @@ -587,6 +587,7 @@ struct d0200_EscapedIdentifiers { // PASS_COMMON-NEXT: {{^}} case `case`{{$}} // PASS_COMMON-NEXT: {{^}} {{.*}}static func __derived_enum_equals(_ a: d0200_EscapedIdentifiers.`enum`, _ b: d0200_EscapedIdentifiers.`enum`) -> Bool // PASS_COMMON-NEXT: {{^}} var hashValue: Int { get }{{$}} +// PASS_COMMON-NEXT: {{^}} func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher{{$}} // PASS_COMMON-NEXT: {{^}} }{{$}} class `class` {} @@ -1015,6 +1016,7 @@ enum d2000_EnumDecl1 { // PASS_COMMON-NEXT: {{^}} case ED1_Second{{$}} // PASS_COMMON-NEXT: {{^}} {{.*}}static func __derived_enum_equals(_ a: d2000_EnumDecl1, _ b: d2000_EnumDecl1) -> Bool // PASS_COMMON-NEXT: {{^}} var hashValue: Int { get }{{$}} +// PASS_COMMON-NEXT: {{^}} func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher{{$}} // PASS_COMMON-NEXT: {{^}}}{{$}} enum d2100_EnumDecl2 { @@ -1072,6 +1074,7 @@ enum d2300_EnumDeclWithValues1 : Int { // PASS_COMMON-NEXT: {{^}} case EDV2_Second{{$}} // PASS_COMMON-NEXT: {{^}} typealias RawValue = Int // PASS_COMMON-NEXT: {{^}} var hashValue: Int { get }{{$}} +// PASS_COMMON-NEXT: {{^}} func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher{{$}} // PASS_COMMON-NEXT: {{^}} init?(rawValue: Int){{$}} // PASS_COMMON-NEXT: {{^}} var rawValue: Int { get }{{$}} // PASS_COMMON-NEXT: {{^}}}{{$}} @@ -1085,6 +1088,7 @@ enum d2400_EnumDeclWithValues2 : Double { // PASS_COMMON-NEXT: {{^}} case EDV3_Second{{$}} // PASS_COMMON-NEXT: {{^}} typealias RawValue = Double // PASS_COMMON-NEXT: {{^}} var hashValue: Int { get }{{$}} +// PASS_COMMON-NEXT: {{^}} func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher{{$}} // PASS_COMMON-NEXT: {{^}} init?(rawValue: Double){{$}} // PASS_COMMON-NEXT: {{^}} var rawValue: Double { get }{{$}} // PASS_COMMON-NEXT: {{^}}}{{$}} diff --git a/test/IRGen/enum_derived.swift b/test/IRGen/enum_derived.swift index c52ddbd54ac6d..e50a39434f41c 100644 --- a/test/IRGen/enum_derived.swift +++ b/test/IRGen/enum_derived.swift @@ -5,8 +5,8 @@ import def_enum -// Check if the hashValue and == for an enum (without payload) are generated and -// check if that functions are compiled in an optimal way. +// Check if hashValue, _hash(into:) and == for an enum (without payload) are +// generated and check that functions are compiled in an optimal way. enum E { case E0 @@ -22,11 +22,19 @@ enum E { // CHECK: %2 = icmp eq i8 %0, %1 // CHECK: ret i1 %2 -// Check if the hashValue getter can be compiled to a simple zext instruction. +// Check for the presence of the hashValue getter. // CHECK-NORMAL-LABEL:define hidden swiftcc i{{.*}} @"$S12enum_derived1EO9hashValueSivg"(i8) // CHECK-TESTABLE-LABEL:define{{( protected)?}} swiftcc i{{.*}} @"$S12enum_derived1EO9hashValueSivg"(i8) -// CHECK: [[R:%.*]] = zext i8 %0 to i{{.*}} +// CHECK: ret i{{.*}} + +// Check if the _hash(into:) method can be compiled to a simple zext instruction +// followed by a call to _UnsafeHasher(appending:). + +// CHECK-NORMAL-LABEL:define hidden swiftcc i{{.*}} @"$S12enum_derived1EO5_hash4intos13_UnsafeHasherVAG_tF" +// CHECK-TESTABLE-LABEL:define{{( protected)?}} swiftcc i{{.*}} @"$S12enum_derived1EO5_hash4intos13_UnsafeHasherVAG_tF" +// CHECK: [[V:%.*]] = zext i8 %1 to i{{.*}} +// CHECK: [[R:%.*]] = tail call swiftcc i{{.*}} @"$Ss13_UnsafeHasherV9appending4bitsABSu_tF"(i{{.*}} [[V]], // CHECK: ret i{{.*}} [[R]] // Derived conformances from extensions diff --git a/test/Interpreter/SDK/Foundation_bridge.swift b/test/Interpreter/SDK/Foundation_bridge.swift index 6812ed5480254..609ca6c6ce55e 100644 --- a/test/Interpreter/SDK/Foundation_bridge.swift +++ b/test/Interpreter/SDK/Foundation_bridge.swift @@ -106,9 +106,9 @@ do { } // CHECK: dictionary bridges to { -// CHECK-NEXT: 2 = World; -// CHECK-NEXT: 1 = Hello; -// CHECK-NEXT: } +// CHECK-DAG: 1 = Hello; +// CHECK-DAG: 2 = World; +// CHECK: } do { var dict: Dictionary = [1: "Hello", 2: "World"] let obj = _bridgeAnythingToObjectiveC(dict) @@ -116,9 +116,9 @@ do { } // CHECK: dictionary bridges to { -// CHECK-NEXT: 2 = World; -// CHECK-NEXT: 1 = Hello; -// CHECK-NEXT: } +// CHECK-DAG: 1 = Hello; +// CHECK-DAG: 2 = World; +// CHECK: } do { var dict2 = [1: "Hello", 2: "World"] let obj = _bridgeAnythingToObjectiveC(dict2) @@ -126,9 +126,9 @@ do { } // CHECK: dictionary bridges to { -// CHECK-NEXT: 2 = "(\"World\", 2)"; -// CHECK-NEXT: 1 = "(\"Hello\", 1)"; -// CHECK-NEXT: } +// CHECK-DAG: 1 = "(\"Hello\", 1)"; +// CHECK-DAG: 2 = "(\"World\", 2)"; +// CHECK: } do { var dict3 = [1: ("Hello", 1), 2: ("World", 2)] let obj = _bridgeAnythingToObjectiveC(dict3) @@ -141,11 +141,11 @@ var dict4 = propListStr.propertyListFromStringsFileFormat()! var hello: NSString = "Hello" var world: NSString = "World" -// Print out the keys. We only check one of these because the order is -// nondeterministic. -// CHECK: Hello +// Print out the keys. +// CHECK-DAG: Bridged key: Hello +// CHECK-DAG: Bridged key: World for key in dict4.keys { - print(key.description) + print("Bridged key: \(key.description)") } // CHECK: Hello: 1 diff --git a/test/Interpreter/repl.swift b/test/Interpreter/repl.swift index 77654f0132b6a..e68e704568d2c 100644 --- a/test/Interpreter/repl.swift +++ b/test/Interpreter/repl.swift @@ -41,7 +41,7 @@ String(4.0) // CHECK: String = "4.0" 123 . hashValue -// CHECK: Int = 123{{$}} +// CHECK: Int = {{[0-9]+$}} // Check that we handle unmatched parentheses in REPL. 1+1) diff --git a/test/SILGen/objc_bridging_any.swift b/test/SILGen/objc_bridging_any.swift index aa23de2da4002..142fd76a5f6ab 100644 --- a/test/SILGen/objc_bridging_any.swift +++ b/test/SILGen/objc_bridging_any.swift @@ -757,4 +757,5 @@ func bridgeOptionalFunctionToAnyObject(fn: (() -> ())?) -> AnyObject { // CHECK-LABEL: sil_witness_table shared [serialized] GenericOption: Hashable module objc_generics { // CHECK-NEXT: base_protocol Equatable: GenericOption: Equatable module objc_generics // CHECK-NEXT: method #Hashable.hashValue!getter.1: {{.*}} : @$SSo13GenericOptionas8HashableSCsACP9hashValueSivgTW +// CHECK-NEXT: method #Hashable._hash!1: {{.*}} : @$SSo13GenericOptionas8HashableSCsACP5_hash4intos13_UnsafeHasherVAH_tFTW // CHECK-NEXT: } diff --git a/test/stdlib/ReflectionHashing.swift b/test/stdlib/ReflectionHashing.swift index abde47737c387..d25770900856a 100644 --- a/test/stdlib/ReflectionHashing.swift +++ b/test/stdlib/ReflectionHashing.swift @@ -2,14 +2,6 @@ // RUN: %target-run %t.out // REQUIRES: executable_test -// This test expects consistent hash code generation. However String instance -// generate different values on Linux depending on which version of libicu is -// installed. Therefore we run this test only on non-linux OSes, which is -// best described as having objc_interop. - -// REQUIRES: objc_interop - -// // This file contains reflection tests that depend on hash values. // Don't add other tests here. // @@ -36,46 +28,16 @@ Reflection.test("Dictionary") { var output = "" dump(dict, to: &output) -#if arch(i386) || arch(arm) - var expected = "" - expected += "▿ 5 key/value pairs\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Four\"\n" - expected += " - value: 4\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"One\"\n" - expected += " - value: 1\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Two\"\n" - expected += " - value: 2\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Five\"\n" - expected += " - value: 5\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Three\"\n" - expected += " - value: 3\n" -#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) + // The order of elements in output depends on the hash values of dict's items, + // which isn't deterministic. However, iterating over dict will get us the + // same elements in the same order. var expected = "" expected += "▿ 5 key/value pairs\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Three\"\n" - expected += " - value: 3\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Two\"\n" - expected += " - value: 2\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Four\"\n" - expected += " - value: 4\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"One\"\n" - expected += " - value: 1\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Five\"\n" - expected += " - value: 5\n" -#else - fatalError("unimplemented") -#endif - + for (key, value) in dict { + expected += " ▿ (2 elements)\n" + expected += " - key: \"\(key)\"\n" + expected += " - value: \(value)\n" + } expectEqual(expected, output) } @@ -85,25 +47,14 @@ Reflection.test("Set") { var output = "" dump(s, to: &output) -#if arch(i386) || arch(arm) - var expected = "" - expected += "▿ 5 members\n" - expected += " - 3\n" - expected += " - 1\n" - expected += " - 5\n" - expected += " - 2\n" - expected += " - 4\n" -#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) + // The order of elements in output depends on the hash values of dict's items, + // which isn't deterministic. However, iterating over dict will get us the + // same elements in the same order. var expected = "" expected += "▿ 5 members\n" - expected += " - 5\n" - expected += " - 2\n" - expected += " - 3\n" - expected += " - 1\n" - expected += " - 4\n" -#else - fatalError("unimplemented") -#endif + for i in s { + expected += " - \(i)\n" + } expectEqual(expected, output) } diff --git a/validation-test/stdlib/Dictionary.swift b/validation-test/stdlib/Dictionary.swift index b2117d6597376..2eb86884076c5 100644 --- a/validation-test/stdlib/Dictionary.swift +++ b/validation-test/stdlib/Dictionary.swift @@ -4492,7 +4492,8 @@ DictionaryTestSuite.test("dropsBridgedCache") { } DictionaryTestSuite.test("getObjects:andKeys:") { - let d = ([1: "one", 2: "two"] as Dictionary) as NSDictionary + let native = [1: "one", 2: "two"] as Dictionary + let d = native as NSDictionary var keys = UnsafeMutableBufferPointer( start: UnsafeMutablePointer.allocate(capacity: 2), count: 2) var values = UnsafeMutableBufferPointer( @@ -4501,17 +4502,27 @@ DictionaryTestSuite.test("getObjects:andKeys:") { var vp = AutoreleasingUnsafeMutablePointer(values.baseAddress!) var null: AutoreleasingUnsafeMutablePointer? + let expectedKeys: [NSNumber] + let expectedValues: [NSString] + if native.first?.key == 1 { + expectedKeys = [1, 2] + expectedValues = ["one", "two"] + } else { + expectedKeys = [2, 1] + expectedValues = ["two", "one"] + } + d.available_getObjects(null, andKeys: null) // don't segfault d.available_getObjects(null, andKeys: kp) - expectEqual([2, 1] as [NSNumber], Array(keys)) + expectEqual(expectedKeys, Array(keys)) d.available_getObjects(vp, andKeys: null) - expectEqual(["two", "one"] as [NSString], Array(values)) + expectEqual(expectedValues, Array(values)) d.available_getObjects(vp, andKeys: kp) - expectEqual([2, 1] as [NSNumber], Array(keys)) - expectEqual(["two", "one"] as [NSString], Array(values)) + expectEqual(expectedKeys, Array(keys)) + expectEqual(expectedValues, Array(values)) } #endif @@ -4530,11 +4541,14 @@ DictionaryTestSuite.test("popFirst") { 2020: 2020, 3030: 3030, ] - let expected = Array(d.map{($0.0, $0.1)}) + let expected = [(1010, 1010), (2020, 2020), (3030, 3030)] while let element = d.popFirst() { popped.append(element) } - expectEqualSequence(expected, Array(popped)) { + // Note that removing an element may reorder remaining items, so we + // can't compare ordering here. + popped.sort(by: { $0.0 < $1.0 }) + expectEqualSequence(expected, popped) { (lhs: (Int, Int), rhs: (Int, Int)) -> Bool in lhs.0 == rhs.0 && lhs.1 == rhs.1 } diff --git a/validation-test/stdlib/FixedPoint.swift.gyb b/validation-test/stdlib/FixedPoint.swift.gyb index 6cecc0c23282a..2ad63c3fb5ce5 100644 --- a/validation-test/stdlib/FixedPoint.swift.gyb +++ b/validation-test/stdlib/FixedPoint.swift.gyb @@ -51,15 +51,6 @@ def prepare_bit_pattern(bit_pattern, dst_bits, dst_signed): if dst <= ((1 << (dst_bits - 1)) - 1): return dst return dst - mask - 1 - -def get_fixed_point_hash(bit_pattern, src_bits, word_bits): - if src_bits <= word_bits: - bit_pattern = prepare_bit_pattern(bit_pattern, src_bits, True) - return prepare_bit_pattern(bit_pattern, word_bits, True) - if src_bits == word_bits * 2: - return prepare_bit_pattern( - bit_pattern ^ (bit_pattern >> 32), word_bits, True) - }% //===----------------------------------------------------------------------===// @@ -250,8 +241,15 @@ FixedPoint.test("${Self}.hashValue") { % input = prepare_bit_pattern(bit_pattern, self_ty.bits, self_ty.is_signed) let input = get${Self}(${input}) let output = getInt(input.hashValue) - let expected = getInt(${get_fixed_point_hash(input, self_ty.bits, word_bits)}) - expectEqual(expected, output) + + var hasher = _SipHash13() +% if prepare_bit_pattern(input, word_bits, self_ty.is_signed) == input: + hasher.append(${input} as ${"" if self_ty.is_signed else "U"}Int) +% else: + hasher.append(input) +% end + let expected = getInt(Int(truncatingIfNeeded: hasher.finalize())) + expectEqual(expected, output, "input: \(input)") } % end @@ -269,7 +267,6 @@ FixedPoint.test("${Self}.hashValue") { ${gyb.execute_template( hash_value_test_template, prepare_bit_pattern=prepare_bit_pattern, - get_fixed_point_hash=get_fixed_point_hash, test_bit_patterns=test_bit_patterns, word_bits=32)} @@ -278,7 +275,6 @@ FixedPoint.test("${Self}.hashValue") { ${gyb.execute_template( hash_value_test_template, prepare_bit_pattern=prepare_bit_pattern, - get_fixed_point_hash=get_fixed_point_hash, test_bit_patterns=test_bit_patterns, word_bits=64)} diff --git a/validation-test/stdlib/Hashing.swift b/validation-test/stdlib/Hashing.swift index 0018c423601c9..b8f0fa1882e83 100644 --- a/validation-test/stdlib/Hashing.swift +++ b/validation-test/stdlib/Hashing.swift @@ -70,24 +70,6 @@ HashingTestSuite.test("_mixInt/GoldenValues") { #endif } -HashingTestSuite.test("_squeezeHashValue/Int") { - // Check that the function can return values that cover the whole range. - func checkRange(_ r: Int) { - var results = Set() - for _ in 0..<(14 * r) { - let v = _squeezeHashValue(randInt(), r) - expectTrue(v < r) - results.insert(v) - } - expectEqual(r, results.count) - } - checkRange(1) - checkRange(2) - checkRange(4) - checkRange(8) - checkRange(16) -} - HashingTestSuite.test("String/hashValue/topBitsSet") { #if _runtime(_ObjC) #if arch(x86_64) || arch(arm64) diff --git a/validation-test/stdlib/Set.swift b/validation-test/stdlib/Set.swift index ff15a83abb8a5..f8a943541112a 100644 --- a/validation-test/stdlib/Set.swift +++ b/validation-test/stdlib/Set.swift @@ -3365,11 +3365,14 @@ SetTestSuite.test("popFirst") { do { var popped = [Int]() var s = Set([1010, 2020, 3030]) - let expected = Array(s) + let expected = [1010, 2020, 3030] while let element = s.popFirst() { popped.append(element) } - expectEqualSequence(expected, Array(popped)) + // Note that removing an element may reorder remaining items, so we + // can't compare ordering here. + popped.sort() + expectEqualSequence(expected, popped) expectTrue(s.isEmpty) } } diff --git a/validation-test/stdlib/SipHash.swift b/validation-test/stdlib/SipHash.swift index cbf5edb070db4..7bda072d069cf 100644 --- a/validation-test/stdlib/SipHash.swift +++ b/validation-test/stdlib/SipHash.swift @@ -243,12 +243,61 @@ func loadUnalignedUIntLE( #endif } +func loadPartialUnalignedUInt64LE( + from p: UnsafeRawPointer, + byteCount: Int +) -> UInt64 { + _sanityCheck((0..<8).contains(byteCount)) + var result: UInt64 = 0 + if byteCount >= 1 { result |= UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) } + if byteCount >= 2 { result |= UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) &<< (8 as UInt64) } + if byteCount >= 3 { result |= UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) &<< (16 as UInt64) } + if byteCount >= 4 { result |= UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) &<< (24 as UInt64) } + if byteCount >= 5 { result |= UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) &<< (32 as UInt64) } + if byteCount >= 6 { result |= UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) &<< (40 as UInt64) } + if byteCount >= 7 { result |= UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) &<< (48 as UInt64) } + return result +} + +func loadPartialUnalignedUInt32LE( + from p: UnsafeRawPointer, + byteCount: Int +) -> UInt32 { + _sanityCheck((0..<4).contains(byteCount)) + var result: UInt32 = 0 + if byteCount >= 1 { result |= UInt32(p.load(fromByteOffset: 0, as: UInt8.self)) } + if byteCount >= 2 { result |= UInt32(p.load(fromByteOffset: 1, as: UInt8.self)) &<< (8 as UInt32) } + if byteCount >= 3 { result |= UInt32(p.load(fromByteOffset: 2, as: UInt8.self)) &<< (16 as UInt32) } + return result +} + +func loadPartialUnalignedUIntLE( + from p: UnsafeRawPointer, + byteCount: Int +) -> UInt { +#if arch(i386) || arch(arm) + return UInt(loadPartialUnalignedUInt32LE(from: p, byteCount: byteCount)) +#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) + return UInt(loadPartialUnalignedUInt64LE(from: p, byteCount: byteCount)) +#endif +} + % for data_type in ['Int', 'Int64', 'Int32']: func loadUnaligned${data_type}LE( from p: UnsafeRawPointer ) -> ${data_type} { return ${data_type}(bitPattern: loadUnalignedU${data_type}LE(from: p)) } + +func loadPartialUnaligned${data_type}LE( + from p: UnsafeRawPointer, + byteCount: Int +) -> ${data_type} { + return ${data_type}( + bitPattern: loadPartialUnalignedU${data_type}LE( + from: p, + byteCount: byteCount)) +} % end % for data_type in ['UInt', 'Int', 'UInt64', 'Int64', 'UInt32', 'Int32']: @@ -257,85 +306,45 @@ func loadUnaligned${data_type}( ) -> ${data_type} { return ${data_type}(littleEndian: loadUnaligned${data_type}LE(from: p)) } +func loadPartialUnaligned${data_type}( + from p: UnsafeRawPointer, + byteCount: Int +) -> ${data_type} { + return ${data_type}(littleEndian: + loadPartialUnaligned${data_type}LE(from: p, byteCount: byteCount)) +} % end % for (Self, tests) in [ -% ('_SipHash13Context', 'sipHash13Tests'), -% ('_SipHash24Context', 'sipHash24Tests') +% ('_SipHash13', 'sipHash13Tests'), +% ('_SipHash24', 'sipHash24Tests') % ]: -SipHashTests.test("${Self}/Oneshot").forEach(in: ${tests}) { - test in - - expectEqual( - test.output, - ${Self}.hash( - data: test.input, - dataByteCount: test.input.count, - key: test.key)) -} - -SipHashTests.test("${Self}.append(UnsafeRawPointer)") - .forEach(in: cartesianProduct(${tests}, incrementalPatterns)) { - test_ in - let (test, pattern) = test_ - - var context = ${Self}(key: test.key) - var startIndex = 0 - var chunkSizeIndex = 0 - while startIndex != test.input.endIndex { - let chunkSize = min( - pattern[chunkSizeIndex], - test.input.endIndex - startIndex) - context.append( - Array(test.input[startIndex..<(startIndex+chunkSize)]), - byteCount: chunkSize) - startIndex += chunkSize - chunkSizeIndex += 1 - chunkSizeIndex %= pattern.count - } - expectEqual( - test.output, - context.finalizeAndReturnHash()) - - // Check that we can query the hash value more than once. - expectEqual( - test.output, - context.finalizeAndReturnHash()) -} - % for data_type in ['UInt', 'Int', 'UInt64', 'Int64', 'UInt32', 'Int32']: SipHashTests.test("${Self}.append(${data_type})").forEach(in: ${tests}) { test in - var context = ${Self}(key: test.key) + var hasher = ${Self}(key: test.key) let chunkSize = MemoryLayout<${data_type}>.size var startIndex = 0 let endIndex = test.input.count - (test.input.count % chunkSize) while startIndex != endIndex { - context.append( + hasher.append( loadUnaligned${data_type}( from: Array( test.input[startIndex..<(startIndex+chunkSize)]))) startIndex += chunkSize } - context.append( - Array(test.input.suffix(from: endIndex)), - byteCount: test.input.count - endIndex) - - expectEqual( - test.output, - context.finalizeAndReturnHash()) + let tailCount = test.input.count - endIndex + let hash = hasher.finalize( + tailBytes: loadPartialUnalignedUInt64( + from: Array(test.input.suffix(from: endIndex)), + byteCount: tailCount), + tailByteCount: tailCount) + expectEqual(test.output, hash) } % end - -SipHashTests.test("${Self}/AppendAfterFinalizing") { - var context = ${Self}(key: (0, 0)) - _ = context.finalizeAndReturnHash() - expectCrashLater() - context.append([], byteCount: 0) -} % end runAllTests()