Built-in unaligned loads


(Justin Kolb) #1

While Swift has aligned loads like this:

public func load<T>(fromByteOffset offset: Int = default, as type: T.Type) -> T

If possible, it would also be nice to add unaligned loads which are sometimes needed when accessing data from binary formats that in some cases expect you to extract data at odd numbered memory locations.

Example API:

public func loadUnaligned<T>(fromByteOffset offset: Int = default, as type: T.Type) -> T

Here is one example implementation that I'm currently using in my own code:

public func loadUnalignedUInt16(fromByteOffset offset: Int = 0) -> UInt16 {
    var value: UInt16 = 0
    UnsafeMutableRawPointer(&value).copyMemory(from: (self + offset), byteCount: MemoryLayout<UInt16>.size)
    return value

(John McCall) #2

This falls into a general class of "interesting features that can only be supported on concrete types, and even then not on all of them". It's on a long list of nice-to-have features that nobody's felt compelled to do, but if you're specifically interested in working on it, I can try to give you some design and implementation direction.

(Happy Human Pointer) #3

This would definitely be useful. One example I see is loading an Int from a UInt pointer.

(Steve Canon) #4

A UInt pointer is necessarily aligned for Int, and you can reinterpret values between the two types using the truncatingBitPattern: init, so I'm not sure how that's relevant?

(John McCall) #5

"unaligned" does not mean "not subject to any other restrictions". Loading an Int from a UInt pointer still likely violates aliasing rules.

(Justin Kolb) #6

I'm willing to give it a shot. Let me know your ideas and any pointers on how to get started as this would be my first time in the Swift codebase. I was guessing it would only be possible to implement for a limited number of types but wasn't sure what the restrictions would be. I kept my implementations to just UInt16, UInt32, and UInt64 since I knew those would be safe and are the only ones I might need in what I was working on.

(John McCall) #7

Hmm. Well, I won't promise that this is going to be easy. :slight_smile:

I think the best approach here is to allow generic parameters to be constrained to only apply to "trivial" types (meaning "plain old data" types that can be copied with memcpy), then add a constrained builtin that performs an unaligned load, then implement UnsafePointer.loadUnaligned using that builtin. The "trivial-only" type constraint will require an evolution proposal, as will loadUnaligned; technically we could allow loadUnaligned to be constrained to trivial types "magically" without proposing an official way of spelling that, but I think that would be a mistake.

So, the first step is probably to start a new thread in the Compiler Development category.

Most of the infrastructure for trivial type constraints is already implemented using the spelling _Trivial. The main thing that's missing is type-checking: you can declare a type parameter to be constrained to be trivial, but we don't actually check that. So the first step would be to actually add that type-checking; this is probably the hardest part. I think this is a matter of grepping lib/Sema for LayoutConstraint and trying to fix those places to handle Trivial, not just class constraints.

For the builtin, you would basically copy how LoadRaw is handled, at least up until SILGen; we'll need to talk with the larger team about how to represent it in SIL. I suppose adding another flag to LoadInst would be consistent. In IRGenSIL, you would then check for unaligned loads and then emit them differently; we can talk about that when we get to that point.

(Justin Kolb) #8

Would the addition of Trivial types be considered a large change for little benefit, or does that allow for other important things?

On a side note are there plans for a formal aggregate memory layout for Swift 5?

(Steve Canon) #9

Swift 5 is branched, so it’s fairly safe to assume that any significant feature not already there is not planned for Swift 5.

(John McCall) #10

Swift 5 has a stable ABI. If you have a struct type that was introduced with a minimum deployment of Swift 5, then values of that struct that are potentially accessed directly by Swift 5 code are guaranteed to have their stable-ABI layout, which is necessarily the layout they had in Swift 5. In particular, that means unsafe pointers, tuple elements that are potentially accessed directly, etc. It does not mean that values of that struct that are e.g. embedded in some other type with weaker restrictions are necessarily stored according to that ABI.

That's a lot of concepts, and we'll have to talk more about them when we introduce support for third-party binary libraries.

(John McCall) #11

I think there are probably other things that would benefit from a Trivial restriction.

(Joe Groff) #12

I agree that a Trivial type constraint would be generally useful. Without that language feature, though, an implementation could assert that it's applied to trivial types, and that assertion has a high likelihood of getting specialized away. (With the compile-time evaluation infrastructure that the Tensorflow team is working on, maybe that assertion could even be promoted to an error or warning at compile time.) To me, it'd make sense to have loadUnaligned and storeUnaligned be operations on UnsafeRawPointer, with memcpy-like semantics, so that type-based aliasing is not a concern.

(Happy Human Pointer) #13

Maybe a Trival protocol like Any (non-nominal).

What requirements would a Trival Type have?