[stdlib]Unify String hashing implementation (#14921)

* Add partial range subscripts to _UnmanagedOpaqueString

* Use SipHash13+_NormalizedCodeUnitIterator for String hashes on all platforms

* Remove unecessary collation algorithm shims

* Pass the buffer to the SipHasher for ASCII

* Hash the ascii parts of UTF16 strings the same way we hash pure ascii strings

* De-dupe some code that can be shared between _UnmanagedOpaqueString and _UnmanagedString<UInt16>

* ASCII strings now hash consistently for in hashASCII() and hashUTF16()

* Fix zalgo comparison regression

* Use hasher

* Fix crash when appending to an empty _FixedArray

* Compact ASCII characters into a single UInt64 for hashing

* String: Switch to _hash(into:)-based hashing

This should speed up String hashing quite a bit, as doing it through hashValue involves two rounds of SipHash nested in each other.

* Remove obsolete workaround for ARC traffic

* Ditch _FixedArray<UInt8> in favor of _UIntBuffer<UInt64, UInt8>

* Bad rebase remnants

* Fix failing benchmarks

* michael's feedback

* clarify the comment about nul-terminated string hashes
This commit is contained in:
Lance Parker
2018-03-17 22:13:37 -07:00
committed by GitHub
parent f5d43e2df9
commit cbf157f924
9 changed files with 228 additions and 401 deletions

View File

@@ -62,22 +62,6 @@ SWIFT_RUNTIME_STDLIB_INTERFACE
const __swift_uint16_t *
_swift_stdlib_ExtendedGraphemeClusterNoBoundaryRulesMatrix;
SWIFT_RUNTIME_STDLIB_INTERFACE
void *_swift_stdlib_unicodeCollationIterator_create(
const __swift_uint16_t *Str,
__swift_uint32_t Length);
SWIFT_RUNTIME_STDLIB_INTERFACE
__swift_int32_t _swift_stdlib_unicodeCollationIterator_next(
void *CollationIterator, __swift_bool *HitEnd);
SWIFT_RUNTIME_STDLIB_INTERFACE
void _swift_stdlib_unicodeCollationIterator_delete(
void *CollationIterator);
SWIFT_RUNTIME_STDLIB_INTERFACE
const __swift_int32_t *_swift_stdlib_unicode_getASCIICollationTable();
SWIFT_RUNTIME_STDLIB_INTERFACE
__swift_int32_t _swift_stdlib_unicode_strToUpper(
__swift_uint16_t *Destination, __swift_int32_t DestinationCapacity,

View File

@@ -127,8 +127,8 @@ extension _FixedArray${N} {
@_versioned
internal mutating func append(_ newElement: T) {
_sanityCheck(count < capacity)
self[count] = newElement
_count += 1
self[count-1] = newElement
}
}

View File

@@ -23,12 +23,11 @@ struct _NormalizedCodeUnitIterator: IteratorProtocol {
typealias CodeUnit = UInt16
init(_ opaqueString: _UnmanagedOpaqueString, startIndex: Int = 0) {
source = _UnmanagedOpaqueStringSource(opaqueString, start: startIndex)
}
init(_ unmanagedString: _UnmanagedString<UInt16>, startIndex: Int = 0) {
source = _UnmanagedStringSource(unmanagedString, start: startIndex)
init<Source: BidirectionalCollection>
(_ collection: Source)
where Source.Element == UInt16, Source.SubSequence == Source
{
source = _CollectionSource(collection)
}
init(_ guts: _StringGuts, _ range: Range<Int>, startIndex: Int = 0) {
@@ -60,23 +59,27 @@ struct _NormalizedCodeUnitIterator: IteratorProtocol {
}
}
struct _UnmanagedOpaqueStringSource: _SegmentSource {
struct _CollectionSource<Source: BidirectionalCollection>: _SegmentSource
where Source.Element == UInt16, Source.SubSequence == Source
{
var remaining: Int {
return opaqueString.count - index
return collection.distance(from: index, to: collection.endIndex)
}
var opaqueString: _UnmanagedOpaqueString
var index: Int
var collection: Source
var index: Source.Index
init(_ opaqueString: _UnmanagedOpaqueString, start: Int = 0) {
self.opaqueString = opaqueString
index = start
init(_ collection: Source) {
self.collection = collection
index = collection.startIndex
}
@_specialize(where Source == _UnmanagedString<UInt16>)
@_specialize(where Source == _UnmanagedOpaqueString)
mutating func tryFill(buffer: UnsafeMutableBufferPointer<UInt16>) -> Int? {
var bufferIndex = 0
let originalIndex = index
repeat {
guard index < opaqueString.count else {
guard index != collection.endIndex else {
break
}
@@ -86,49 +89,11 @@ struct _NormalizedCodeUnitIterator: IteratorProtocol {
return nil
}
let cu = opaqueString[index]
let cu = collection[index]
buffer[bufferIndex] = cu
index += 1
index = collection.index(after: index)
bufferIndex += 1
} while !opaqueString.hasNormalizationBoundary(after: index - 1)
return bufferIndex
}
}
struct _UnmanagedStringSource: _SegmentSource {
var remaining: Int {
return unmanagedString.count - index
}
var unmanagedString: _UnmanagedString<UInt16>
var index: Int
init(_ unmanagedString: _UnmanagedString<UInt16>, start: Int = 0) {
self.unmanagedString = unmanagedString
index = start
}
mutating func tryFill(buffer: UnsafeMutableBufferPointer<UInt16>) -> Int? {
var bufferIndex = 0
let originalIndex = index
repeat {
guard index < unmanagedString.count else {
break
}
guard bufferIndex < buffer.count else {
//The buffer isn't big enough for the current segment
index = originalIndex
return nil
}
let cu = unmanagedString[index]
buffer[bufferIndex] = cu
index += 1
bufferIndex += 1
} while unmanagedString.hasNormalizationBoundary(
after: index - 1) == false
} while !collection.hasNormalizationBoundary(after: collection.index(before: index))
return bufferIndex
}

View File

@@ -641,7 +641,7 @@ extension _UnmanagedOpaqueString {
) -> _Ordering {
// Compare by pulling in a segment at a time, normalizing then comparing
// individual code units
var selfIterator = _NormalizedCodeUnitIterator(self, startIndex: startingFrom)
var selfIterator = _NormalizedCodeUnitIterator(self[startingFrom...])
return selfIterator.compare(with:
_NormalizedCodeUnitIterator(other, otherRange, startIndex: startingFrom)
)
@@ -788,8 +788,8 @@ extension _UnmanagedString where CodeUnit == UInt8 {
let otherCU = other[idx]
//
// Fast path: if other is super-ASCII post-normalization, we must be less. If
// other is ASCII and a single-scalar segment, we have our answer.
// Fast path: if other is super-ASCII post-normalization, we must be less.
// If other is ASCII and a single-scalar segment, we have our answer.
//
if otherCU > 0x7F {
if _fastPath(
@@ -819,7 +819,7 @@ extension _UnmanagedString where CodeUnit == UInt8 {
}
extension _StringGuts {
func hasNormalizationBoundary(after index: Int) -> Bool {
internal func hasNormalizationBoundary(after index: Int) -> Bool {
let nextIndex = index + 1
if nextIndex >= self.count {
return true
@@ -831,7 +831,7 @@ extension _StringGuts {
}
extension _UnmanagedOpaqueString {
func hasNormalizationBoundary(after index: Int) -> Bool {
internal func hasNormalizationBoundary(after index: Int) -> Bool {
let nextIndex = index + 1
if nextIndex >= self.count {
return true
@@ -843,7 +843,7 @@ extension _UnmanagedOpaqueString {
}
extension _UnmanagedString where CodeUnit == UInt16 {
func hasNormalizationBoundary(after index: Int) -> Bool {
internal func hasNormalizationBoundary(after index: Int) -> Bool {
let nextIndex = index + 1
if nextIndex >= self.count {
return true
@@ -854,6 +854,18 @@ extension _UnmanagedString where CodeUnit == UInt16 {
}
}
extension BidirectionalCollection where Element == UInt16, SubSequence == Self {
internal func hasNormalizationBoundary(after index: Index) -> Bool {
let nextIndex = self.index(after: index)
if nextIndex == self.endIndex {
return true
}
let nextCU = self[nextIndex]
return _hasNormalizationBoundary(before: nextCU)
}
}
private func _compareStringsPostSuffix(
selfASCIIChar: UInt16,
otherUTF16: _UnmanagedString<UInt16>

View File

@@ -12,181 +12,146 @@
import SwiftShims
#if _runtime(_ObjC)
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
@_silgen_name("swift_stdlib_NSStringHashValue")
internal func _stdlib_NSStringHashValue(
_ str: AnyObject, isASCII: Bool) -> Int
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
@_silgen_name("swift_stdlib_NSStringHashValuePointer")
internal func _stdlib_NSStringHashValuePointer(
_ str: OpaquePointer, isASCII: Bool) -> Int
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
@_silgen_name("swift_stdlib_CFStringHashCString")
internal func _stdlib_CFStringHashCString(
_ str: OpaquePointer, _ len: Int) -> Int
#endif
extension Unicode {
// FIXME: cannot be marked @_versioned. See <rdar://problem/34438258>
// @_inlineable // FIXME(sil-serialize-all)
// @_versioned // FIXME(sil-serialize-all)
internal static func hashASCII(
_ string: UnsafeBufferPointer<UInt8>,
into hasher: inout _Hasher
) {
let collationTable = _swift_stdlib_unicode_getASCIICollationTable()
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(Int(truncatingIfNeeded: element))
}
}
}
// FIXME: cannot be marked @_versioned. See <rdar://problem/34438258>
// @_inlineable // FIXME(sil-serialize-all)
// @_versioned // FIXME(sil-serialize-all)
internal static func hashUTF16(
_ string: UnsafeBufferPointer<UInt16>,
into hasher: inout _Hasher
) {
let collationIterator = _swift_stdlib_unicodeCollationIterator_create(
string.baseAddress!,
UInt32(string.count))
defer { _swift_stdlib_unicodeCollationIterator_delete(collationIterator) }
while true {
var hitEnd = false
let element =
_swift_stdlib_unicodeCollationIterator_next(collationIterator, &hitEnd)
if hitEnd {
break
}
// Ignore zero valued collation elements. They don't participate in the
// ordering relation.
if element != 0 {
hasher.append(Int(truncatingIfNeeded: element))
}
}
}
func _emptyASCIIHashBuffer() -> _UIntBuffer<UInt64, UInt8> {
var buffer = _UIntBuffer<UInt64, UInt8>()
// we need to take into account the nul suffix for nul-terminated strings.
// A partially filled ascii buffer should have 1s in the leftover space
buffer._storage = UInt64.max
return buffer
}
#if _runtime(_ObjC)
#if arch(i386) || arch(arm)
private let stringHashOffset = Int(bitPattern: 0x88dd_cc21)
#else
private let stringHashOffset = Int(bitPattern: 0x429b_1266_88dd_cc21)
#endif // arch(i386) || arch(arm)
#endif // _runtime(_ObjC)
internal struct ASCIIHasher {
private var buffer = _emptyASCIIHashBuffer()
internal mutating func consume() -> UInt64? {
if !buffer.isEmpty {
defer { resetBuffer() }
return buffer._storage
}
return nil
}
private mutating func resetBuffer() {
buffer = _emptyASCIIHashBuffer()
}
internal mutating func append(_ c: UInt8) -> UInt64? {
if buffer.count < buffer.capacity {
buffer.append(c)
}
if buffer.count == buffer.capacity {
defer { resetBuffer() }
return buffer._storage
}
return nil
}
}
extension _UnmanagedString where CodeUnit == UInt8 {
@_versioned
@inline(never) // Hide the CF dependency
internal func computeHashValue() -> Int {
#if _runtime(_ObjC)
let hash = _stdlib_CFStringHashCString(OpaquePointer(start), count)
// Mix random bits into NSString's hash so that clients don't rely on
// Swift.String.hashValue and NSString.hash being the same.
return stringHashOffset ^ hash
#else
var hasher = _Hasher()
Unicode.hashASCII(self.buffer, into: &hasher)
return Int(truncatingIfNeeded: hasher.finalize())
#endif // _runtime(_ObjC)
// NOT @_versioned
@effects(releasenone)
internal func hashASCII(into hasher: inout _Hasher) {
var asciiHasher = ASCIIHasher()
for c in self {
if let combined = asciiHasher.append(UInt8(truncatingIfNeeded: c)) {
hasher.append(combined)
}
}
if let combined = asciiHasher.consume() {
hasher.append(combined)
}
}
}
extension _UnmanagedString where CodeUnit == UTF16.CodeUnit {
@_versioned
@inline(never) // Hide the CF dependency
internal func computeHashValue() -> Int {
#if _runtime(_ObjC)
let temp = _NSContiguousString(_StringGuts(_large: self))
let hash = temp._unsafeWithNotEscapedSelfPointer {
return _stdlib_NSStringHashValuePointer($0, isASCII: false)
extension BidirectionalCollection where Element == UInt16, SubSequence == Self {
// NOT @_versioned
internal func hashUTF16(into hasher: inout _Hasher) {
var asciiHasher = ASCIIHasher()
for i in self.indices {
let cu = self[i]
let cuIsASCII = cu <= 0x7F
let isSingleSegmentScalar = self.hasNormalizationBoundary(after: i)
guard cuIsASCII && isSingleSegmentScalar else {
if let combined = asciiHasher.consume() {
hasher.append(combined)
}
// Mix random bits into NSString's hash so that clients don't rely on
// Swift.String.hashValue and NSString.hash being the same.
return stringHashOffset ^ hash
#else
var hasher = _Hasher()
Unicode.hashUTF16(self.buffer, into: &hasher)
return Int(truncatingIfNeeded: hasher.finalize())
#endif // _runtime(_ObjC)
let codeUnitSequence = IteratorSequence(
_NormalizedCodeUnitIterator(self[i..<endIndex])
)
for element in codeUnitSequence {
hasher.append(UInt(element))
}
return
}
if let combined = asciiHasher.append(UInt8(truncatingIfNeeded: cu)) {
hasher.append(combined)
}
}
if let combined = asciiHasher.consume() {
hasher.append(combined)
}
}
}
extension _UnmanagedString where CodeUnit == UInt8 {
@effects(releasenone)
@_versioned
internal func computeHashValue(into hasher: inout _Hasher) {
self.hashASCII(into: &hasher)
}
}
extension _UnmanagedString where CodeUnit == UInt16 {
@effects(releasenone)
@_versioned
internal func computeHashValue(into hasher: inout _Hasher) {
self.hashUTF16(into: &hasher)
}
}
extension _UnmanagedOpaqueString {
@_versioned
@inline(never) // Hide the CF dependency
internal func computeHashValue() -> Int {
#if _runtime(_ObjC)
// TODO: ranged hash?
let hash = _stdlib_NSStringHashValue(cocoaSlice(), isASCII: false)
// Mix random bits into NSString's hash so that clients don't rely on
// Swift.String.hashValue and NSString.hash being the same.
return stringHashOffset ^ hash
#else
// FIXME: Streaming hash
let p = UnsafeMutablePointer<UTF16.CodeUnit>.allocate(capacity: count)
defer { p.deallocate(capacity: count) }
let buffer = UnsafeMutableBufferPointer(start: p, count: count)
_copy(into: buffer)
var hasher = _Hasher()
Unicode.hashUTF16(
UnsafeBufferPointer(start: p, count: count),
into: &hasher)
return Int(truncatingIfNeeded: hasher.finalize())
#endif
internal func computeHashValue(into hasher: inout _Hasher) {
self.hashUTF16(into: &hasher)
}
}
extension _StringGuts {
//
// FIXME(TODO: JIRA): HACK HACK HACK: Work around for ARC :-(
//
@_versioned
@effects(readonly)
@inline(never) // Hide the CF dependency
internal static func _computeHashValue(
_unsafeBitPattern: _RawBitPattern
) -> Int {
return _StringGuts(rawBits: _unsafeBitPattern)._computeHashValue()
@effects(releasenone) // FIXME: Is this guaranteed in the opaque case?
internal func _hash(into hasher: inout _Hasher) {
defer { _fixLifetime(self) }
if _slowPath(_isOpaque) {
_asOpaque().computeHashValue(into: &hasher)
return
}
if isASCII {
_unmanagedASCIIView.computeHashValue(into: &hasher)
return
}
_unmanagedUTF16View.computeHashValue(into: &hasher)
}
@_versioned
// TODO: After removing above hack: @inline(never) // Hide the CF dependency
internal func _computeHashValue() -> Int {
@effects(releasenone) // FIXME: Is this guaranteed in the opaque case?
internal func _hash(_ range: Range<Int>, into hasher: inout _Hasher) {
defer { _fixLifetime(self) }
if _slowPath(_isOpaque) {
return _asOpaque().computeHashValue()
_asOpaque()[range].computeHashValue(into: &hasher)
return
}
if isASCII {
return _unmanagedASCIIView.computeHashValue()
_unmanagedASCIIView[range].computeHashValue(into: &hasher)
return
}
return _unmanagedUTF16View.computeHashValue()
}
@_versioned
// TODO: After removing above hack: @inline(never) // Hide the CF dependency
internal func _computeHashValue(_ range: Range<Int>) -> Int {
defer { _fixLifetime(self) }
if _slowPath(_isOpaque) {
return _asOpaque()[range].computeHashValue()
}
if isASCII {
return _unmanagedASCIIView[range].computeHashValue()
}
return _unmanagedUTF16View[range].computeHashValue()
_unmanagedUTF16View[range].computeHashValue(into: &hasher)
}
}
@@ -195,27 +160,25 @@ extension String : Hashable {
///
/// 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.
@_inlineable // FIXME(sil-serialize-all)
@_inlineable
public var hashValue: Int {
defer { _fixLifetime(self) }
let gutsBits = _guts.rawBits
return _StringGuts._computeHashValue(_unsafeBitPattern: gutsBits)
return _hashValue(for: self)
}
@_inlineable
public func _hash(into hasher: inout _Hasher) {
hasher.append(self.hashValue)
_guts._hash(into: &hasher)
}
}
extension StringProtocol {
@_inlineable // FIXME(sil-serialize-all)
@_inlineable
public var hashValue : Int {
return _wholeString._guts._computeHashValue(_encodedOffsetRange)
return _hashValue(for: self)
}
@_inlineable
public func _hash(into hasher: inout _Hasher) {
hasher.append(self.hashValue)
_wholeString._guts._hash(_encodedOffsetRange, into: &hasher)
}
}

View File

@@ -192,6 +192,7 @@ extension _UIntBuffer : RangeReplaceableCollection {
@inline(__always)
public mutating func append(_ newElement: Element) {
_debugPrecondition(count + 1 <= capacity)
_storage &= ~(Storage(Element.max) &<< _bitCount)
_storage |= Storage(newElement) &<< _bitCount
_bitCount = _bitCount &+ _elementWidth
}

View File

@@ -302,6 +302,39 @@ extension _UnmanagedOpaqueString : _StringVariant {
return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice)
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal subscript(offsetRange: PartialRangeUpTo<Int>) -> SubSequence {
_sanityCheck(offsetRange.upperBound <= range.count)
let b: Range<Int> =
range.lowerBound ..<
range.lowerBound + offsetRange.upperBound
let newSlice = self.isSlice || b.count != range.count
return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice)
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal subscript(offsetRange: PartialRangeThrough<Int>) -> SubSequence {
_sanityCheck(offsetRange.upperBound <= range.count)
let b: Range<Int> =
range.lowerBound ..<
range.lowerBound + offsetRange.upperBound + 1
let newSlice = self.isSlice || b.count != range.count
return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice)
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal subscript(offsetRange: PartialRangeFrom<Int>) -> SubSequence {
_sanityCheck(offsetRange.lowerBound < range.count)
let b: Range<Int> =
range.lowerBound + offsetRange.lowerBound ..<
range.upperBound
let newSlice = self.isSlice || b.count != range.count
return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice)
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal func _copy(

View File

@@ -103,93 +103,6 @@ static const UCollator *GetRootCollator() {
return SWIFT_LAZY_CONSTANT(MakeRootCollator());
}
/// This class caches the collation element results for the ASCII subset of
/// unicode.
class ASCIICollation {
public:
friend class swift::Lazy<ASCIICollation>;
static swift::Lazy<ASCIICollation> theTable;
static const ASCIICollation *getTable() {
return &theTable.get();
}
int32_t CollationTable[128];
/// Maps an ASCII character to a collation element priority as would be
/// returned by a call to ucol_next().
int32_t map(unsigned char c) const {
return CollationTable[c];
}
private:
/// Construct the ASCII collation table.
ASCIICollation() {
const UCollator *Collator = GetRootCollator();
for (unsigned char c = 0; c < 128; ++c) {
UErrorCode ErrorCode = U_ZERO_ERROR;
intptr_t NumCollationElts = 0;
UChar Buffer[1];
Buffer[0] = c;
UCollationElements *CollationIterator =
ucol_openElements(Collator, Buffer, 1, &ErrorCode);
while (U_SUCCESS(ErrorCode)) {
intptr_t Elem = ucol_next(CollationIterator, &ErrorCode);
if (Elem != UCOL_NULLORDER) {
CollationTable[c] = Elem;
++NumCollationElts;
} else {
break;
}
}
ucol_closeElements(CollationIterator);
if (U_FAILURE(ErrorCode) || NumCollationElts != 1) {
swift::crash("Error setting up the ASCII collation table");
}
}
}
ASCIICollation &operator=(const ASCIICollation &) = delete;
ASCIICollation(const ASCIICollation &) = delete;
};
void *swift::_swift_stdlib_unicodeCollationIterator_create(
const __swift_uint16_t *Str, __swift_uint32_t Length) {
UErrorCode ErrorCode = U_ZERO_ERROR;
UCollationElements *CollationIterator =
ucol_openElements(GetRootCollator(), reinterpret_cast<const UChar *>(Str),
Length, &ErrorCode);
if (U_FAILURE(ErrorCode)) {
swift::crash("_swift_stdlib_unicodeCollationIterator_create: ucol_openElements() failed.");
}
return CollationIterator;
}
__swift_int32_t swift::_swift_stdlib_unicodeCollationIterator_next(
void *CollationIterator, bool *HitEnd) {
UErrorCode ErrorCode = U_ZERO_ERROR;
auto Result = ucol_next(
static_cast<UCollationElements *>(CollationIterator), &ErrorCode);
if (U_FAILURE(ErrorCode)) {
swift::crash(
"_swift_stdlib_unicodeCollationIterator_next: ucol_next() failed.");
}
*HitEnd = (Result == UCOL_NULLORDER);
return Result;
}
void swift::_swift_stdlib_unicodeCollationIterator_delete(
void *CollationIterator) {
ucol_closeElements(static_cast<UCollationElements *>(CollationIterator));
}
const __swift_int32_t *swift::_swift_stdlib_unicode_getASCIICollationTable() {
return ASCIICollation::getTable()->CollationTable;
}
/// Convert the unicode string to uppercase. This function will return the
/// required buffer length as a result. If this length does not match the
/// 'DestinationCapacity' this function must be called again with a buffer of
@@ -231,8 +144,6 @@ swift::_swift_stdlib_unicode_strToLower(uint16_t *Destination,
}
return OutputLength;
}
swift::Lazy<ASCIICollation> ASCIICollation::theTable;
#endif
namespace {

View File

@@ -470,48 +470,6 @@ var nsStringCanaryCount = 0
}
}
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValue/NoLeak") {
nsStringCanaryCount = 0
autoreleasepool {
let a = NSStringCanary()
expectEqual(1, nsStringCanaryCount)
_stdlib_NSStringHashValue(a, isASCII: true)
}
expectEqual(0, nsStringCanaryCount)
}
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValueNonASCII/NoLeak") {
nsStringCanaryCount = 0
autoreleasepool {
let a = NSStringCanary()
expectEqual(1, nsStringCanaryCount)
_stdlib_NSStringHashValue(a, isASCII: false)
}
expectEqual(0, nsStringCanaryCount)
}
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValuePointer/NoLeak") {
nsStringCanaryCount = 0
autoreleasepool {
let a = NSStringCanary()
expectEqual(1, nsStringCanaryCount)
let ptrA = unsafeBitCast(a, to: OpaquePointer.self)
_stdlib_NSStringHashValuePointer(ptrA, isASCII: true)
}
expectEqual(0, nsStringCanaryCount)
}
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValuePointerNonASCII/NoLeak") {
nsStringCanaryCount = 0
autoreleasepool {
let a = NSStringCanary()
expectEqual(1, nsStringCanaryCount)
let ptrA = unsafeBitCast(a, to: OpaquePointer.self)
_stdlib_NSStringHashValuePointer(ptrA, isASCII: false)
}
expectEqual(0, nsStringCanaryCount)
}
RuntimeFoundationWrappers.test("_stdlib_NSStringLowercaseString/NoLeak") {
nsStringCanaryCount = 0
autoreleasepool {