Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Persistent collections updates (part 5) #179

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2e523b1
[PersistentCollections] Reverse ordering of items in node storage
lorentey Sep 16, 2022
3e38184
[PersistentCollections] Optimize _Node.==
lorentey Sep 16, 2022
edaadc9
[PersistentCollections] _Level: Store the shift amount in an UInt8
lorentey Sep 16, 2022
6dd0211
[PersistentCollections] Flesh out _RawNode, add _UnmanagedNode
lorentey Sep 16, 2022
547ac90
[PersistentCollections] Rework basic node properties
lorentey Sep 16, 2022
02133f1
[PersistentCollections] Implement path-based indices
lorentey Sep 16, 2022
16393ea
[PersistentCollections] Allow dumping hash trees in iteration order
lorentey Sep 16, 2022
bf3617e
[PersistentCollections] Work on testing a bit; add fixtures
lorentey Sep 16, 2022
0fe7f08
[PersistentCollections] offset → slot
lorentey Sep 16, 2022
52e3ea6
[test] LifetimeTracked: Implement high-fidelity hash forwarding
lorentey Sep 16, 2022
6cf128e
[PersistentCollections] Internal doc updates
lorentey Sep 16, 2022
1324c4e
[OrderedDictionary] Implement index invalidation
lorentey Sep 16, 2022
1661bd4
[PersistentDictionary] Implement in-place mutations for defaulted sub…
lorentey Sep 16, 2022
3b7d241
[PersistentDictionary] Add some docs
lorentey Sep 16, 2022
57deed5
[PersistentDictionary] Implement in-place mutations
lorentey Sep 17, 2022
2321b34
[PersistentCollections] Fix node sizing logic
lorentey Sep 17, 2022
5240bf6
[PersistentCollections] Reduce _Bucket’s storage size
lorentey Sep 18, 2022
d45f264
[PersistentDictionary] Fix index(forKey:) performance
lorentey Sep 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions Sources/PersistentCollections/Node/_AncestorOffsets.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Collections open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

/// A collection of slot values logically addressing a particular node in a
/// hash tree. The collection is (logically) extended with zero slots up to
/// the maximum depth of the tree -- to unambiguously address a single node,
/// this therefore needs to be augmented with a `_Level` value.
///
/// This construct can only be used to identify a particular node in the tree;
/// it does not necessarily have room to include an item offset in the addressed
/// node. (See `_Path` if you need to address a particular item.)
@usableFromInline
@frozen
struct _AncestorSlots {
@usableFromInline
internal var path: UInt

@inlinable @inline(__always)
internal init(_ path: UInt) {
self.path = path
}
}

extension _AncestorSlots: Equatable {
@inlinable @inline(__always)
internal static func ==(left: Self, right: Self) -> Bool {
left.path == right.path
}
}

extension _AncestorSlots {
@inlinable @inline(__always)
internal static var empty: Self { Self(0) }
}

extension _AncestorSlots {
/// Return or set the slot value at the specified level.
/// If this is used to mutate the collection, then the original value
/// on the given level must be zero.
@inlinable @inline(__always)
internal subscript(_ level: _Level) -> _Slot {
get {
assert(level.shift < UInt.bitWidth)
return _Slot((path &>> level.shift) & _Bucket.bitMask)
}
set {
assert(newValue._value < UInt.bitWidth)
assert(self[level] == .zero)
path |= (UInt(truncatingIfNeeded: newValue._value) &<< level.shift)
}
}

@inlinable @inline(__always)
internal func appending(_ slot: _Slot, at level: _Level) -> Self {
var result = self
result[level] = slot
return result
}

/// Clear the slot at the specified level, by setting it to zero.
@inlinable
internal mutating func clear(_ level: _Level) {
guard level.shift < UInt.bitWidth else { return }
path &= ~(_Bucket.bitMask &<< level.shift)
}

/// Truncate this path to the specified level.
/// Slots at or beyond the specified level are cleared.
@inlinable
internal func truncating(to level: _Level) -> _AncestorSlots {
assert(level.shift <= UInt.bitWidth)
guard level.shift < UInt.bitWidth else { return self }
return _AncestorSlots(path & ((1 &<< level.shift) &- 1))
}

/// Returns true if this path contains non-zero slots at or beyond the
/// specified level, otherwise returns false.
@inlinable
internal func hasDataBelow(_ level: _Level) -> Bool {
guard level.shift < UInt.bitWidth else { return false }
return (path &>> level.shift) != 0
}

/// Compares this path to `other` up to but not including the specified level.
/// Returns true if the path prefixes compare equal, otherwise returns false.
@inlinable
internal func isEqual(to other: Self, upTo level: _Level) -> Bool {
if level.isAtRoot { return true }
if level.isAtBottom { return self == other }
let s = UInt(UInt.bitWidth) - level.shift
let v1 = self.path &<< s
let v2 = other.path &<< s
return v1 == v2
}
}

8 changes: 4 additions & 4 deletions Sources/PersistentCollections/Node/_Bitmap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ extension _Bitmap {
}

@inlinable @inline(__always)
internal func offset(of bucket: _Bucket) -> Int {
_value._rank(ofBit: bucket.value)
internal func slot(of bucket: _Bucket) -> _Slot {
_Slot(_value._rank(ofBit: bucket.value))
}

@inlinable @inline(__always)
internal func bucket(at offset: Int) -> _Bucket {
_Bucket(_value._bit(ranked: offset)!)
internal func bucket(at slot: _Slot) -> _Bucket {
_Bucket(_value._bit(ranked: slot.value)!)
}
}

Expand Down
25 changes: 18 additions & 7 deletions Sources/PersistentCollections/Node/_Bucket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,23 @@
@frozen
internal struct _Bucket {
@usableFromInline
internal var value: UInt
internal var _value: UInt8

@inlinable @inline(__always)
internal init(_value: UInt8) {
assert(_value < _Bitmap.capacity || _value == .max)
self._value = _value
}
}

extension _Bucket {
@inlinable @inline(__always)
internal var value: UInt { UInt(truncatingIfNeeded: _value) }

@inlinable @inline(__always)
internal init(_ value: UInt) {
assert(value < _Bitmap.capacity || value == .max)
self.value = value
self._value = UInt8(truncatingIfNeeded: value)
}
}

Expand All @@ -32,29 +43,29 @@ extension _Bucket {
static var bitMask: UInt { UInt(bitPattern: _Bitmap.capacity) &- 1 }

@inlinable @inline(__always)
static var invalid: _Bucket { _Bucket(.max) }
static var invalid: _Bucket { _Bucket(_value: .max) }

@inlinable @inline(__always)
var isInvalid: Bool { value == .max }
var isInvalid: Bool { _value == .max }
}

extension _Bucket: Equatable {
@inlinable @inline(__always)
internal static func ==(left: Self, right: Self) -> Bool {
left.value == right.value
left._value == right._value
}
}

extension _Bucket: Comparable {
@inlinable @inline(__always)
internal static func <(left: Self, right: Self) -> Bool {
left.value < right.value
left._value < right._value
}
}

extension _Bucket: CustomStringConvertible {
@usableFromInline
internal var description: String {
String(value, radix: _Bitmap.capacity)
String(_value, radix: _Bitmap.capacity)
}
}
44 changes: 35 additions & 9 deletions Sources/PersistentCollections/Node/_Level.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,28 @@
//
//===----------------------------------------------------------------------===//

/// Identifies a particular level within the hash tree.
///
/// Hash trees have a maximum depth of ⎡`UInt.bitWidth / _Bucket.bitWidth`⎤, so
/// the level always fits in an `UInt8` value.
@usableFromInline
@frozen
internal struct _Level {
/// The bit position within a hash value that begins the hash slice that is
/// associated with this level. For collision nodes, this can be larger than
/// `UInt.bitWidth`.
@usableFromInline
internal var shift: UInt
internal var _shift: UInt8
msteindorfer marked this conversation as resolved.
Show resolved Hide resolved

@inlinable @inline(__always)
init(_shift: UInt8) {
self._shift = _shift
}

@inlinable @inline(__always)
init(shift: UInt) {
self.shift = shift
assert(shift <= UInt8.max)
self._shift = UInt8(truncatingIfNeeded: shift)
}
}

Expand All @@ -28,37 +41,50 @@ extension _Level {
}

@inlinable @inline(__always)
internal static var _step: UInt {
UInt(bitPattern: _Bitmap.bitWidth)
internal static var _step: UInt8 {
UInt8(truncatingIfNeeded: _Bitmap.bitWidth)
}

@inlinable @inline(__always)
internal static var top: _Level {
_Level(shift: 0)
}

/// The bit position within a hash value that begins the hash slice that is
/// associated with this level. For collision nodes, this can be larger than
/// `UInt.bitWidth`.
@inlinable @inline(__always)
internal var shift: UInt { UInt(truncatingIfNeeded: _shift) }

@inlinable @inline(__always)
internal var isAtRoot: Bool { shift == 0 }
internal var isAtRoot: Bool { _shift == 0 }

@inlinable @inline(__always)
internal var isAtBottom: Bool { shift >= UInt.bitWidth }
internal var isAtBottom: Bool { _shift >= UInt.bitWidth }

@inlinable @inline(__always)
internal func descend() -> _Level {
// FIXME: Consider returning nil when we run out of bits
_Level(shift: shift &+ Self._step)
_Level(_shift: _shift &+ Self._step)
}

@inlinable @inline(__always)
internal func ascend() -> _Level {
assert(!isAtRoot)
return _Level(shift: shift &+ Self._step)
return _Level(_shift: _shift &- Self._step)
}
}

extension _Level: Equatable {
@inlinable @inline(__always)
internal static func ==(left: Self, right: Self) -> Bool {
left.shift == right.shift
left._shift == right._shift
}
}

extension _Level: Comparable {
@inlinable @inline(__always)
internal static func <(left: Self, right: Self) -> Bool {
left._shift < right._shift
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ extension _Node: CustomStringConvertible {
var result = "["
var first = true
read {
for (key, value) in $0._items {
for (key, value) in $0.reverseItems.reversed() {
if first {
first = false
} else {
Expand Down
Loading