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

Endianness sensitive collection v2 #72

Closed
oscbyspro opened this issue Aug 26, 2023 · 16 comments
Closed

Endianness sensitive collection v2 #72

oscbyspro opened this issue Aug 26, 2023 · 16 comments
Labels
addition oh, so shiny! subtraction huh? where did it go?
Milestone

Comments

@oscbyspro
Copy link
Owner

oscbyspro commented Aug 26, 2023

Reworking NBKLittleEndianOrdered to also handle big endianness and dynamic ordering is quite simple, and it appears to be just as viable in Swift 5.8 (did not test 5.7). I envision something like the following:

@frozen public struct NBKEndiannessOrdered<Base, Endianness> { ... }
//  this could be an ordering protocol or whatever
public protocol NBKEndianness {

    //  relative to system architecture, dynamic, or whatever
    //  edit: Swift 5.7 needs @_transparent or @inline(__always)
    var direction: Int { get } // 1 or -1
}
//  with a prayer for constant-folded branches for each specialization
@inlinable public func baseIndex(_ index: Int) -> Base.Index {
    if  self.endianness.direction.isLessThanZero {
        return self.base.index(self.base.endIndex,   offsetBy: ~index)
    }   else {
        return self.base.index(self.base.startIndex, offsetBy:  index)
    }
}

I can even make the change seamless with some type aliases:

public typealias    NBKBigEndianOrdered<Base> = NBKEndiannessOrdered<Base,    NBKBigEndian>
public typealias NBKLittleEndianOrdered<Base> = NBKEndiannessOrdered<Base, NBKLittleEndian>
@oscbyspro oscbyspro added addition oh, so shiny! subtraction huh? where did it go? labels Aug 26, 2023
@oscbyspro oscbyspro added this to the v0.11.0 milestone Aug 26, 2023
@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 26, 2023

Also, rebasing slices is too complicated. I'll have a look at that. Perhaps:

typealias SubSequence = NBKEndiannessOrdered<Base.SebSequence, Endianness>

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 26, 2023

This model can be made standalone, but endianness justifies the constraints.

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 27, 2023

Another thought: there's a light-weight alternative without protocols:

struct BigEndianRange { // light-weight without conformances
    
    typealias Base = Range<Int>
    
    let base: Base
    
    init(_ base: Base) { self.base = base }
    
    subscript(index: Int) -> Base.Element {
        #if _endian(big)
        base[base.index(base.startIndex, offsetBy:  index)]
        #else
        base[base.index(base.endIndex,   offsetBy: ~index)]
        #endif
    }
}

let base = [0, 1, 2, 3]
let bigEndianOrderedIndices = BigEndianRange(base.indices)
for baseIndex in bigEndianOrderedIndices.base {
    print(base[bigEndianOrderedIndices[baseIndex]], base[baseIndex])
}

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 27, 2023

Another thought: perhaps I don't need to parameterize endianness if I pass an index and a direction to the iterator. If the iterator just calls base.index(baseIndex, offsetBy: direction), then maybe a dynamic endianness can perform well.

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 27, 2023

So I had a really neat realization. I can make the dynamic case branchless if the base index is constrained to Int by storing an endianness sensitive base index and a bit-mask used to XOR the offset relative to the base index. Galaxy brain stuff.

Edit: oh, wait, there's more. I'm XOR-ing the offset, so the base index can be whatever.

Edit: oh, wait, there's more. If I add the opposite index, the collection becomes self-slicing.

Edit: Hm. It seems useful beyond this project. I might turn it into a separate mini-package.

Edit: self-slicing is made too awkward by the shared indices requirement of subsequences.

@oscbyspro oscbyspro added the maybe to do, or not to do? label Aug 27, 2023
@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 27, 2023

TODO: If I pick a dynamic solution, I can remove potential calls to reverse() without creating more specialization of NBK.integerTextUnchecked(...). The potential reverse() calls come from NBKDoubleWidth/withContiguousStorage(_:).

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 28, 2023

The desire to co-opt "endianness" to contextualize the collection's directionality is strong, but I suppose I could call it "TwinHeaded", "BooleanOrdered", "ReversibleCollection" or something else entirely.

oscbyspro added a commit that referenced this issue Aug 28, 2023
@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 28, 2023

I see two alternatives using this approach: with and without a stored index (in case it matters).

@inlinable func baseSubscriptIndex(_ index: Int) -> Base.Index {
    return base.index(edge,            offsetBy: index ^ mask)
    return base.index(base.startIndex, offsetBy: index ^ mask &+ count &* (~mask &+ 1))
}

oscbyspro added a commit that referenced this issue Aug 28, 2023
@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 28, 2023

Hm. I suppose I cannot expose a mutable base with a store index, but I can without.

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 28, 2023

Hm. Slicing and rebasing seems slow. I wonder if I need a custom subsequence model. I can work around it by wrapping the indices, rebasing the buffer, then wrapping the rebased buffer—but that's lame.

Edit: I can't think of another collection that allows this kind of nested rebasing, so maybe I should just drop it?

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 28, 2023

I can get rebasing to perform with a custom subsequence. I'm unsure why it's better than Slice<Self>.

Edit: I think Slice<Self> will work, but you have to avoid slice.indices like the plague. ¯\_(ツ)_/¯

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 29, 2023

Here are some really nice conveniences I could think of:

/// Returns whether the base collection's elements are presented front-to-back.
var isFrontToBack: Bool { get }

/// Returns the base collection, if its elements matches this collection.
var asFrontToBack: Base? { get }

/// Returns whether the base collection's elements are presented back-to-front.
var isBackToFront: Bool { get }

/// Returns the base collection but reversed, if its elements matches this collection.
var asBackToFront: ReversedCollection<Base>? { get }

/// Creates a collection with the same memory and direction as the given subsequence.
init<T>(rebasing subsequence: SubSequence) where Base == UnsafeBufferPointer<T>

/// Creates a collection with the same memory and direction as the given subsequence.
init<T>(rebasing subsequence: SubSequence) where Base == UnsafeMutableBufferPointer<T>

oscbyspro added a commit that referenced this issue Aug 29, 2023
oscbyspro added a commit that referenced this issue Aug 29, 2023
@oscbyspro oscbyspro removed the maybe to do, or not to do? label Aug 29, 2023
@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 29, 2023

I've played with conveniences like NBKTwinHeaded(base, head: .system), and it is somewhat arcane while not much more convenient than NBKTwinHeaded(base, reversed: NBK.isBigEndian)—so I'll remove it. The idea was that this is clunky, but meh: NBKEndianness.system == .big. I think naming the reversed condition is fine.

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 29, 2023

Zzz. It seems like I'll have to expose baseSubscriptIndex(_:) for performance reasons.

@oscbyspro
Copy link
Owner Author

oscbyspro commented Aug 30, 2023

This is cute; you can get the direction of iteration as a ±1 integer with bitwise operations:

var direction: Int { mask &<< 1 | 1 }

oscbyspro added a commit that referenced this issue Aug 30, 2023
oscbyspro added a commit that referenced this issue Aug 30, 2023
@oscbyspro
Copy link
Owner Author

Here's a neat drop-last-with-rebase trick using pointers and free™ reversed() calls.

NBKTwinHeaded(rebasing: NBKTwinHeaded(data).reversed().drop(while:{ $0.isZero })).reversed()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition oh, so shiny! subtraction huh? where did it go?
Projects
None yet
Development

No branches or pull requests

1 participant