From 8ae92cac0020d5f9d6000bc763a0ecf856e5661a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 3 Oct 2018 15:31:41 +0100 Subject: [PATCH 1/4] [stdlib] Hasher: Make buffer inlinable; make SipHash state resilient --- stdlib/public/core/Hasher.swift | 524 +++++++++++------------- stdlib/public/core/SipHash.swift | 120 +----- stdlib/public/core/StringHashable.swift | 34 +- 3 files changed, 260 insertions(+), 418 deletions(-) diff --git a/stdlib/public/core/Hasher.swift b/stdlib/public/core/Hasher.swift index 488ce0873e733..4c0f59a0da6e6 100644 --- a/stdlib/public/core/Hasher.swift +++ b/stdlib/public/core/Hasher.swift @@ -16,21 +16,14 @@ import SwiftShims -// FIXME: Remove @usableFromInline once Hasher is resilient. -// rdar://problem/38549901 -@usableFromInline -internal protocol _HasherCore { - init(rawSeed: (UInt64, UInt64)) - mutating func compress(_ value: UInt64) - mutating func finalize(tailAndByteCount: UInt64) -> UInt64 -} - -extension _HasherCore { +extension _SipHash13Core { + @inlinable @inline(__always) internal init() { self.init(rawSeed: Hasher._executionSeed) } + @inlinable @inline(__always) internal init(seed: Int) { let executionSeed = Hasher._executionSeed @@ -43,235 +36,6 @@ extension _HasherCore { } } -@inline(__always) -internal func _loadPartialUnalignedUInt64LE( - _ p: UnsafeRawPointer, - byteCount: Int -) -> UInt64 { - var result: UInt64 = 0 - switch byteCount { - case 7: - result |= UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) &<< 48 - fallthrough - case 6: - result |= UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) &<< 40 - fallthrough - case 5: - result |= UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) &<< 32 - fallthrough - case 4: - result |= UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) &<< 24 - fallthrough - case 3: - result |= UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) &<< 16 - fallthrough - case 2: - result |= UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) &<< 8 - fallthrough - case 1: - result |= UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) - fallthrough - case 0: - return result - default: - _sanityCheckFailure() - } -} - -/// This is a buffer for segmenting arbitrary data into 8-byte chunks. Buffer -/// storage is represented by a single 64-bit value in the format used by the -/// finalization step of SipHash. (The least significant 56 bits hold the -/// trailing bytes, while the most significant 8 bits hold the count of bytes -/// appended so far, modulo 256. The count of bytes currently stored in the -/// buffer is in the lower three bits of the byte count.) -// FIXME: Remove @usableFromInline and @_fixed_layout once Hasher is resilient. -// rdar://problem/38549901 -@usableFromInline @_fixed_layout -internal struct _HasherTailBuffer { - // msb lsb - // +---------+-------+-------+-------+-------+-------+-------+-------+ - // |byteCount| tail (<= 56 bits) | - // +---------+-------+-------+-------+-------+-------+-------+-------+ - internal var value: UInt64 - - @inline(__always) - internal init() { - self.value = 0 - } - - @inline(__always) - internal init(tail: UInt64, byteCount: UInt64) { - // byteCount can be any value, but we only keep the lower 8 bits. (The - // lower three bits specify the count of bytes stored in this buffer.) - // FIXME: This should be a single expression, but it causes exponential - // behavior in the expression type checker . - let shiftedByteCount: UInt64 = ((byteCount & 7) << 3) - let mask: UInt64 = (1 << shiftedByteCount - 1) - _sanityCheck(tail & ~mask == 0) - self.value = (byteCount &<< 56 | tail) - } - - @inline(__always) - internal init(tail: UInt64, byteCount: Int) { - self.init(tail: tail, byteCount: UInt64(truncatingIfNeeded: byteCount)) - } - - internal var tail: UInt64 { - @inline(__always) - get { return value & ~(0xFF &<< 56) } - } - - internal var byteCount: UInt64 { - @inline(__always) - get { return value &>> 56 } - } - - @inline(__always) - internal mutating func append(_ bytes: UInt64) -> UInt64 { - let c = byteCount & 7 - if c == 0 { - value = value &+ (8 &<< 56) - return bytes - } - let shift = c &<< 3 - let chunk = tail | (bytes &<< shift) - value = (((value &>> 56) &+ 8) &<< 56) | (bytes &>> (64 - shift)) - return chunk - } - - @inline(__always) - internal - mutating func append(_ bytes: UInt64, count: UInt64) -> UInt64? { - _sanityCheck(count >= 0 && count < 8) - _sanityCheck(bytes & ~((1 &<< (count &<< 3)) &- 1) == 0) - let c = byteCount & 7 - let shift = c &<< 3 - if c + count < 8 { - value = (value | (bytes &<< shift)) &+ (count &<< 56) - return nil - } - let chunk = tail | (bytes &<< shift) - value = ((value &>> 56) &+ count) &<< 56 - if c + count > 8 { - value |= bytes &>> (64 - shift) - } - return chunk - } -} - -// FIXME: Remove @usableFromInline and @_fixed_layout once Hasher is resilient. -// rdar://problem/38549901 -@usableFromInline @_fixed_layout -internal struct _BufferingHasher { - private var _buffer: _HasherTailBuffer - private var _core: RawCore - - @inline(__always) - internal init(core: RawCore) { - self._buffer = _HasherTailBuffer() - self._core = core - } - - @inline(__always) - internal init() { - self.init(core: RawCore()) - } - - @inline(__always) - internal init(seed: Int) { - self.init(core: RawCore(seed: seed)) - } - - @inline(__always) - internal mutating func combine(_ value: UInt) { -#if arch(i386) || arch(arm) - combine(UInt32(truncatingIfNeeded: value)) -#else - combine(UInt64(truncatingIfNeeded: value)) -#endif - } - - @inline(__always) - internal mutating func combine(_ value: UInt64) { - _core.compress(_buffer.append(value)) - } - - @inline(__always) - internal mutating func combine(_ value: UInt32) { - let value = UInt64(truncatingIfNeeded: value) - if let chunk = _buffer.append(value, count: 4) { - _core.compress(chunk) - } - } - - @inline(__always) - internal mutating func combine(_ value: UInt16) { - let value = UInt64(truncatingIfNeeded: value) - if let chunk = _buffer.append(value, count: 2) { - _core.compress(chunk) - } - } - - @inline(__always) - internal mutating func combine(_ value: UInt8) { - let value = UInt64(truncatingIfNeeded: value) - if let chunk = _buffer.append(value, count: 1) { - _core.compress(chunk) - } - } - - @inline(__always) - internal mutating func combine(bytes: UInt64, count: Int) { - _sanityCheck(count >= 0 && count < 8) - let count = UInt64(truncatingIfNeeded: count) - if let chunk = _buffer.append(bytes, count: count) { - _core.compress(chunk) - } - } - - @inline(__always) - internal mutating func combine(bytes: UnsafeRawBufferPointer) { - var remaining = bytes.count - guard remaining > 0 else { return } - var data = bytes.baseAddress! - - // Load first unaligned partial word of data - do { - let start = UInt(bitPattern: data) - let end = _roundUp(start, toAlignment: MemoryLayout.alignment) - let c = min(remaining, Int(end - start)) - if c > 0 { - let chunk = _loadPartialUnalignedUInt64LE(data, byteCount: c) - combine(bytes: chunk, count: c) - data += c - remaining -= c - } - } - _sanityCheck( - remaining == 0 || - Int(bitPattern: data) & (MemoryLayout.alignment - 1) == 0) - - // Load as many aligned words as there are in the input buffer - while remaining >= MemoryLayout.size { - combine(UInt64(littleEndian: data.load(as: UInt64.self))) - data += MemoryLayout.size - remaining -= MemoryLayout.size - } - - // Load last partial word of data - _sanityCheck(remaining >= 0 && remaining < 8) - if remaining > 0 { - let chunk = _loadPartialUnalignedUInt64LE(data, byteCount: remaining) - combine(bytes: chunk, count: remaining) - } - } - - @inline(__always) - internal mutating func finalize() -> UInt64 { - return _core.finalize(tailAndByteCount: _buffer.value) - } -} - /// The universal hash function used by `Set` and `Dictionary`. /// /// `Hasher` can be used to map an arbitrary sequence of bytes to an integer @@ -295,43 +59,135 @@ internal struct _BufferingHasher { /// different values on every new execution of your program. The hash /// algorithm implemented by `Hasher` may itself change between any two /// versions of the standard library. -@_fixed_layout // FIXME: Should be resilient (rdar://problem/38549901) +@_fixed_layout public struct Hasher { - // FIXME: Remove @usableFromInline once Hasher is resilient. - // rdar://problem/38549901 @usableFromInline - internal typealias RawCore = _SipHash13Core - // FIXME: Remove @usableFromInline once Hasher is resilient. - // rdar://problem/38549901 + internal typealias _Core = _SipHash13Core + @usableFromInline - internal typealias Core = _BufferingHasher + internal var _tail: _TailBuffer - internal var _core: Core + @usableFromInline + internal var _core: _Core /// Creates a new hasher. /// /// The hasher uses a per-execution seed value that is set during process /// startup, usually from a high-quality random source. - @_effects(releasenone) + @inlinable public init() { - self._core = Core() + self._tail = _TailBuffer() + self._core = _Core() } /// Initialize a new hasher using the specified seed value. /// The provided seed is mixed in with the global execution seed. - @usableFromInline - @_effects(releasenone) + @inlinable internal init(_seed: Int) { - self._core = Core(seed: _seed) + self._tail = _TailBuffer() + self._core = _Core(seed: _seed) } /// Initialize a new hasher using the specified seed value. - @usableFromInline // @testable - @_effects(releasenone) + @inlinable internal init(_rawSeed: (UInt64, UInt64)) { - self._core = Core(core: RawCore(rawSeed: _rawSeed)) + self._tail = _TailBuffer() + self._core = _Core(rawSeed: _rawSeed) + } +} + +extension Hasher { + /// This is a buffer for segmenting arbitrary data into 8-byte chunks. Buffer + /// storage is represented by a single 64-bit value in the format used by the + /// finalization step of SipHash. (The least significant 56 bits hold the + /// trailing bytes, while the most significant 8 bits hold the count of bytes + /// appended so far, modulo 256. The count of bytes currently stored in the + /// buffer is in the lower three bits of the byte count.) + @usableFromInline + @_fixed_layout + internal struct _TailBuffer { + // msb lsb + // +---------+-------+-------+-------+-------+-------+-------+-------+ + // |byteCount| tail (<= 56 bits) | + // +---------+-------+-------+-------+-------+-------+-------+-------+ + @usableFromInline + internal var value: UInt64 + + @inlinable + @inline(__always) + internal init() { + self.value = 0 + } + + @inlinable + @inline(__always) + internal init(tail: UInt64, byteCount: UInt64) { + // byteCount can be any value, but we only keep the lower 8 bits. (The + // lower three bits specify the count of bytes stored in this buffer.) + // FIXME: This should be a single expression, but it causes exponential + // behavior in the expression type checker . + let shiftedByteCount: UInt64 = (byteCount & 7) &<< 3 + let mask: UInt64 = (1 &<< shiftedByteCount) &- 1 + _sanityCheck(tail & ~mask == 0) + self.value = (byteCount &<< 56 | tail) + } + + @inlinable + @inline(__always) + internal init(tail: UInt64, byteCount: Int) { + self.init(tail: tail, byteCount: UInt64(truncatingIfNeeded: byteCount)) + } + + @inlinable + internal var tail: UInt64 { + @inline(__always) + get { return value & ~(0xFF &<< 56) } + } + + @inlinable + internal var byteCount: UInt64 { + @inline(__always) + get { return value &>> 56 } + } + + @inlinable + @inline(__always) + internal mutating func append(_ bytes: UInt64) -> UInt64 { + let c = byteCount & 7 + if c == 0 { + value = value &+ (8 &<< 56) + return bytes + } + let shift = c &<< 3 + let chunk = tail | (bytes &<< shift) + value = (((value &>> 56) &+ 8) &<< 56) | (bytes &>> (64 &- shift)) + return chunk + } + + @inlinable + @inline(__always) + internal + mutating func append(_ bytes: UInt64, count: UInt64) -> UInt64? { + _sanityCheck(count >= 0 && count < 8) + let inputMask: UInt64 = (1 &<< (count &<< 3)) &- 1 + _sanityCheck(bytes & ~inputMask == 0) + let c = byteCount & 7 + let shift = c &<< 3 + if c &+ count < 8 { + value = (value | (bytes &<< shift)) &+ (count &<< 56) + return nil + } + let chunk = tail | (bytes &<< shift) + value = ((value &>> 56) &+ count) &<< 56 + if c &+ count > 8 { + value |= bytes &>> (64 &- shift) + } + return chunk + } } +} +extension Hasher { // Runtime settings /// Indicates whether we're running in an environment where hashing needs to /// be deterministic. If this is true, the hash seed is not random, and hash /// tables do not apply per-instance perturbation that is not repeatable. @@ -348,7 +204,7 @@ public struct Hasher { /// The 128-bit hash seed used to initialize the hasher state. Initialized /// once during process startup. - @inlinable // @testable + @inlinable internal static var _executionSeed: (UInt64, UInt64) { @inline(__always) get { @@ -361,7 +217,9 @@ public struct Hasher { _swift_stdlib_Hashing_parameters.seed1) } } +} +extension Hasher { // Combining arbitrary Hashable values /// Adds the given value to this hasher, mixing its essential parts into the /// hasher state. /// @@ -371,41 +229,89 @@ public struct Hasher { public mutating func combine(_ value: H) { value.hash(into: &self) } +} - @_effects(releasenone) - @usableFromInline +extension Hasher { // Combining integers + @inlinable + @inline(__always) internal mutating func _combine(_ value: UInt) { - _core.combine(value) +#if arch(i386) || arch(arm) + self.combine(UInt32(truncatingIfNeeded: value)) +#else + self.combine(UInt64(truncatingIfNeeded: value)) +#endif } - @_effects(releasenone) - @usableFromInline + @inlinable + @inline(__always) internal mutating func _combine(_ value: UInt64) { - _core.combine(value) + _core.compress(_tail.append(value)) } - @_effects(releasenone) - @usableFromInline + @inlinable + @inline(__always) internal mutating func _combine(_ value: UInt32) { - _core.combine(value) + self._combine(bytes: UInt64(truncatingIfNeeded: value), count: 4) } - @_effects(releasenone) - @usableFromInline + @inlinable + @inline(__always) internal mutating func _combine(_ value: UInt16) { - _core.combine(value) + self._combine(bytes: UInt64(truncatingIfNeeded: value), count: 2) } - @_effects(releasenone) - @usableFromInline + @inlinable + @inline(__always) internal mutating func _combine(_ value: UInt8) { - _core.combine(value) + self._combine(bytes: UInt64(truncatingIfNeeded: value), count: 1) } - @_effects(releasenone) - @usableFromInline - internal mutating func _combine(bytes value: UInt64, count: Int) { - _core.combine(bytes: value, count: count) + @inlinable + @inline(__always) + internal mutating func _combine(bytes: UInt64, count: Int) { + _sanityCheck(count >= 0 && count < 8) + let count = UInt64(truncatingIfNeeded: count) + if let chunk = _tail.append(bytes, count: count) { + _core.compress(chunk) + } + } +} + +extension Hasher { // Combining arbitrary byte sequences + // Load a partial 64-bit little-endian integer from the specified address. + @inline(__always) + internal static func _loadPartialUnalignedUInt64LE( + _ p: UnsafeRawPointer, + byteCount: Int + ) -> UInt64 { + var result: UInt64 = 0 + switch byteCount { + case 7: + result |= UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) &<< 48 + fallthrough + case 6: + result |= UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) &<< 40 + fallthrough + case 5: + result |= UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) &<< 32 + fallthrough + case 4: + result |= UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) &<< 24 + fallthrough + case 3: + result |= UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) &<< 16 + fallthrough + case 2: + result |= UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) &<< 8 + fallthrough + case 1: + result |= UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) + fallthrough + case 0: + return result + default: + _sanityCheckFailure() + } } /// Adds the contents of the given buffer to this hasher, mixing it into the @@ -414,16 +320,53 @@ public struct Hasher { /// - Parameter bytes: A raw memory buffer. @_effects(releasenone) public mutating func combine(bytes: UnsafeRawBufferPointer) { - _core.combine(bytes: bytes) + var remaining = bytes.count + guard remaining > 0 else { return } + var data = bytes.baseAddress! + + // Load first unaligned partial word of data + do { + let start = UInt(bitPattern: data) + let end = _roundUp(start, toAlignment: MemoryLayout.alignment) + let c = min(remaining, Int(end - start)) + if c > 0 { + let chunk = Hasher._loadPartialUnalignedUInt64LE(data, byteCount: c) + self._combine(bytes: chunk, count: c) + data += c + remaining -= c + } + } + _sanityCheck( + remaining == 0 || + Int(bitPattern: data) & (MemoryLayout.alignment - 1) == 0) + + // Load as many aligned words as there are in the input buffer + while remaining >= MemoryLayout.size { + self._combine(UInt64(littleEndian: data.load(as: UInt64.self))) + data += MemoryLayout.size + remaining -= MemoryLayout.size + } + + // Load last partial word of data + _sanityCheck(remaining >= 0 && remaining < 8) + if remaining > 0 { + let chunk = Hasher._loadPartialUnalignedUInt64LE( + data, + byteCount: remaining) + self._combine(bytes: chunk, count: remaining) + } } +} +extension Hasher { // Finalization /// Finalize the hasher state and return the hash value. /// Finalizing invalidates the hasher; additional bits cannot be combined /// into it, and it cannot be finalized again. @_effects(releasenone) @usableFromInline internal mutating func _finalize() -> Int { - return Int(truncatingIfNeeded: _core.finalize()) + let value = _core.finalize(tailAndByteCount: _tail.value) + return Int(truncatingIfNeeded: value) } /// Finalizes the hasher state and returns the hash value. @@ -439,31 +382,34 @@ public struct Hasher { @_effects(releasenone) public __consuming func finalize() -> Int { var core = _core - return Int(truncatingIfNeeded: core.finalize()) + let value = core.finalize(tailAndByteCount: _tail.value) + return Int(truncatingIfNeeded: value) } +} +extension Hasher { // Helpers for _rawHashValue(seed:) @_effects(readnone) @usableFromInline internal static func _hash(seed: Int, _ value: UInt64) -> Int { - var core = RawCore(seed: seed) + var core = _Core(seed: seed) core.compress(value) - let tbc = _HasherTailBuffer(tail: 0, byteCount: 8) + let tbc = _TailBuffer(tail: 0, byteCount: 8) return Int(truncatingIfNeeded: core.finalize(tailAndByteCount: tbc.value)) } @_effects(readnone) @usableFromInline internal static func _hash(seed: Int, _ value: UInt) -> Int { - var core = RawCore(seed: seed) + var core = _Core(seed: seed) #if arch(i386) || arch(arm) - _sanityCheck(UInt.bitWidth < UInt64.bitWidth) - let tbc = _HasherTailBuffer( + _sanityCheck(UInt.bitWidth == 32) + let tbc = _TailBuffer( tail: UInt64(truncatingIfNeeded: value), byteCount: UInt.bitWidth &>> 3) #else _sanityCheck(UInt.bitWidth == UInt64.bitWidth) core.compress(UInt64(truncatingIfNeeded: value)) - let tbc = _HasherTailBuffer(tail: 0, byteCount: 8) + let tbc = _TailBuffer(tail: 0, byteCount: 8) #endif return Int(truncatingIfNeeded: core.finalize(tailAndByteCount: tbc.value)) } @@ -475,8 +421,8 @@ public struct Hasher { bytes value: UInt64, count: Int) -> Int { _sanityCheck(count >= 0 && count < 8) - var core = RawCore(seed: seed) - let tbc = _HasherTailBuffer(tail: value, byteCount: count) + var core = _Core(seed: seed) + let tbc = _TailBuffer(tail: value, byteCount: count) return Int(truncatingIfNeeded: core.finalize(tailAndByteCount: tbc.value)) } @@ -485,8 +431,8 @@ public struct Hasher { internal static func _hash( seed: Int, bytes: UnsafeRawBufferPointer) -> Int { - var core = Core(seed: seed) - core.combine(bytes: bytes) - return Int(truncatingIfNeeded: core.finalize()) + var hasher = Hasher(_seed: seed) + hasher.combine(bytes: bytes) + return hasher.finalize() } } diff --git a/stdlib/public/core/SipHash.swift b/stdlib/public/core/SipHash.swift index e3412f271c3f9..93cf9fe1339e7 100644 --- a/stdlib/public/core/SipHash.swift +++ b/stdlib/public/core/SipHash.swift @@ -19,9 +19,6 @@ /// * Daniel J. Bernstein //===----------------------------------------------------------------------===// -// FIXME: Remove @usableFromInline and @_fixed_layout once Hasher is resilient. -// rdar://problem/38549901 -@usableFromInline @_fixed_layout internal struct _SipHashState { // "somepseudorandomlygeneratedbytes" fileprivate var v0: UInt64 = 0x736f6d6570736575 @@ -67,25 +64,26 @@ internal struct _SipHashState { } } -// FIXME: Remove @usableFromInline and @_fixed_layout once Hasher is resilient. -// rdar://problem/38549901 -@usableFromInline @_fixed_layout -internal struct _SipHash13Core: _HasherCore { +@usableFromInline +internal struct _SipHash13Core { private var _state: _SipHashState - @inline(__always) + @usableFromInline + @_effects(releasenone) internal init(rawSeed: (UInt64, UInt64)) { _state = _SipHashState(rawSeed: rawSeed) } - @inline(__always) + @usableFromInline + @_effects(releasenone) internal mutating func compress(_ m: UInt64) { _state.v3 ^= m _state._round() _state.v0 ^= m } - @inline(__always) + @usableFromInline + @_effects(releasenone) internal mutating func finalize(tailAndByteCount: UInt64) -> UInt64 { compress(tailAndByteCount) _state.v2 ^= 0xff @@ -95,105 +93,3 @@ internal struct _SipHash13Core: _HasherCore { return _state._extract() } } - -internal struct _SipHash24Core: _HasherCore { - private var _state: _SipHashState - - @inline(__always) - internal init(rawSeed: (UInt64, UInt64)) { - _state = _SipHashState(rawSeed: rawSeed) - } - - @inline(__always) - internal mutating func compress(_ m: UInt64) { - _state.v3 ^= m - _state._round() - _state._round() - _state.v0 ^= m - } - - @inline(__always) - internal mutating func finalize(tailAndByteCount: UInt64) -> UInt64 { - compress(tailAndByteCount) - - _state.v2 ^= 0xff - for _ in 0..<4 { - _state._round() - } - return _state._extract() - } -} - -// FIXME: This type only exists to facilitate testing, and should not exist in -// production builds. -@usableFromInline // @testable -internal struct _SipHash13 { - internal typealias Core = _SipHash13Core - - internal var _core: _BufferingHasher - - @usableFromInline // @testable - internal init(_rawSeed: (UInt64, UInt64)) { - _core = _BufferingHasher(core: Core(rawSeed: _rawSeed)) - } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt64) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt32) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt16) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt8) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(bytes v: UInt64, count: Int) { - _core.combine(bytes: v, count: count) - } - @usableFromInline // @testable - internal mutating func combine(bytes: UnsafeRawBufferPointer) { - _core.combine(bytes: bytes) - } - @usableFromInline // @testable - internal __consuming func finalize() -> UInt64 { - var core = _core - return core.finalize() - } -} - -// FIXME: This type only exists to facilitate testing, and should not exist in -// production builds. -@usableFromInline // @testable -internal struct _SipHash24 { - internal typealias Core = _SipHash24Core - - internal var _core: _BufferingHasher - - @usableFromInline // @testable - internal init(_rawSeed: (UInt64, UInt64)) { - _core = _BufferingHasher(core: Core(rawSeed: _rawSeed)) - } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt64) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt32) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt16) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(_ v: UInt8) { _core.combine(v) } - @usableFromInline // @testable - internal mutating func _combine(bytes v: UInt64, count: Int) { - _core.combine(bytes: v, count: count) - } - @usableFromInline // @testable - internal mutating func combine(bytes: UnsafeRawBufferPointer) { - _core.combine(bytes: bytes) - } - @usableFromInline // @testable - internal __consuming func finalize() -> UInt64 { - var core = _core - return core.finalize() - } -} diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 5ff449aa1029b..cd2f7b6b648dd 100644 --- a/stdlib/public/core/StringHashable.swift +++ b/stdlib/public/core/StringHashable.swift @@ -13,20 +13,20 @@ import SwiftShims extension _UnmanagedString where CodeUnit == UInt8 { - internal func hashASCII(into core: inout Hasher.Core) { - core.combine(bytes: rawBuffer) + internal func hashASCII(into hasher: inout Hasher) { + hasher.combine(bytes: rawBuffer) } } extension BidirectionalCollection where Element == UInt16, SubSequence == Self { - internal func hashUTF16(into core: inout Hasher.Core) { + internal func hashUTF16(into hasher: inout Hasher) { for i in self.indices { let cu = self[i] let cuIsASCII = cu <= 0x7F let isSingleSegmentScalar = self.hasNormalizationBoundary(after: i) if cuIsASCII && isSingleSegmentScalar { - core.combine(UInt8(truncatingIfNeeded: cu)) + hasher._combine(UInt8(truncatingIfNeeded: cu)) } else { for encodedScalar in Unicode._ParsingIterator( codeUnits: _NormalizedCodeUnitIterator(self[i.. Int { @@ -57,27 +57,27 @@ extension _UnmanagedString where CodeUnit == UInt8 { extension _UnmanagedString where CodeUnit == UInt16 { internal func hash(into hasher: inout Hasher) { - self.hashUTF16(into: &hasher._core) - hasher._core.combine(0xFF as UInt8) // terminator + self.hashUTF16(into: &hasher) + hasher._combine(0xFF as UInt8) // terminator } internal func _rawHashValue(seed: Int) -> Int { - var core = Hasher.Core(seed: seed) - self.hashUTF16(into: &core) - return Int(truncatingIfNeeded: core.finalize()) + var hasher = Hasher(_seed: seed) + self.hashUTF16(into: &hasher) + return hasher._finalize() } } extension _UnmanagedOpaqueString { internal func hash(into hasher: inout Hasher) { - self.hashUTF16(into: &hasher._core) - hasher._core.combine(0xFF as UInt8) // terminator + self.hashUTF16(into: &hasher) + hasher._combine(0xFF as UInt8) // terminator } internal func _rawHashValue(seed: Int) -> Int { - var core = Hasher.Core(seed: seed) - self.hashUTF16(into: &core) - return Int(truncatingIfNeeded: core.finalize()) + var hasher = Hasher(_seed: seed) + self.hashUTF16(into: &hasher) + return hasher._finalize() } } From 45c9be51340f048ee89451bddaf2b2493c83922e Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 28 Sep 2018 19:36:25 +0100 Subject: [PATCH 2/4] [stdlib] Set, Dictionary: Flatten switch statements The optimizer dislikes nested switch statements; flatten them out to simplify optimization and to hopefully speed things up a little. --- stdlib/public/core/Dictionary.swift | 84 ++++-- stdlib/public/core/DictionaryBridging.swift | 8 +- stdlib/public/core/DictionaryCasting.swift | 14 +- stdlib/public/core/DictionaryVariant.swift | 278 ++++++++------------ stdlib/public/core/NativeDictionary.swift | 9 +- stdlib/public/core/NativeSet.swift | 9 +- stdlib/public/core/Set.swift | 73 +++-- stdlib/public/core/SetBridging.swift | 8 +- stdlib/public/core/SetCasting.swift | 8 +- stdlib/public/core/SetVariant.swift | 209 +++++++-------- 10 files changed, 338 insertions(+), 362 deletions(-) diff --git a/stdlib/public/core/Dictionary.swift b/stdlib/public/core/Dictionary.swift index 8169357875226..df816e40ed1fe 100644 --- a/stdlib/public/core/Dictionary.swift +++ b/stdlib/public/core/Dictionary.swift @@ -1514,20 +1514,14 @@ extension Dictionary { @inlinable public mutating func swapAt(_ i: Index, _ j: Index) { guard i != j else { return } - let (a, b): (_HashTable.Bucket, _HashTable.Bucket) - switch _variant { - case .native(let native): - a = native.validatedBucket(for: i) - b = native.validatedBucket(for: j) #if _runtime(_ObjC) - case .cocoa(let cocoa): - _variant.cocoaPath() - let native = _NativeDictionary(cocoa) - a = native.validatedBucket(for: i) - b = native.validatedBucket(for: j) - _variant = .native(native) -#endif + if !_variant.isNative { + _variant = .native(_NativeDictionary(_variant.asCocoa)) } +#endif + let native = _variant.asNative + let a = native.validatedBucket(for: i) + let b = native.validatedBucket(for: j) let isUnique = _variant.isUniquelyReferenced() _variant.asNative.swapValuesAt(a, b, isUnique: isUnique) } @@ -1851,6 +1845,17 @@ extension Dictionary.Index { var handle = _asCocoa.handleBitPattern return handle == 0 || _isUnique_native(&handle) } + + @usableFromInline @_transparent + internal var _isNative: Bool { + switch _variant { + case .native: + return true + case .cocoa: + _cocoaPath() + return false + } + } #endif @usableFromInline @_transparent @@ -1936,19 +1941,17 @@ extension Dictionary.Index: Comparable { extension Dictionary.Index: Hashable { public // FIXME(cocoa-index): Make inlinable func hash(into hasher: inout Hasher) { - #if _runtime(_ObjC) - switch _variant { - case .native(let nativeIndex): - hasher.combine(0 as UInt8) - hasher.combine(nativeIndex.bucket.offset) - case .cocoa(let cocoaIndex): - _cocoaPath() +#if _runtime(_ObjC) + guard _isNative else { hasher.combine(1 as UInt8) - hasher.combine(cocoaIndex.storage.currentKeyIndex) + hasher.combine(_asCocoa.storage.currentKeyIndex) + return } - #else + hasher.combine(0 as UInt8) hasher.combine(_asNative.bucket.offset) - #endif +#else + hasher.combine(_asNative.bucket.offset) +#endif } } @@ -2012,6 +2015,17 @@ extension Dictionary.Iterator { _conditionallyUnreachable() } } + + @usableFromInline @_transparent + internal var _isNative: Bool { + switch _variant { + case .native: + return true + case .cocoa: + _cocoaPath() + return false + } + } #endif @usableFromInline @_transparent @@ -2030,6 +2044,21 @@ extension Dictionary.Iterator { self._variant = .native(newValue) } } + +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var _asCocoa: _CocoaDictionary.Iterator { + get { + switch _variant { + case .native: + _sanityCheckFailure("internal error: does not contain a Cocoa index") + case .cocoa(let cocoa): + return cocoa + } + } + } +#endif + } extension Dictionary.Iterator: IteratorProtocol { @@ -2040,20 +2069,17 @@ extension Dictionary.Iterator: IteratorProtocol { @inlinable @inline(__always) public mutating func next() -> (key: Key, value: Value)? { - switch _variant { - case .native: - return _asNative.next() #if _runtime(_ObjC) - case .cocoa(let cocoaIterator): - _cocoaPath() - if let (cocoaKey, cocoaValue) = cocoaIterator.next() { + guard _isNative else { + if let (cocoaKey, cocoaValue) = _asCocoa.next() { let nativeKey = _forceBridgeFromObjectiveC(cocoaKey, Key.self) let nativeValue = _forceBridgeFromObjectiveC(cocoaValue, Value.self) return (nativeKey, nativeValue) } return nil -#endif } +#endif + return _asNative.next() } } diff --git a/stdlib/public/core/DictionaryBridging.swift b/stdlib/public/core/DictionaryBridging.swift index a434aaf7db9c0..cf67d5887a18f 100644 --- a/stdlib/public/core/DictionaryBridging.swift +++ b/stdlib/public/core/DictionaryBridging.swift @@ -797,12 +797,10 @@ extension _CocoaDictionary.Iterator: IteratorProtocol { extension Dictionary { @inlinable public __consuming func _bridgeToObjectiveCImpl() -> _NSDictionaryCore { - switch _variant { - case .native(let nativeDictionary): - return nativeDictionary.bridged() - case .cocoa(let cocoaDictionary): - return cocoaDictionary.object + guard _variant.isNative else { + return _variant.asCocoa.object } + return _variant.asNative.bridged() } /// Returns the native Dictionary hidden inside this NSDictionary; diff --git a/stdlib/public/core/DictionaryCasting.swift b/stdlib/public/core/DictionaryCasting.swift index 50403ee41c7e9..38f83b940772f 100644 --- a/stdlib/public/core/DictionaryCasting.swift +++ b/stdlib/public/core/DictionaryCasting.swift @@ -56,14 +56,14 @@ public func _dictionaryDownCast( && _isClassOrObjCExistential(DerivedKey.self) && _isClassOrObjCExistential(DerivedValue.self) { - switch source._variant { - case .native(let native): - // Note: it is safe to treat the buffer as immutable here because - // Dictionary will not mutate buffer with reference count greater than 1. - return Dictionary(_immutableCocoaDictionary: native.bridged()) - case .cocoa(let cocoa): - return Dictionary(_immutableCocoaDictionary: cocoa.object) + guard source._variant.isNative else { + return Dictionary( + _immutableCocoaDictionary: source._variant.asCocoa.object) } + // Note: it is safe to treat the buffer as immutable here because + // Dictionary will not mutate buffer with reference count greater than 1. + return Dictionary( + _immutableCocoaDictionary: source._variant.asNative.bridged()) } #endif return _dictionaryDownCastConditional(source)! diff --git a/stdlib/public/core/DictionaryVariant.swift b/stdlib/public/core/DictionaryVariant.swift index ee5ea7d82123a..8f3d0f67dac0c 100644 --- a/stdlib/public/core/DictionaryVariant.swift +++ b/stdlib/public/core/DictionaryVariant.swift @@ -60,21 +60,31 @@ extension Dictionary._Variant { @inlinable internal mutating func isUniquelyReferenced() -> Bool { +#if _runtime(_ObjC) + guard isNative else { + // Don't consider Cocoa a buffer mutable, even if it is mutable and it is + // uniquely referenced. + return false + } +#endif + // Note that &self drills down through .native(_NativeDictionary) to the + // first property in _NativeDictionary, which is the reference to the + // storage. + return _isUnique_native(&self) + } + +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var isNative: Bool { switch self { case .native: - // Note that &self drills down through .native(_NativeDictionary) to the - // first property in _NativeDictionary, which is the reference to the - // storage. - return _isUnique_native(&self) -#if _runtime(_ObjC) + return true case .cocoa: cocoaPath() - // Don't consider Cocoa buffer mutable, even if it is mutable and is - // uniquely referenced. return false -#endif } } +#endif @inlinable internal var asNative: _NativeDictionary { @@ -110,17 +120,16 @@ extension Dictionary._Variant { /// Reserves enough space for the specified number of elements to be stored /// without reallocating additional storage. internal mutating func reserveCapacity(_ capacity: Int) { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - asNative.reserveCapacity(capacity, isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { + let cocoa = asCocoa let capacity = Swift.max(cocoa.count, capacity) self = .native(_NativeDictionary(cocoa, capacity: capacity)) -#endif + return } +#endif + let isUnique = isUniquelyReferenced() + asNative.reserveCapacity(capacity, isUnique: isUnique) } /// The number of elements that can be stored without expanding the current @@ -132,15 +141,12 @@ extension Dictionary._Variant { /// at which adding any more elements will exceed the load factor. @inlinable internal var capacity: Int { - switch self { - case .native: - return asNative.capacity #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.count -#endif + guard isNative else { + return asCocoa.count } +#endif + return asNative.capacity } } @@ -152,41 +158,32 @@ extension Dictionary._Variant: _DictionaryBuffer { @inlinable internal var startIndex: Index { - switch self { - case .native(let native): - return native.startIndex #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Index(_cocoa: cocoa.startIndex) -#endif + guard isNative else { + return Index(_cocoa: asCocoa.startIndex) } +#endif + return asNative.startIndex } @inlinable internal var endIndex: Index { - switch self { - case .native(let native): - return native.endIndex #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Index(_cocoa: cocoa.endIndex) -#endif + guard isNative else { + return Index(_cocoa: asCocoa.endIndex) } +#endif + return asNative.endIndex } @inlinable internal func index(after index: Index) -> Index { - switch self { - case .native(let native): - return native.index(after: index) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Index(_cocoa: cocoa.index(after: index._asCocoa)) -#endif + guard isNative else { + return Index(_cocoa: asCocoa.index(after: index._asCocoa)) } +#endif + return asNative.index(after: index) } @inlinable @@ -206,111 +203,90 @@ extension Dictionary._Variant: _DictionaryBuffer { @inlinable @inline(__always) internal func index(forKey key: Key) -> Index? { - switch self { - case .native(let native): - return native.index(forKey: key) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { let cocoaKey = _bridgeAnythingToObjectiveC(key) - guard let index = cocoa.index(forKey: cocoaKey) else { return nil } + guard let index = asCocoa.index(forKey: cocoaKey) else { return nil } return Index(_cocoa: index) -#endif } +#endif + return asNative.index(forKey: key) } @inlinable internal var count: Int { @inline(__always) get { - switch self { - case .native(let native): - return native.count #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.count -#endif + guard isNative else { + return asCocoa.count } +#endif + return asNative.count } } @inlinable @inline(__always) func contains(_ key: Key) -> Bool { - switch self { - case .native(let native): - return native.contains(key) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { let cocoaKey = _bridgeAnythingToObjectiveC(key) - return cocoa.contains(cocoaKey) -#endif + return asCocoa.contains(cocoaKey) } +#endif + return asNative.contains(key) } @inlinable @inline(__always) func lookup(_ key: Key) -> Value? { - switch self { - case .native(let native): - return native.lookup(key) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { let cocoaKey = _bridgeAnythingToObjectiveC(key) - guard let cocoaValue = cocoa.lookup(cocoaKey) else { return nil } + guard let cocoaValue = asCocoa.lookup(cocoaKey) else { return nil } return _forceBridgeFromObjectiveC(cocoaValue, Value.self) -#endif } +#endif + return asNative.lookup(key) } @inlinable @inline(__always) func lookup(_ index: Index) -> (key: Key, value: Value) { - switch self { - case .native(let native): - return native.lookup(index) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let (cocoaKey, cocoaValue) = cocoa.lookup(index._asCocoa) + guard isNative else { + let (cocoaKey, cocoaValue) = asCocoa.lookup(index._asCocoa) let nativeKey = _forceBridgeFromObjectiveC(cocoaKey, Key.self) let nativeValue = _forceBridgeFromObjectiveC(cocoaValue, Value.self) return (nativeKey, nativeValue) -#endif } +#endif + return asNative.lookup(index) } @inlinable @inline(__always) func key(at index: Index) -> Key { - switch self { - case .native(let native): - return native.key(at: index) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaKey = cocoa.key(at: index._asCocoa) + guard isNative else { + let cocoaKey = asCocoa.key(at: index._asCocoa) return _forceBridgeFromObjectiveC(cocoaKey, Key.self) -#endif } +#endif + return asNative.key(at: index) } @inlinable @inline(__always) func value(at index: Index) -> Value { - switch self { - case .native(let native): - return native.value(at: index) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaValue = cocoa.value(at: index._asCocoa) + guard isNative else { + let cocoaValue = asCocoa.value(at: index._asCocoa) return _forceBridgeFromObjectiveC(cocoaValue, Value.self) -#endif } +#endif + return asNative.value(at: index) } } @@ -325,29 +301,27 @@ extension Dictionary._Variant { internal mutating func mutatingFind( _ key: Key ) -> (bucket: _NativeDictionary.Bucket, found: Bool) { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - return asNative.mutatingFind(key, isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { + let cocoa = asCocoa var native = _NativeDictionary( cocoa, capacity: cocoa.count + 1) let result = native.mutatingFind(key, isUnique: true) self = .native(native) return result -#endif } +#endif + let isUnique = isUniquelyReferenced() + return asNative.mutatingFind(key, isUnique: isUnique) } @inlinable @inline(__always) internal mutating func ensureUniqueNative() -> _NativeDictionary { #if _runtime(_ObjC) - if case .cocoa(let cocoa) = self { + guard isNative else { cocoaPath() - let native = _NativeDictionary(cocoa) + let native = _NativeDictionary(asCocoa) self = .native(native) return native } @@ -364,41 +338,35 @@ extension Dictionary._Variant { _ value: __owned Value, forKey key: Key ) -> Value? { - switch self { - case .native: - let isUnique = self.isUniquelyReferenced() - return asNative.updateValue(value, forKey: key, isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { // Make sure we have space for an extra element. + let cocoa = asCocoa var native = _NativeDictionary( cocoa, capacity: cocoa.count + 1) let result = native.updateValue(value, forKey: key, isUnique: true) self = .native(native) return result -#endif } +#endif + let isUnique = self.isUniquelyReferenced() + return asNative.updateValue(value, forKey: key, isUnique: isUnique) } @inlinable internal mutating func setValue(_ value: __owned Value, forKey key: Key) { - switch self { - case .native: - let isUnique = self.isUniquelyReferenced() - asNative.setValue(value, forKey: key, isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + if !isNative { // Make sure we have space for an extra element. - var native = _NativeDictionary( + let cocoa = asCocoa + self = .native(_NativeDictionary( cocoa, - capacity: cocoa.count + 1) - native.setValue(value, forKey: key, isUnique: true) - self = .native(native) -#endif + capacity: cocoa.count + 1)) } +#endif + let isUnique = self.isUniquelyReferenced() + asNative.setValue(value, forKey: key, isUnique: isUnique) } @inlinable @@ -412,16 +380,10 @@ extension Dictionary._Variant { @inlinable internal mutating func removeValue(forKey key: Key) -> Value? { - switch self { - case .native: - let (bucket, found) = asNative.find(key) - guard found else { return nil } - let isUnique = isUniquelyReferenced() - return asNative.uncheckedRemove(at: bucket, isUnique: isUnique).value #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { let cocoaKey = _bridgeAnythingToObjectiveC(key) + let cocoa = asCocoa guard cocoa.lookup(cocoaKey) != nil else { return nil } var native = _NativeDictionary(cocoa) let (bucket, found) = native.find(key) @@ -429,8 +391,12 @@ extension Dictionary._Variant { let old = native.uncheckedRemove(at: bucket, isUnique: true).value self = .native(native) return old -#endif } +#endif + let (bucket, found) = asNative.find(key) + guard found else { return nil } + let isUnique = isUniquelyReferenced() + return asNative.uncheckedRemove(at: bucket, isUnique: isUnique).value } @inlinable @@ -441,16 +407,14 @@ extension Dictionary._Variant { } guard count > 0 else { return } - switch self { - case .native: - let isUnique = isUniquelyReferenced() - asNative.removeAll(isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - self = .native(_NativeDictionary(capacity: cocoa.count)) -#endif + guard isNative else { + self = .native(_NativeDictionary(capacity: asCocoa.count)) + return } +#endif + let isUnique = isUniquelyReferenced() + asNative.removeAll(isUnique: isUnique) } } @@ -461,15 +425,12 @@ extension Dictionary._Variant { @inlinable @inline(__always) __consuming internal func makeIterator() -> Dictionary.Iterator { - switch self { - case .native(let native): - return Dictionary.Iterator(_native: native.makeIterator()) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Dictionary.Iterator(_cocoa: cocoa.makeIterator()) -#endif + guard isNative else { + return Dictionary.Iterator(_cocoa: asCocoa.makeIterator()) } +#endif + return Dictionary.Iterator(_native: asNative.makeIterator()) } } @@ -478,15 +439,12 @@ extension Dictionary._Variant { internal func mapValues( _ transform: (Value) throws -> T ) rethrows -> _NativeDictionary { - switch self { - case .native(let native): - return try native.mapValues(transform) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return try cocoa.mapValues(transform) -#endif + guard isNative else { + return try asCocoa.mapValues(transform) } +#endif + return try asNative.mapValues(transform) } @inlinable @@ -494,24 +452,22 @@ extension Dictionary._Variant { _ keysAndValues: __owned S, uniquingKeysWith combine: (Value, Value) throws -> Value ) rethrows where S.Element == (Key, Value) { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - try asNative.merge( - keysAndValues, - isUnique: isUnique, - uniquingKeysWith: combine) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - var native = _NativeDictionary(cocoa) + guard isNative else { + var native = _NativeDictionary(asCocoa) try native.merge( keysAndValues, isUnique: true, uniquingKeysWith: combine) self = .native(native) -#endif + return } +#endif + let isUnique = isUniquelyReferenced() + try asNative.merge( + keysAndValues, + isUnique: isUnique, + uniquingKeysWith: combine) } } diff --git a/stdlib/public/core/NativeDictionary.swift b/stdlib/public/core/NativeDictionary.swift index 502ba1f1133ce..6a93fd307185a 100644 --- a/stdlib/public/core/NativeDictionary.swift +++ b/stdlib/public/core/NativeDictionary.swift @@ -275,15 +275,13 @@ extension _NativeDictionary { @inlinable @inline(__always) func validatedBucket(for index: Dictionary.Index) -> Bucket { - switch index._variant { - case .native(let native): - return validatedBucket(for: native) #if _runtime(_ObjC) - case .cocoa(let cocoa): + guard index._isNative else { index._cocoaPath() // Accept Cocoa indices as long as they contain a key that exists in this // dictionary, and the address of their Cocoa object generates the same // age. + let cocoa = index._asCocoa if cocoa.age == self.age { let key = _forceBridgeFromObjectiveC(cocoa.key, Key.self) let (bucket, found) = find(key) @@ -293,8 +291,9 @@ extension _NativeDictionary { } _preconditionFailure( "Attempting to access Dictionary elements using an invalid index") -#endif } +#endif + return validatedBucket(for: index._asNative) } } diff --git a/stdlib/public/core/NativeSet.swift b/stdlib/public/core/NativeSet.swift index 636dc2402a550..4fe98bb486a05 100644 --- a/stdlib/public/core/NativeSet.swift +++ b/stdlib/public/core/NativeSet.swift @@ -241,12 +241,10 @@ extension _NativeSet { @inlinable @inline(__always) func validatedBucket(for index: Set.Index) -> Bucket { - switch index._variant { - case .native(let native): - return validatedBucket(for: native) #if _runtime(_ObjC) - case .cocoa(let cocoa): + guard index._isNative else { index._cocoaPath() + let cocoa = index._asCocoa // Accept Cocoa indices as long as they contain an element that exists in // this set, and the address of their Cocoa object generates the same age. if cocoa.age == self.age { @@ -258,8 +256,9 @@ extension _NativeSet { } _preconditionFailure( "Attempting to access Set elements using an invalid index") -#endif } +#endif + return validatedBucket(for: index._asNative) } } diff --git a/stdlib/public/core/Set.swift b/stdlib/public/core/Set.swift index 2dadaabf386a7..dba65a482cd7f 100644 --- a/stdlib/public/core/Set.swift +++ b/stdlib/public/core/Set.swift @@ -1358,6 +1358,19 @@ extension Set.Index { } #endif +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var _isNative: Bool { + switch _variant { + case .native: + return true + case .cocoa: + _cocoaPath() + return false + } + } +#endif + @usableFromInline @_transparent internal var _asNative: _HashTable.Index { switch _variant { @@ -1446,19 +1459,17 @@ extension Set.Index: Hashable { /// of this instance. public // FIXME(cocoa-index): Make inlinable func hash(into hasher: inout Hasher) { - #if _runtime(_ObjC) - switch _variant { - case .native(let nativeIndex): - hasher.combine(0 as UInt8) - hasher.combine(nativeIndex.bucket.offset) - case .cocoa(let cocoaIndex): - _cocoaPath() +#if _runtime(_ObjC) + guard _isNative else { hasher.combine(1 as UInt8) - hasher.combine(cocoaIndex.storage.currentKeyIndex) + hasher.combine(_asCocoa.storage.currentKeyIndex) + return } - #else + hasher.combine(0 as UInt8) hasher.combine(_asNative.bucket.offset) - #endif +#else + hasher.combine(_asNative.bucket.offset) +#endif } } @@ -1523,6 +1534,19 @@ extension Set.Iterator { } #endif +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var _isNative: Bool { + switch _variant { + case .native: + return true + case .cocoa: + _cocoaPath() + return false + } + } +#endif + @usableFromInline @_transparent internal var _asNative: _NativeSet.Iterator { get { @@ -1539,6 +1563,20 @@ extension Set.Iterator { self._variant = .native(newValue) } } + +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var _asCocoa: _CocoaSet.Iterator { + get { + switch _variant { + case .native: + _sanityCheckFailure("internal error: does not contain a Cocoa index") + case .cocoa(let cocoa): + return cocoa + } + } + } +#endif } extension Set.Iterator: IteratorProtocol { @@ -1550,19 +1588,12 @@ extension Set.Iterator: IteratorProtocol { @inline(__always) public mutating func next() -> Element? { #if _runtime(_ObjC) - switch _variant { - case .native: - return _asNative.next() - case .cocoa(let cocoaIterator): - _cocoaPath() - if let cocoaElement = cocoaIterator.next() { - return _forceBridgeFromObjectiveC(cocoaElement, Element.self) - } - return nil + guard _isNative else { + guard let cocoaElement = _asCocoa.next() else { return nil } + return _forceBridgeFromObjectiveC(cocoaElement, Element.self) } -#else - return _asNative.next() #endif + return _asNative.next() } } diff --git a/stdlib/public/core/SetBridging.swift b/stdlib/public/core/SetBridging.swift index 06cdef01d569b..d793b971abdba 100644 --- a/stdlib/public/core/SetBridging.swift +++ b/stdlib/public/core/SetBridging.swift @@ -619,12 +619,10 @@ extension _CocoaSet.Iterator: IteratorProtocol { extension Set { @inlinable public __consuming func _bridgeToObjectiveCImpl() -> _NSSetCore { - switch _variant { - case .native(let nativeSet): - return nativeSet.bridged() - case .cocoa(let cocoaSet): - return cocoaSet.object + guard _variant.isNative else { + return _variant.asCocoa.object } + return _variant.asNative.bridged() } /// Returns the native Dictionary hidden inside this NSDictionary; diff --git a/stdlib/public/core/SetCasting.swift b/stdlib/public/core/SetCasting.swift index 787c51a420717..6562738c228b0 100644 --- a/stdlib/public/core/SetCasting.swift +++ b/stdlib/public/core/SetCasting.swift @@ -48,12 +48,10 @@ public func _setDownCast(_ source: Set) #if _runtime(_ObjC) if _isClassOrObjCExistential(BaseValue.self) && _isClassOrObjCExistential(DerivedValue.self) { - switch source._variant { - case .native(let nativeSet): - return Set(_immutableCocoaSet: nativeSet.bridged()) - case .cocoa(let cocoaSet): - return Set(_immutableCocoaSet: cocoaSet.object) + guard source._variant.isNative else { + return Set(_immutableCocoaSet: source._variant.asCocoa.object) } + return Set(_immutableCocoaSet: source._variant.asNative.bridged()) } #endif return _setDownCastConditional(source)! diff --git a/stdlib/public/core/SetVariant.swift b/stdlib/public/core/SetVariant.swift index 33ce0d85cd88b..2ee712554c9f7 100644 --- a/stdlib/public/core/SetVariant.swift +++ b/stdlib/public/core/SetVariant.swift @@ -58,20 +58,30 @@ extension Set._Variant { @inlinable internal mutating func isUniquelyReferenced() -> Bool { +#if _runtime(_ObjC) + guard isNative else { + // Don't consider Cocoa buffer mutable, even if it is mutable and is + // uniquely referenced. + return false + } +#endif // Note that &self drills down through .native(_NativeSet) to the first // property in _NativeSet, which is the reference to the storage. + return _isUnique_native(&self) + } + +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var isNative: Bool { switch self { case .native: - return _isUnique_native(&self) -#if _runtime(_ObjC) + return true case .cocoa: cocoaPath() - // Don't consider Cocoa buffer mutable, even if it is mutable and is - // uniquely referenced. return false -#endif } } +#endif @usableFromInline @_transparent internal var asNative: _NativeSet { @@ -105,17 +115,16 @@ extension Set._Variant { /// Reserves enough space for the specified number of elements to be stored /// without reallocating additional storage. internal mutating func reserveCapacity(_ capacity: Int) { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - asNative.reserveCapacity(capacity, isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { + let cocoa = asCocoa let capacity = Swift.max(cocoa.count, capacity) self = .native(_NativeSet(cocoa, capacity: capacity)) -#endif + return } +#endif + let isUnique = isUniquelyReferenced() + asNative.reserveCapacity(capacity, isUnique: isUnique) } /// The number of elements that can be stored without expanding the current @@ -127,15 +136,12 @@ extension Set._Variant { /// at which adding any more elements will exceed the load factor. @inlinable internal var capacity: Int { - switch self { - case .native: - return asNative.capacity #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.count -#endif + guard isNative else { + return asCocoa.count } +#endif + return asNative.capacity } } @@ -145,41 +151,32 @@ extension Set._Variant: _SetBuffer { @inlinable internal var startIndex: Index { - switch self { - case .native(let native): - return native.startIndex #if _runtime(_ObjC) - case .cocoa(let cocoaSet): - cocoaPath() - return Index(_cocoa: cocoaSet.startIndex) -#endif + guard isNative else { + return Index(_cocoa: asCocoa.startIndex) } +#endif + return asNative.startIndex } @inlinable internal var endIndex: Index { - switch self { - case .native(let native): - return native.endIndex #if _runtime(_ObjC) - case .cocoa(let cocoaSet): - cocoaPath() - return Index(_cocoa: cocoaSet.endIndex) -#endif + guard isNative else { + return Index(_cocoa: asCocoa.endIndex) } +#endif + return asNative.endIndex } @inlinable internal func index(after index: Index) -> Index { - switch self { - case .native(let native): - return native.index(after: index) #if _runtime(_ObjC) - case .cocoa(let cocoaSet): - cocoaPath() - return Index(_cocoa: cocoaSet.index(after: index._asCocoa)) -#endif + guard isNative else { + return Index(_cocoa: asCocoa.index(after: index._asCocoa)) } +#endif + return asNative.index(after: index) } @inlinable @@ -199,102 +196,78 @@ extension Set._Variant: _SetBuffer { @inlinable @inline(__always) internal func index(for element: Element) -> Index? { - switch self { - case .native(let native): - return native.index(for: element) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { let cocoaElement = _bridgeAnythingToObjectiveC(element) - guard let index = cocoa.index(for: cocoaElement) else { return nil } + guard let index = asCocoa.index(for: cocoaElement) else { return nil } return Index(_cocoa: index) -#endif } +#endif + return asNative.index(for: element) } @inlinable internal var count: Int { @inline(__always) get { - switch self { - case .native(let native): - return native.count #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.count -#endif + guard isNative else { + return asCocoa.count } +#endif + return asNative.count } } @inlinable @inline(__always) internal func contains(_ member: Element) -> Bool { - switch self { - case .native(let native): - return native.contains(member) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.contains(_bridgeAnythingToObjectiveC(member)) -#endif + guard isNative else { + return asCocoa.contains(_bridgeAnythingToObjectiveC(member)) } +#endif + return asNative.contains(member) } @inlinable @inline(__always) internal func element(at index: Index) -> Element { - switch self { - case .native(let native): - return native.element(at: index) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaMember = cocoa.element(at: index._asCocoa) + guard isNative else { + let cocoaMember = asCocoa.element(at: index._asCocoa) return _forceBridgeFromObjectiveC(cocoaMember, Element.self) -#endif } +#endif + return asNative.element(at: index) } } extension Set._Variant { @inlinable internal mutating func update(with value: __owned Element) -> Element? { - switch self { - case .native: - let isUnique = self.isUniquelyReferenced() - return asNative.update(with: value, isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { // Make sure we have space for an extra element. - var native = _NativeSet(cocoa, capacity: cocoa.count + 1) + var native = _NativeSet(asCocoa, capacity: asCocoa.count + 1) let old = native.update(with: value, isUnique: true) self = .native(native) return old -#endif } +#endif + let isUnique = self.isUniquelyReferenced() + return asNative.update(with: value, isUnique: isUnique) } @inlinable internal mutating func insert( _ element: __owned Element ) -> (inserted: Bool, memberAfterInsert: Element) { - switch self { - case .native: - let (bucket, found) = asNative.find(element) - if found { - return (false, asNative.uncheckedElement(at: bucket)) - } - let isUnique = self.isUniquelyReferenced() - asNative.insertNew(element, at: bucket, isUnique: isUnique) - return (true, element) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { // Make sure we have space for an extra element. let cocoaMember = _bridgeAnythingToObjectiveC(element) + let cocoa = asCocoa if let m = cocoa.member(for: cocoaMember) { return (false, _forceBridgeFromObjectiveC(m, Element.self)) } @@ -302,47 +275,50 @@ extension Set._Variant { native.insertNew(element, isUnique: true) self = .native(native) return (true, element) + } #endif + let (bucket, found) = asNative.find(element) + if found { + return (false, asNative.uncheckedElement(at: bucket)) } + let isUnique = self.isUniquelyReferenced() + asNative.insertNew(element, at: bucket, isUnique: isUnique) + return (true, element) } @inlinable @discardableResult internal mutating func remove(at index: Index) -> Element { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - let bucket = asNative.validatedBucket(for: index) - return asNative.uncheckedRemove(at: bucket, isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { // We have to migrate the data first. But after we do so, the Cocoa // index becomes useless, so get the element first. + let cocoa = asCocoa let cocoaMember = cocoa.member(for: index._asCocoa) let nativeMember = _forceBridgeFromObjectiveC(cocoaMember, Element.self) return _migrateToNative(cocoa, removing: nativeMember) -#endif } +#endif + let isUnique = isUniquelyReferenced() + let bucket = asNative.validatedBucket(for: index) + return asNative.uncheckedRemove(at: bucket, isUnique: isUnique) } @inlinable @discardableResult internal mutating func remove(_ member: Element) -> Element? { - switch self { - case .native: - let (bucket, found) = asNative.find(member) - guard found else { return nil } - let isUnique = isUniquelyReferenced() - return asNative.uncheckedRemove(at: bucket, isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { + let cocoa = asCocoa let cocoaMember = _bridgeAnythingToObjectiveC(member) guard cocoa.contains(cocoaMember) else { return nil } return _migrateToNative(cocoa, removing: member) -#endif } +#endif + let (bucket, found) = asNative.find(member) + guard found else { return nil } + let isUnique = isUniquelyReferenced() + return asNative.uncheckedRemove(at: bucket, isUnique: isUnique) } #if _runtime(_ObjC) @@ -371,16 +347,14 @@ extension Set._Variant { } guard count > 0 else { return } - switch self { - case .native: - let isUnique = isUniquelyReferenced() - asNative.removeAll(isUnique: isUnique) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - self = .native(_NativeSet(capacity: cocoa.count)) -#endif + guard isNative else { + self = .native(_NativeSet(capacity: asCocoa.count)) + return } +#endif + let isUnique = isUniquelyReferenced() + asNative.removeAll(isUnique: isUnique) } } @@ -391,15 +365,12 @@ extension Set._Variant { @inlinable @inline(__always) internal __consuming func makeIterator() -> Set.Iterator { - switch self { - case .native(let native): - return Set.Iterator(_native: native.makeIterator()) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Set.Iterator(_cocoa: cocoa.makeIterator()) -#endif + guard isNative else { + return Set.Iterator(_cocoa: asCocoa.makeIterator()) } +#endif + return Set.Iterator(_native: asNative.makeIterator()) } } From a6611e7fb7a2abe752b6f328107d461996c7e244 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 3 Oct 2018 17:35:34 +0100 Subject: [PATCH 3/4] [stdlib] Set, Dictionary: Switch to using a _BridgeObject instead of an enum --- stdlib/public/core/Dictionary.swift | 76 ++++++++------- stdlib/public/core/DictionaryVariant.swift | 104 +++++++-------------- stdlib/public/core/Set.swift | 54 +++++------ stdlib/public/core/SetVariant.swift | 93 +++++++----------- 4 files changed, 133 insertions(+), 194 deletions(-) diff --git a/stdlib/public/core/Dictionary.swift b/stdlib/public/core/Dictionary.swift index df816e40ed1fe..5ecd64d2295c7 100644 --- a/stdlib/public/core/Dictionary.swift +++ b/stdlib/public/core/Dictionary.swift @@ -396,13 +396,13 @@ public struct Dictionary { @inlinable internal init(_native: __owned _NativeDictionary) { - _variant = .native(_native) + _variant = _Variant(native: _native) } #if _runtime(_ObjC) @inlinable internal init(_cocoa: __owned _CocoaDictionary) { - _variant = .cocoa(_cocoa) + _variant = _Variant(cocoa: _cocoa) } /// Private initializer used for bridging. @@ -444,7 +444,7 @@ public struct Dictionary { /// reallocating its storage buffer. public // FIXME(reserveCapacity): Should be inlinable init(minimumCapacity: Int) { - _variant = .native(_NativeDictionary(capacity: minimumCapacity)) + _variant = _Variant(native: _NativeDictionary(capacity: minimumCapacity)) } /// Creates a new dictionary from the key-value pairs in the given sequence. @@ -1317,7 +1317,7 @@ extension Dictionary { } _modify { var values = Values(_dictionary: self) - _variant = .native(_NativeDictionary()) + _variant = _Variant(native: _NativeDictionary()) yield &values self._variant = values._variant } @@ -1401,10 +1401,22 @@ extension Dictionary { @inlinable public static func ==(lhs: Keys, rhs: Keys) -> Bool { // Equal if the two dictionaries share storage. - if case (.native(let ln), .native(let rn)) = (lhs._variant, rhs._variant), - ln._storage === rn._storage { + if + lhs._variant.isNative, + rhs._variant.isNative, + lhs._variant.asNative._storage === rhs._variant.asNative._storage + { return true } +#if _runtime(_ObjC) + if + !lhs._variant.isNative, + !rhs._variant.isNative, + lhs._variant.asCocoa.object === rhs._variant.asCocoa.object + { + return true + } +#endif // Not equal if the dictionaries are different sizes. if lhs.count != rhs.count { @@ -1516,7 +1528,7 @@ extension Dictionary { guard i != j else { return } #if _runtime(_ObjC) if !_variant.isNative { - _variant = .native(_NativeDictionary(_variant.asCocoa)) + _variant = .init(native: _NativeDictionary(_variant.asCocoa)) } #endif let native = _variant.asNative @@ -1591,48 +1603,44 @@ extension Dictionary.Values { extension Dictionary: Equatable where Value: Equatable { @inlinable public static func == (lhs: [Key: Value], rhs: [Key: Value]) -> Bool { - switch (lhs._variant, rhs._variant) { - case (.native(let lhsNative), .native(let rhsNative)): + switch (lhs._variant.isNative, rhs._variant.isNative) { + case (true, true): + let lhs = lhs._variant.asNative + let rhs = rhs._variant.asNative - if lhsNative._storage === rhsNative._storage { - return true - } - - if lhsNative.count != rhsNative.count { - return false - } + if lhs._storage === rhs._storage { return true } + if lhs.count != rhs.count { return false } for (k, v) in lhs { - let (bucket, found) = rhsNative.find(k) - guard found, rhsNative.uncheckedValue(at: bucket) == v else { - return false - } + let (bucket, found) = rhs.find(k) + guard found, rhs.uncheckedValue(at: bucket) == v else { return false } } return true - #if _runtime(_ObjC) - case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): - return lhsCocoa == rhsCocoa +#if _runtime(_ObjC) + case (false, false): + return lhs._variant.asCocoa == rhs._variant.asCocoa - case (.native(let lhsNative), .cocoa(let rhsCocoa)): - if lhsNative.count != rhsCocoa.count { - return false - } + case (true, false): + let lhs = lhs._variant.asNative + let rhs = rhs._variant.asCocoa + + if lhs.count != rhs.count { return false } - defer { _fixLifetime(lhsNative) } - for bucket in lhsNative.hashTable { - let key = lhsNative.uncheckedKey(at: bucket) - let value = lhsNative.uncheckedValue(at: bucket) + defer { _fixLifetime(lhs) } + for bucket in lhs.hashTable { + let key = lhs.uncheckedKey(at: bucket) + let value = lhs.uncheckedValue(at: bucket) guard - let rhsValue = rhsCocoa.lookup(_bridgeAnythingToObjectiveC(key)), - value == _forceBridgeFromObjectiveC(rhsValue, Value.self) + let cocoaValue = rhs.lookup(_bridgeAnythingToObjectiveC(key)), + value == _forceBridgeFromObjectiveC(cocoaValue, Value.self) else { return false } } return true - case (.cocoa, .native): + case (false, true): return rhs == lhs #endif } diff --git a/stdlib/public/core/DictionaryVariant.swift b/stdlib/public/core/DictionaryVariant.swift index 8f3d0f67dac0c..a0bd30849b685 100644 --- a/stdlib/public/core/DictionaryVariant.swift +++ b/stdlib/public/core/DictionaryVariant.swift @@ -32,12 +32,22 @@ internal protocol _DictionaryBuffer { extension Dictionary { @usableFromInline - @_frozen - internal enum _Variant { - case native(_NativeDictionary) -#if _runtime(_ObjC) - case cocoa(_CocoaDictionary) -#endif + @_fixed_layout + internal struct _Variant { + @usableFromInline + internal var object: _BridgeStorage<_RawDictionaryStorage, _NSDictionary> + + @inlinable + @inline(__always) + init(native: __owned _NativeDictionary) { + self.object = _BridgeStorage(native: native._storage) + } + + @inlinable + @inline(__always) + init(cocoa: __owned _CocoaDictionary) { + self.object = _BridgeStorage(objC: cocoa.object) + } } } @@ -47,73 +57,34 @@ extension Dictionary._Variant { internal var guaranteedNative: Bool { return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0 } - - // Allow the optimizer to consider the surrounding code unreachable if Element - // is guaranteed to be native. - @usableFromInline @_transparent - internal func cocoaPath() { - if guaranteedNative { - _conditionallyUnreachable() - } - } #endif @inlinable internal mutating func isUniquelyReferenced() -> Bool { -#if _runtime(_ObjC) - guard isNative else { - // Don't consider Cocoa a buffer mutable, even if it is mutable and it is - // uniquely referenced. - return false - } -#endif - // Note that &self drills down through .native(_NativeDictionary) to the - // first property in _NativeDictionary, which is the reference to the - // storage. - return _isUnique_native(&self) + return object.isUniquelyReferencedNative() } #if _runtime(_ObjC) @usableFromInline @_transparent internal var isNative: Bool { - switch self { - case .native: - return true - case .cocoa: - cocoaPath() - return false - } + return guaranteedNative || object.isNative } #endif - @inlinable + @usableFromInline @_transparent internal var asNative: _NativeDictionary { - @inline(__always) get { - switch self { - case .native(let native): - return native -#if _runtime(_ObjC) - case .cocoa: - _sanityCheckFailure("internal error: not backed by native buffer") -#endif - } + return _NativeDictionary(object.nativeInstance) } - @inline(__always) set { - self = .native(newValue) + self = .init(native: newValue) } } #if _runtime(_ObjC) @inlinable internal var asCocoa: _CocoaDictionary { - switch self { - case .native: - _sanityCheckFailure("internal error: not backed by NSDictionary") - case .cocoa(let cocoa): - return cocoa - } + return _CocoaDictionary(object.objCInstance) } #endif @@ -124,7 +95,7 @@ extension Dictionary._Variant { guard isNative else { let cocoa = asCocoa let capacity = Swift.max(cocoa.count, capacity) - self = .native(_NativeDictionary(cocoa, capacity: capacity)) + self = .init(native: _NativeDictionary(cocoa, capacity: capacity)) return } #endif @@ -188,16 +159,14 @@ extension Dictionary._Variant: _DictionaryBuffer { @inlinable internal func formIndex(after index: inout Index) { - switch self { - case .native(let native): - index = native.index(after: index) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { let isUnique = index._isUniquelyReferenced() - cocoa.formIndex(after: &index._asCocoa, isUnique: isUnique) -#endif + asCocoa.formIndex(after: &index._asCocoa, isUnique: isUnique) + return } +#endif + index = asNative.index(after: index) } @inlinable @@ -307,7 +276,7 @@ extension Dictionary._Variant { var native = _NativeDictionary( cocoa, capacity: cocoa.count + 1) let result = native.mutatingFind(key, isUnique: true) - self = .native(native) + self = .init(native: native) return result } #endif @@ -320,9 +289,8 @@ extension Dictionary._Variant { internal mutating func ensureUniqueNative() -> _NativeDictionary { #if _runtime(_ObjC) guard isNative else { - cocoaPath() let native = _NativeDictionary(asCocoa) - self = .native(native) + self = .init(native: native) return native } #endif @@ -346,7 +314,7 @@ extension Dictionary._Variant { cocoa, capacity: cocoa.count + 1) let result = native.updateValue(value, forKey: key, isUnique: true) - self = .native(native) + self = .init(native: native) return result } #endif @@ -360,7 +328,7 @@ extension Dictionary._Variant { if !isNative { // Make sure we have space for an extra element. let cocoa = asCocoa - self = .native(_NativeDictionary( + self = .init(native: _NativeDictionary( cocoa, capacity: cocoa.count + 1)) } @@ -389,7 +357,7 @@ extension Dictionary._Variant { let (bucket, found) = native.find(key) _precondition(found, "Bridging did not preserve equality") let old = native.uncheckedRemove(at: bucket, isUnique: true).value - self = .native(native) + self = .init(native: native) return old } #endif @@ -402,14 +370,14 @@ extension Dictionary._Variant { @inlinable internal mutating func removeAll(keepingCapacity keepCapacity: Bool) { if !keepCapacity { - self = .native(_NativeDictionary()) + self = .init(native: _NativeDictionary()) return } guard count > 0 else { return } #if _runtime(_ObjC) guard isNative else { - self = .native(_NativeDictionary(capacity: asCocoa.count)) + self = .init(native: _NativeDictionary(capacity: asCocoa.count)) return } #endif @@ -459,7 +427,7 @@ extension Dictionary._Variant { keysAndValues, isUnique: true, uniquingKeysWith: combine) - self = .native(native) + self = .init(native: native) return } #endif diff --git a/stdlib/public/core/Set.swift b/stdlib/public/core/Set.swift index dba65a482cd7f..6004d994d2122 100644 --- a/stdlib/public/core/Set.swift +++ b/stdlib/public/core/Set.swift @@ -163,19 +163,19 @@ public struct Set { /// storage buffer. public // FIXME(reserveCapacity): Should be inlinable init(minimumCapacity: Int) { - _variant = .native(_NativeSet(capacity: minimumCapacity)) + _variant = _Variant(native: _NativeSet(capacity: minimumCapacity)) } /// Private initializer. @inlinable internal init(_native: __owned _NativeSet) { - _variant = .native(_native) + _variant = _Variant(native: _native) } #if _runtime(_ObjC) @inlinable internal init(_cocoa: __owned _CocoaSet) { - _variant = .cocoa(_cocoa) + _variant = _Variant(cocoa: _cocoa) } /// Private initializer used for bridging. @@ -418,45 +418,37 @@ extension Set: Equatable { /// `false`. @inlinable public static func == (lhs: Set, rhs: Set) -> Bool { - switch (lhs._variant, rhs._variant) { - case (.native(let lhsNative), .native(let rhsNative)): + switch (lhs._variant.isNative, rhs._variant.isNative) { + case (true, true): + let lhs = lhs._variant.asNative + let rhs = rhs._variant.asNative - if lhsNative._storage === rhsNative._storage { - return true - } + if lhs._storage === rhs._storage { return true } + if lhs.count != rhs.count { return false } - if lhsNative.count != rhsNative.count { - return false - } - - for member in lhsNative { - guard rhsNative.find(member).found else { - return false - } + for member in lhs { + guard rhs.find(member).found else { return false } } return true +#if _runtime(_ObjC) + case (false, false): + return lhs._variant.asCocoa == rhs._variant.asCocoa - #if _runtime(_ObjC) - case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): - return lhsCocoa == rhsCocoa + case (true, false): + let lhs = lhs._variant.asNative + let rhs = rhs._variant.asCocoa - case (.native(let lhsNative), .cocoa(let rhsCocoa)): - if lhsNative.count != rhsCocoa.count { - return false - } + if lhs.count != rhs.count { return false } - defer { _fixLifetime(lhsNative) } - for bucket in lhsNative.hashTable { - let key = lhsNative.uncheckedElement(at: bucket) + defer { _fixLifetime(lhs) } + for bucket in lhs.hashTable { + let key = lhs.uncheckedElement(at: bucket) let bridgedKey = _bridgeAnythingToObjectiveC(key) - if rhsCocoa.contains(bridgedKey) { - continue - } - return false + guard rhs.contains(bridgedKey) else { return false } } return true - case (.cocoa, .native): + case (false, true): return rhs == lhs #endif } diff --git a/stdlib/public/core/SetVariant.swift b/stdlib/public/core/SetVariant.swift index 2ee712554c9f7..643a52e3f7692 100644 --- a/stdlib/public/core/SetVariant.swift +++ b/stdlib/public/core/SetVariant.swift @@ -28,12 +28,22 @@ internal protocol _SetBuffer { extension Set { @usableFromInline - @_frozen - internal enum _Variant { - case native(_NativeSet) -#if _runtime(_ObjC) - case cocoa(_CocoaSet) -#endif + @_fixed_layout + internal struct _Variant { + @usableFromInline + internal var object: _BridgeStorage<_RawSetStorage, _NSSet> + + @inlinable + @inline(__always) + init(native: __owned _NativeSet) { + self.object = _BridgeStorage(native: native._storage) + } + + @inlinable + @inline(__always) + init(cocoa: __owned _CocoaSet) { + self.object = _BridgeStorage(objC: cocoa.object) + } } } @@ -44,71 +54,34 @@ extension Set._Variant { internal var guaranteedNative: Bool { return _canBeClass(Element.self) == 0 } - - /// Allow the optimizer to consider the surrounding code unreachable if - /// Set is guaranteed to be native. - @usableFromInline - @_transparent - internal func cocoaPath() { - if guaranteedNative { - _conditionallyUnreachable() - } - } #endif @inlinable internal mutating func isUniquelyReferenced() -> Bool { -#if _runtime(_ObjC) - guard isNative else { - // Don't consider Cocoa buffer mutable, even if it is mutable and is - // uniquely referenced. - return false - } -#endif - // Note that &self drills down through .native(_NativeSet) to the first - // property in _NativeSet, which is the reference to the storage. - return _isUnique_native(&self) + return object.isUniquelyReferencedNative() } #if _runtime(_ObjC) @usableFromInline @_transparent internal var isNative: Bool { - switch self { - case .native: - return true - case .cocoa: - cocoaPath() - return false - } + return guaranteedNative || object.isNative } #endif @usableFromInline @_transparent internal var asNative: _NativeSet { get { - switch self { - case .native(let nativeSet): - return nativeSet -#if _runtime(_ObjC) - case .cocoa: - _sanityCheckFailure("internal error: not backed by native buffer") -#endif - } + return _NativeSet(object.nativeInstance) } set { - self = .native(newValue) + self = .init(native: newValue) } } #if _runtime(_ObjC) @inlinable internal var asCocoa: _CocoaSet { - switch self { - case .native: - _sanityCheckFailure("internal error: not backed by NSSet") - case .cocoa(let cocoa): - return cocoa - } + return _CocoaSet(object.objCInstance) } #endif @@ -119,7 +92,7 @@ extension Set._Variant { guard isNative else { let cocoa = asCocoa let capacity = Swift.max(cocoa.count, capacity) - self = .native(_NativeSet(cocoa, capacity: capacity)) + self = .init(native: _NativeSet(cocoa, capacity: capacity)) return } #endif @@ -181,16 +154,14 @@ extension Set._Variant: _SetBuffer { @inlinable internal func formIndex(after index: inout Index) { - switch self { - case .native(let native): - index = native.index(after: index) #if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() + guard isNative else { let isUnique = index._isUniquelyReferenced() - cocoa.formIndex(after: &index._asCocoa, isUnique: isUnique) -#endif + asCocoa.formIndex(after: &index._asCocoa, isUnique: isUnique) + return } +#endif + index = asNative.index(after: index) } @inlinable @@ -251,7 +222,7 @@ extension Set._Variant { // Make sure we have space for an extra element. var native = _NativeSet(asCocoa, capacity: asCocoa.count + 1) let old = native.update(with: value, isUnique: true) - self = .native(native) + self = .init(native: native) return old } #endif @@ -273,7 +244,7 @@ extension Set._Variant { } var native = _NativeSet(cocoa, capacity: cocoa.count + 1) native.insertNew(element, isUnique: true) - self = .native(native) + self = .init(native: native) return (true, element) } #endif @@ -334,7 +305,7 @@ extension Set._Variant { _precondition(found, "Bridging did not preserve equality") let old = native.uncheckedRemove(at: bucket, isUnique: true) _precondition(member == old, "Bridging did not preserve equality") - self = .native(native) + self = .init(native: native) return old } #endif @@ -342,14 +313,14 @@ extension Set._Variant { @inlinable internal mutating func removeAll(keepingCapacity keepCapacity: Bool) { if !keepCapacity { - self = .native(_NativeSet()) + self = .init(native: _NativeSet()) return } guard count > 0 else { return } #if _runtime(_ObjC) guard isNative else { - self = .native(_NativeSet(capacity: asCocoa.count)) + self = .init(native: _NativeSet(capacity: asCocoa.count)) return } #endif From e47de238a88c85bc3b2ce7ed1e10cac62d5506cd Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 3 Oct 2018 17:36:10 +0100 Subject: [PATCH 4/4] [stdlib] Set, Dictionary: Implement modify accessor for _variant.asNative --- stdlib/public/core/BridgeStorage.swift | 8 +++++++- stdlib/public/core/DictionaryVariant.swift | 6 ++++++ stdlib/public/core/SetVariant.swift | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/stdlib/public/core/BridgeStorage.swift b/stdlib/public/core/BridgeStorage.swift index 9e8447b800223..a8d1dba6a4300 100644 --- a/stdlib/public/core/BridgeStorage.swift +++ b/stdlib/public/core/BridgeStorage.swift @@ -60,7 +60,13 @@ struct _BridgeStorage< _sanityCheck(_usesNativeSwiftReferenceCounting(NativeClass.self)) rawValue = Builtin.reinterpretCast(native) } - + + @inlinable + @inline(__always) + internal init(taggedPayload: UInt) { + rawValue = _bridgeObject(taggingPayload: taggedPayload) + } + @inlinable // FIXME(sil-serialize-all) public // @testable var spareBits: Int { diff --git a/stdlib/public/core/DictionaryVariant.swift b/stdlib/public/core/DictionaryVariant.swift index a0bd30849b685..ec621acd2c319 100644 --- a/stdlib/public/core/DictionaryVariant.swift +++ b/stdlib/public/core/DictionaryVariant.swift @@ -79,6 +79,12 @@ extension Dictionary._Variant { set { self = .init(native: newValue) } + _modify { + var native = _NativeDictionary(object.nativeInstance) + object = .init(taggedPayload: 0) + yield &native + object = .init(native: native._storage) + } } #if _runtime(_ObjC) diff --git a/stdlib/public/core/SetVariant.swift b/stdlib/public/core/SetVariant.swift index 643a52e3f7692..8091500b1b6cd 100644 --- a/stdlib/public/core/SetVariant.swift +++ b/stdlib/public/core/SetVariant.swift @@ -76,6 +76,12 @@ extension Set._Variant { set { self = .init(native: newValue) } + _modify { + var native = _NativeSet(object.nativeInstance) + object = .init(taggedPayload: 0) + yield &native + object = .init(native: native._storage) + } } #if _runtime(_ObjC)