[Proposal] Introduces endianness specific type


#1

IMO, it has unclear representation when FixedWidthInteger working with
endianness specific type.

so I want to introduce the endianness specific wrapper:

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger {

    public var bigEndian: BEInteger { get }

    public var littleEndian: LEInteger<Base> { get }

}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger {

    public var bigEndian: BEInteger<Base> { get }

    public var littleEndian: LEInteger { get }

}

also, we should change the FixedWidthInteger as follow:

public protocol FixedWidthInteger : BinaryInteger {

    /// deprecated, we should use value.bigEndian instead

    init(bigEndian value: Self)

    /// deprecated, we should use value.littleEndian instead

    init(littleEndian value: Self)

    associatedtype EndianRepresentingValue : FixedWidthInteger

    var bigEndian: BEInteger<EndianRepresentingValue> { get }

    var littleEndian: LEInteger<EndianRepresentingValue> { get }

}

···

=============================

this is my working alternative implementation:

@_versioned

protocol EndianInteger : FixedWidthInteger {

    associatedtype BitPattern : FixedWidthInteger

    associatedtype RepresentingValue : FixedWidthInteger

    var bitPattern: BitPattern { get }

    init(bitPattern: BitPattern)

    var representingValue : RepresentingValue { get set }

    init(representingValue: RepresentingValue)

}

extension EndianInteger {

    @_transparent

    public init(integerLiteral value: RepresentingValue.IntegerLiteralType)
{

        self.init(representingValue: RepresentingValue(integerLiteral:
value))

    }

    @_transparent

    public init?<T>(exactly source: T) where T : BinaryInteger {

        guard let value = RepresentingValue(exactly: source) else { return
nil }

        self.init(representingValue: value)

    }

    @_transparent

    public init?<T>(exactly source: T) where T : FloatingPoint {

        guard let value = RepresentingValue(exactly: source) else { return
nil }

        self.init(representingValue: value)

    }

    @_transparent

    public init(_ value: RepresentingValue) {

        self.init(representingValue: value)

    }

    @_transparent

    public init<T>(_ source: T) where T : FloatingPoint {

        self.init(representingValue: RepresentingValue(source))

    }

    @_transparent

    public init<T>(_ source: T) where T : BinaryInteger {

        self.init(representingValue: RepresentingValue(source))

    }

    @_transparent

    public init<T>(extendingOrTruncating source: T) where T : BinaryInteger
{

        self.init(representingValue: RepresentingValue(extendingOrTruncating:
source))

    }

    @_transparent

    public init<T>(clamping source: T) where T : BinaryInteger {

        self.init(representingValue: RepresentingValue(clamping: source))

    }

    @_transparent

    public init(_truncatingBits bits: UInt) {

        self.init(representingValue: RepresentingValue(_truncatingBits:
bits))

    }

}

extension EndianInteger {

    @_transparent

    public static var isSigned: Bool {

        return RepresentingValue.isSigned

    }

    @_transparent

    public static var bitWidth: Int {

        return RepresentingValue.bitWidth

    }

    @_transparent

    public static var max: Self {

        return Self(representingValue: RepresentingValue.max)

    }

    @_transparent

    public static var min: Self {

        return Self(representingValue: RepresentingValue.min)

    }

}

extension EndianInteger {

    @_transparent

    public var hashValue: Int {

        return representingValue.hashValue

    }

    @_transparent

    public var description: String {

        return representingValue.description

    }

    @_transparent

    public var bitWidth: Int {

        return representingValue.bitWidth

    }

    @_transparent

    public var magnitude: RepresentingValue.Magnitude {

        return representingValue.magnitude

    }

    @_transparent

    public var trailingZeroBitCount: Int {

        return representingValue.trailingZeroBitCount

    }

    @_transparent

    public var nonzeroBitCount: Int {

        return representingValue.nonzeroBitCount

    }

    @_transparent

    public var leadingZeroBitCount: Int {

        return representingValue.leadingZeroBitCount

    }

    @_transparent

    public var byteSwapped: Self {

        return Self(representingValue: representingValue.byteSwapped)

    }

}

extension EndianInteger {

    @_transparent

    public func _word(at n: Int) -> UInt {

        return representingValue._word(at: n)

    }

    @_transparent

    public func distance(to other: Self) -> RepresentingValue.Stride {

        return self.representingValue.distance(to: other.representingValue)

    }

    @_transparent

    public func advanced(by n: RepresentingValue.Stride) -> Self {

        return Self(representingValue: self.representingValue.advanced(by:
n))

    }

    @_transparent

    public func addingReportingOverflow(_ rhs: Self) -> (partialValue: Self,
overflow: ArithmeticOverflow) {

        let (partialValue, overflow) = representingValue.addingReport
ingOverflow(rhs.representingValue)

        return (Self(representingValue: partialValue), overflow)

    }

    @_transparent

    public func subtractingReportingOverflow(_ rhs: Self) -> (partialValue:
Self, overflow: ArithmeticOverflow) {

        let (partialValue, overflow) = representingValue.subtractingR
eportingOverflow(rhs.representingValue)

        return (Self(representingValue: partialValue), overflow)

    }

    @_transparent

    public func multipliedReportingOverflow(by rhs: Self) -> (partialValue:
Self, overflow: ArithmeticOverflow) {

        let (partialValue, overflow) = representingValue.multipliedRe
portingOverflow(by: rhs.representingValue)

        return (Self(representingValue: partialValue), overflow)

    }

    @_transparent

    public func dividedReportingOverflow(by rhs: Self) -> (partialValue:
Self, overflow: ArithmeticOverflow) {

        let (partialValue, overflow) = representingValue.dividedRepor
tingOverflow(by: rhs.representingValue)

        return (Self(representingValue: partialValue), overflow)

    }

    @_transparent

    public func remainderReportingOverflow(dividingBy rhs: Self) ->
(partialValue: Self, overflow: ArithmeticOverflow) {

        let (partialValue, overflow) = representingValue.remainderRep
ortingOverflow(dividingBy: rhs.representingValue)

        return (Self(representingValue: partialValue), overflow)

    }

    @_transparent

    public func multipliedFullWidth(by other: Self) -> (high: Self, low:
RepresentingValue.Magnitude) {

        let (high, low) = representingValue.multipliedFullWidth(by: other.
representingValue)

        return (Self(representingValue: high), low)

    }

    @_transparent

    public func dividingFullWidth(_ dividend: (high: Self, low:
RepresentingValue.Magnitude)) -> (quotient: Self, remainder: Self) {

        let (quotient, remainder) = representingValue.dividingFullWidth
((dividend.high.representingValue, dividend.low))

        return (Self(representingValue: quotient), Self(representingValue:
remainder))

    }

}

extension EndianInteger {

    @_transparent

    public static prefix func +(x: Self) -> Self {

        return x

    }

    @_transparent

    public static func +(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue + rhs.
representingValue)

    }

    @_transparent

    public static func +=(lhs: inout Self, rhs: Self) {

        lhs.representingValue += rhs.representingValue

    }

    @_transparent

    public static func -(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue - rhs.
representingValue)

    }

    @_transparent

    public static func -=(lhs: inout Self, rhs: Self) {

        lhs.representingValue -= rhs.representingValue

    }

    @_transparent

    public static func *(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue * rhs.
representingValue)

    }

    @_transparent

    public static func *=(lhs: inout Self, rhs: Self) {

        lhs.representingValue *= rhs.representingValue

    }

    @_transparent

    public static func /(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue / rhs.
representingValue)

    }

    @_transparent

    public static func /=(lhs: inout Self, rhs: Self) {

        lhs.representingValue /= rhs.representingValue

    }

    @_transparent

    public static func %(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue % rhs.
representingValue)

    }

    @_transparent

    public static func %=(lhs: inout Self, rhs: Self) {

        lhs.representingValue %= rhs.representingValue

    }

    @_transparent

    public static func &(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue & rhs.
representingValue)

    }

    @_transparent

    public static func &=(lhs: inout Self, rhs: Self) {

        lhs.representingValue &= rhs.representingValue

    }

    @_transparent

    public static func |(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue | rhs.
representingValue)

    }

    @_transparent

    public static func |=(lhs: inout Self, rhs: Self) {

        lhs.representingValue |= rhs.representingValue

    }

    @_transparent

    public static func ^(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue ^ rhs.
representingValue)

    }

    @_transparent

    public static func ^=(lhs: inout Self, rhs: Self) {

        lhs.representingValue ^= rhs.representingValue

    }

    @_transparent

    prefix public static func ~(x: Self) -> Self {

        return Self(representingValue: ~x.representingValue)

    }

    @_transparent

    public static func &>>(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue &>> rhs.
representingValue)

    }

    @_transparent

    public static func &<<(lhs: Self, rhs: Self) -> Self {

        return Self(representingValue: lhs.representingValue &<< rhs.
representingValue)

    }

    @_transparent

    public static func ==(lhs: Self, rhs: Self) -> Bool {

        return lhs.bitPattern == rhs.bitPattern

    }

    @_transparent

    public static func !=(lhs: Self, rhs: Self) -> Bool {

        return lhs.bitPattern != rhs.bitPattern

    }

    @_transparent

    public static func >(lhs: Self, rhs: Self) -> Bool {

        return lhs.representingValue > rhs.representingValue

    }

    @_transparent

    public static func <(lhs: Self, rhs: Self) -> Bool {

        return lhs.representingValue < rhs.representingValue

    }

    @_transparent

    public static func >=(lhs: Self, rhs: Self) -> Bool {

        return lhs.representingValue >= rhs.representingValue

    }

    @_transparent

    public static func <=(lhs: Self, rhs: Self) -> Bool {

        return lhs.representingValue <= rhs.representingValue

    }

}

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger,
EndianInteger {

    public var bitPattern: Base

    @_transparent

    public init(bitPattern: Base) {

        self.bitPattern = bitPattern

    }

    @_versioned

    @_transparent

    init(representingValue: Base) {

        self.bitPattern = representingValue.bigEndian

    }

    @_versioned

    @_transparent

    var representingValue: Base {

        get {

            return Base(bigEndian: bitPattern)

        }

        set {

            bitPattern = newValue.bigEndian

        }

    }

    @_transparent

    public init(bigEndian value: BEInteger) {

        self.bitPattern = value.bitPattern

    }

    @_transparent

    public init(littleEndian value: BEInteger) {

        self.bitPattern = value.bitPattern.byteSwapped

    }

    @_transparent

    public var bigEndian: BEInteger {

        return self

    }

    @_transparent

    public var littleEndian: BEInteger {

        return BEInteger(littleEndian: self)

    }

}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger,
EndianInteger {

    public var bitPattern: Base

    @_transparent

    public init(bitPattern: Base) {

        self.bitPattern = bitPattern

    }

    @_versioned

    @_transparent

    init(representingValue: Base) {

        self.bitPattern = representingValue.littleEndian

    }

    @_versioned

    @_transparent

    var representingValue: Base {

        get {

            return Base(littleEndian: bitPattern)

        }

        set {

            bitPattern = newValue.littleEndian

        }

    }

    @_transparent

    public init(bigEndian value: LEInteger) {

        self.bitPattern = value.bitPattern.byteSwapped

    }

    @_transparent

    public init(littleEndian value: LEInteger) {

        self.bitPattern = value.bitPattern

    }

    @_transparent

    public var bigEndian: LEInteger {

        return LEInteger(bigEndian: self)

    }

    @_transparent

    public var littleEndian: LEInteger {

        return self

    }

}


(Max Moiseev) #2

Hi Susan,

Was there any motivation for this proposal that I missed? If not then, can you please provide it in a few sentences? Otherwise it’s not clear to me what problem it is supposed to fix.

Thanks,
Max

···

On Jul 6, 2017, at 8:21 PM, Susan Cheng via swift-evolution <swift-evolution@swift.org> wrote:

IMO, it has unclear representation when FixedWidthInteger working with endianness specific type.

so I want to introduce the endianness specific wrapper:

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger {
    
    public var bigEndian: BEInteger { get }
    
    public var littleEndian: LEInteger<Base> { get }
}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger {
    
    public var bigEndian: BEInteger<Base> { get }
    
    public var littleEndian: LEInteger { get }
}

also, we should change the FixedWidthInteger as follow:

public protocol FixedWidthInteger : BinaryInteger {
    
    /// deprecated, we should use value.bigEndian instead
    init(bigEndian value: Self)
    
    /// deprecated, we should use value.littleEndian instead
    init(littleEndian value: Self)
    
    associatedtype EndianRepresentingValue : FixedWidthInteger
    
    var bigEndian: BEInteger<EndianRepresentingValue> { get }
    
    var littleEndian: LEInteger<EndianRepresentingValue> { get }
}

=============================

this is my working alternative implementation:

@_versioned
protocol EndianInteger : FixedWidthInteger {
    
    associatedtype BitPattern : FixedWidthInteger
    
    associatedtype RepresentingValue : FixedWidthInteger
    
    var bitPattern: BitPattern { get }
    
    init(bitPattern: BitPattern)
    
    var representingValue : RepresentingValue { get set }
    
    init(representingValue: RepresentingValue)
}

extension EndianInteger {
    
    @_transparent
    public init(integerLiteral value: RepresentingValue.IntegerLiteralType) {
        self.init(representingValue: RepresentingValue(integerLiteral: value))
    }
    
    @_transparent
    public init?<T>(exactly source: T) where T : BinaryInteger {
        guard let value = RepresentingValue(exactly: source) else { return nil }
        self.init(representingValue: value)
    }
    
    @_transparent
    public init?<T>(exactly source: T) where T : FloatingPoint {
        guard let value = RepresentingValue(exactly: source) else { return nil }
        self.init(representingValue: value)
    }
    
    @_transparent
    public init(_ value: RepresentingValue) {
        self.init(representingValue: value)
    }
    
    @_transparent
    public init<T>(_ source: T) where T : FloatingPoint {
        self.init(representingValue: RepresentingValue(source))
    }
    
    @_transparent
    public init<T>(_ source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(source))
    }
    
    @_transparent
    public init<T>(extendingOrTruncating source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(extendingOrTruncating: source))
    }
    
    @_transparent
    public init<T>(clamping source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(clamping: source))
    }
    
    @_transparent
    public init(_truncatingBits bits: UInt) {
        self.init(representingValue: RepresentingValue(_truncatingBits: bits))
    }
}

extension EndianInteger {
    
    @_transparent
    public static var isSigned: Bool {
        return RepresentingValue.isSigned
    }
    
    @_transparent
    public static var bitWidth: Int {
        return RepresentingValue.bitWidth
    }
    
    @_transparent
    public static var max: Self {
        return Self(representingValue: RepresentingValue.max)
    }
    
    @_transparent
    public static var min: Self {
        return Self(representingValue: RepresentingValue.min)
    }
}

extension EndianInteger {
    
    @_transparent
    public var hashValue: Int {
        return representingValue.hashValue
    }
    
    @_transparent
    public var description: String {
        return representingValue.description
    }
    
    @_transparent
    public var bitWidth: Int {
        return representingValue.bitWidth
    }
    
    @_transparent
    public var magnitude: RepresentingValue.Magnitude {
        return representingValue.magnitude
    }
    
    @_transparent
    public var trailingZeroBitCount: Int {
        return representingValue.trailingZeroBitCount
    }
    
    @_transparent
    public var nonzeroBitCount: Int {
        return representingValue.nonzeroBitCount
    }
    
    @_transparent
    public var leadingZeroBitCount: Int {
        return representingValue.leadingZeroBitCount
    }
    
    @_transparent
    public var byteSwapped: Self {
        return Self(representingValue: representingValue.byteSwapped)
    }
}

extension EndianInteger {
    
    @_transparent
    public func _word(at n: Int) -> UInt {
        return representingValue._word(at: n)
    }
    
    @_transparent
    public func distance(to other: Self) -> RepresentingValue.Stride {
        return self.representingValue.distance(to: other.representingValue)
    }
    
    @_transparent
    public func advanced(by n: RepresentingValue.Stride) -> Self {
        return Self(representingValue: self.representingValue.advanced(by: n))
    }
    
    @_transparent
    public func addingReportingOverflow(_ rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.addingReportingOverflow(rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func subtractingReportingOverflow(_ rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.subtractingReportingOverflow(rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func multipliedReportingOverflow(by rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.multipliedReportingOverflow(by: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func dividedReportingOverflow(by rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.dividedReportingOverflow(by: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func remainderReportingOverflow(dividingBy rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.remainderReportingOverflow(dividingBy: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func multipliedFullWidth(by other: Self) -> (high: Self, low: RepresentingValue.Magnitude) {
        let (high, low) = representingValue.multipliedFullWidth(by: other.representingValue)
        return (Self(representingValue: high), low)
    }
    
    @_transparent
    public func dividingFullWidth(_ dividend: (high: Self, low: RepresentingValue.Magnitude)) -> (quotient: Self, remainder: Self) {
        let (quotient, remainder) = representingValue.dividingFullWidth((dividend.high.representingValue, dividend.low))
        return (Self(representingValue: quotient), Self(representingValue: remainder))
    }
}

extension EndianInteger {
    
    @_transparent
    public static prefix func +(x: Self) -> Self {
        return x
    }
    
    @_transparent
    public static func +(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue + rhs.representingValue)
    }
    
    @_transparent
    public static func +=(lhs: inout Self, rhs: Self) {
        lhs.representingValue += rhs.representingValue
    }
    
    @_transparent
    public static func -(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue - rhs.representingValue)
    }
    
    @_transparent
    public static func -=(lhs: inout Self, rhs: Self) {
        lhs.representingValue -= rhs.representingValue
    }
    
    @_transparent
    public static func *(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue * rhs.representingValue)
    }
    
    @_transparent
    public static func *=(lhs: inout Self, rhs: Self) {
        lhs.representingValue *= rhs.representingValue
    }
    
    @_transparent
    public static func /(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue / rhs.representingValue)
    }
    
    @_transparent
    public static func /=(lhs: inout Self, rhs: Self) {
        lhs.representingValue /= rhs.representingValue
    }
    
    @_transparent
    public static func %(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue % rhs.representingValue)
    }
    
    @_transparent
    public static func %=(lhs: inout Self, rhs: Self) {
        lhs.representingValue %= rhs.representingValue
    }
    
    @_transparent
    public static func &(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue & rhs.representingValue)
    }
    
    @_transparent
    public static func &=(lhs: inout Self, rhs: Self) {
        lhs.representingValue &= rhs.representingValue
    }
    
    @_transparent
    public static func |(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue | rhs.representingValue)
    }
    
    @_transparent
    public static func |=(lhs: inout Self, rhs: Self) {
        lhs.representingValue |= rhs.representingValue
    }
    
    @_transparent
    public static func ^(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue ^ rhs.representingValue)
    }
    
    @_transparent
    public static func ^=(lhs: inout Self, rhs: Self) {
        lhs.representingValue ^= rhs.representingValue
    }
    
    @_transparent
    prefix public static func ~(x: Self) -> Self {
        return Self(representingValue: ~x.representingValue)
    }
    
    @_transparent
    public static func &>>(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue &>> rhs.representingValue)
    }
    
    @_transparent
    public static func &<<(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue &<< rhs.representingValue)
    }
    
    @_transparent
    public static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.bitPattern == rhs.bitPattern
    }
    
    @_transparent
    public static func !=(lhs: Self, rhs: Self) -> Bool {
        return lhs.bitPattern != rhs.bitPattern
    }
    
    @_transparent
    public static func >(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue > rhs.representingValue
    }
    
    @_transparent
    public static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue < rhs.representingValue
    }
    
    @_transparent
    public static func >=(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue >= rhs.representingValue
    }
    
    @_transparent
    public static func <=(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue <= rhs.representingValue
    }
}

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger, EndianInteger {
    
    public var bitPattern: Base
    
    @_transparent
    public init(bitPattern: Base) {
        self.bitPattern = bitPattern
    }
    
    @_versioned
    @_transparent
    init(representingValue: Base) {
        self.bitPattern = representingValue.bigEndian
    }
    
    @_versioned
    @_transparent
    var representingValue: Base {
        get {
            return Base(bigEndian: bitPattern)
        }
        set {
            bitPattern = newValue.bigEndian
        }
    }
    
    @_transparent
    public init(bigEndian value: BEInteger) {
        self.bitPattern = value.bitPattern
    }
    
    @_transparent
    public init(littleEndian value: BEInteger) {
        self.bitPattern = value.bitPattern.byteSwapped
    }
    
    @_transparent
    public var bigEndian: BEInteger {
        return self
    }
    
    @_transparent
    public var littleEndian: BEInteger {
        return BEInteger(littleEndian: self)
    }
}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger, EndianInteger {
    
    public var bitPattern: Base
    
    @_transparent
    public init(bitPattern: Base) {
        self.bitPattern = bitPattern
    }
    
    @_versioned
    @_transparent
    init(representingValue: Base) {
        self.bitPattern = representingValue.littleEndian
    }
    
    @_versioned
    @_transparent
    var representingValue: Base {
        get {
            return Base(littleEndian: bitPattern)
        }
        set {
            bitPattern = newValue.littleEndian
        }
    }
    
    @_transparent
    public init(bigEndian value: LEInteger) {
        self.bitPattern = value.bitPattern.byteSwapped
    }
    
    @_transparent
    public init(littleEndian value: LEInteger) {
        self.bitPattern = value.bitPattern
    }
    
    @_transparent
    public var bigEndian: LEInteger {
        return LEInteger(bigEndian: self)
    }
    
    @_transparent
    public var littleEndian: LEInteger {
        return self
    }
}
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


#3

Here are two problems being fixed.

First, considering the example:

struct MyRawDataStruct {

      var size: UInt32
      var signature: UInt32
      var width: UInt32
      var height: UInt32
}

The type UInt32 doesn't tall us the endianness of the value. Also, if we read the value of it, the value is being byte-swapped when endianness is not matching with the system.

This causes us have to manual convert the value from/to correct endianness.

struct MyRawDataStruct {

      var size: BEInteger<UInt32>
      var signature: BEInteger<UInt32>
      var width: BEInteger<UInt32>
      var height: BEInteger<UInt32>
}

So, my proposal fix the problem. We can easily to get the value.

let header: MyRawDataStruct = data.withUnsafePointer { $0.pointee }

print(header.size) // print the representing value

Second, it's misleading means of bigEndian and littleEndian from FixedWidthInteger

if we do this

let a = 1

print(a.bigEndian.bigEndian)

It's just swap bytes twice but not converting value to big-endian

My proposal solves the problem

let b = a.bigEndian // BEInteger<Int>

b.bigEndian // remain big-endian of a

···

Max Moiseev <moiseev@apple.com> 於 2017年7月8日 上午1:48 寫道:

Hi Susan,

Was there any motivation for this proposal that I missed? If not then, can you please provide it in a few sentences? Otherwise it’s not clear to me what problem it is supposed to fix.

Thanks,
Max

On Jul 6, 2017, at 8:21 PM, Susan Cheng via swift-evolution <swift-evolution@swift.org> wrote:

IMO, it has unclear representation when FixedWidthInteger working with endianness specific type.

so I want to introduce the endianness specific wrapper:

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger {
    
    public var bigEndian: BEInteger { get }
    
    public var littleEndian: LEInteger<Base> { get }
}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger {
    
    public var bigEndian: BEInteger<Base> { get }
    
    public var littleEndian: LEInteger { get }
}

also, we should change the FixedWidthInteger as follow:

public protocol FixedWidthInteger : BinaryInteger {
    
    /// deprecated, we should use value.bigEndian instead
    init(bigEndian value: Self)
    
    /// deprecated, we should use value.littleEndian instead
    init(littleEndian value: Self)
    
    associatedtype EndianRepresentingValue : FixedWidthInteger
    
    var bigEndian: BEInteger<EndianRepresentingValue> { get }
    
    var littleEndian: LEInteger<EndianRepresentingValue> { get }
}

=============================

this is my working alternative implementation:

@_versioned
protocol EndianInteger : FixedWidthInteger {
    
    associatedtype BitPattern : FixedWidthInteger
    
    associatedtype RepresentingValue : FixedWidthInteger
    
    var bitPattern: BitPattern { get }
    
    init(bitPattern: BitPattern)
    
    var representingValue : RepresentingValue { get set }
    
    init(representingValue: RepresentingValue)
}

extension EndianInteger {
    
    @_transparent
    public init(integerLiteral value: RepresentingValue.IntegerLiteralType) {
        self.init(representingValue: RepresentingValue(integerLiteral: value))
    }
    
    @_transparent
    public init?<T>(exactly source: T) where T : BinaryInteger {
        guard let value = RepresentingValue(exactly: source) else { return nil }
        self.init(representingValue: value)
    }
    
    @_transparent
    public init?<T>(exactly source: T) where T : FloatingPoint {
        guard let value = RepresentingValue(exactly: source) else { return nil }
        self.init(representingValue: value)
    }
    
    @_transparent
    public init(_ value: RepresentingValue) {
        self.init(representingValue: value)
    }
    
    @_transparent
    public init<T>(_ source: T) where T : FloatingPoint {
        self.init(representingValue: RepresentingValue(source))
    }
    
    @_transparent
    public init<T>(_ source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(source))
    }
    
    @_transparent
    public init<T>(extendingOrTruncating source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(extendingOrTruncating: source))
    }
    
    @_transparent
    public init<T>(clamping source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(clamping: source))
    }
    
    @_transparent
    public init(_truncatingBits bits: UInt) {
        self.init(representingValue: RepresentingValue(_truncatingBits: bits))
    }
}

extension EndianInteger {
    
    @_transparent
    public static var isSigned: Bool {
        return RepresentingValue.isSigned
    }
    
    @_transparent
    public static var bitWidth: Int {
        return RepresentingValue.bitWidth
    }
    
    @_transparent
    public static var max: Self {
        return Self(representingValue: RepresentingValue.max)
    }
    
    @_transparent
    public static var min: Self {
        return Self(representingValue: RepresentingValue.min)
    }
}

extension EndianInteger {
    
    @_transparent
    public var hashValue: Int {
        return representingValue.hashValue
    }
    
    @_transparent
    public var description: String {
        return representingValue.description
    }
    
    @_transparent
    public var bitWidth: Int {
        return representingValue.bitWidth
    }
    
    @_transparent
    public var magnitude: RepresentingValue.Magnitude {
        return representingValue.magnitude
    }
    
    @_transparent
    public var trailingZeroBitCount: Int {
        return representingValue.trailingZeroBitCount
    }
    
    @_transparent
    public var nonzeroBitCount: Int {
        return representingValue.nonzeroBitCount
    }
    
    @_transparent
    public var leadingZeroBitCount: Int {
        return representingValue.leadingZeroBitCount
    }
    
    @_transparent
    public var byteSwapped: Self {
        return Self(representingValue: representingValue.byteSwapped)
    }
}

extension EndianInteger {
    
    @_transparent
    public func _word(at n: Int) -> UInt {
        return representingValue._word(at: n)
    }
    
    @_transparent
    public func distance(to other: Self) -> RepresentingValue.Stride {
        return self.representingValue.distance(to: other.representingValue)
    }
    
    @_transparent
    public func advanced(by n: RepresentingValue.Stride) -> Self {
        return Self(representingValue: self.representingValue.advanced(by: n))
    }
    
    @_transparent
    public func addingReportingOverflow(_ rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.addingReportingOverflow(rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func subtractingReportingOverflow(_ rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.subtractingReportingOverflow(rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func multipliedReportingOverflow(by rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.multipliedReportingOverflow(by: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func dividedReportingOverflow(by rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.dividedReportingOverflow(by: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func remainderReportingOverflow(dividingBy rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.remainderReportingOverflow(dividingBy: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func multipliedFullWidth(by other: Self) -> (high: Self, low: RepresentingValue.Magnitude) {
        let (high, low) = representingValue.multipliedFullWidth(by: other.representingValue)
        return (Self(representingValue: high), low)
    }
    
    @_transparent
    public func dividingFullWidth(_ dividend: (high: Self, low: RepresentingValue.Magnitude)) -> (quotient: Self, remainder: Self) {
        let (quotient, remainder) = representingValue.dividingFullWidth((dividend.high.representingValue, dividend.low))
        return (Self(representingValue: quotient), Self(representingValue: remainder))
    }
}

extension EndianInteger {
    
    @_transparent
    public static prefix func +(x: Self) -> Self {
        return x
    }
    
    @_transparent
    public static func +(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue + rhs.representingValue)
    }
    
    @_transparent
    public static func +=(lhs: inout Self, rhs: Self) {
        lhs.representingValue += rhs.representingValue
    }
    
    @_transparent
    public static func -(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue - rhs.representingValue)
    }
    
    @_transparent
    public static func -=(lhs: inout Self, rhs: Self) {
        lhs.representingValue -= rhs.representingValue
    }
    
    @_transparent
    public static func *(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue * rhs.representingValue)
    }
    
    @_transparent
    public static func *=(lhs: inout Self, rhs: Self) {
        lhs.representingValue *= rhs.representingValue
    }
    
    @_transparent
    public static func /(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue / rhs.representingValue)
    }
    
    @_transparent
    public static func /=(lhs: inout Self, rhs: Self) {
        lhs.representingValue /= rhs.representingValue
    }
    
    @_transparent
    public static func %(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue % rhs.representingValue)
    }
    
    @_transparent
    public static func %=(lhs: inout Self, rhs: Self) {
        lhs.representingValue %= rhs.representingValue
    }
    
    @_transparent
    public static func &(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue & rhs.representingValue)
    }
    
    @_transparent
    public static func &=(lhs: inout Self, rhs: Self) {
        lhs.representingValue &= rhs.representingValue
    }
    
    @_transparent
    public static func |(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue | rhs.representingValue)
    }
    
    @_transparent
    public static func |=(lhs: inout Self, rhs: Self) {
        lhs.representingValue |= rhs.representingValue
    }
    
    @_transparent
    public static func ^(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue ^ rhs.representingValue)
    }
    
    @_transparent
    public static func ^=(lhs: inout Self, rhs: Self) {
        lhs.representingValue ^= rhs.representingValue
    }
    
    @_transparent
    prefix public static func ~(x: Self) -> Self {
        return Self(representingValue: ~x.representingValue)
    }
    
    @_transparent
    public static func &>>(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue &>> rhs.representingValue)
    }
    
    @_transparent
    public static func &<<(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue &<< rhs.representingValue)
    }
    
    @_transparent
    public static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.bitPattern == rhs.bitPattern
    }
    
    @_transparent
    public static func !=(lhs: Self, rhs: Self) -> Bool {
        return lhs.bitPattern != rhs.bitPattern
    }
    
    @_transparent
    public static func >(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue > rhs.representingValue
    }
    
    @_transparent
    public static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue < rhs.representingValue
    }
    
    @_transparent
    public static func >=(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue >= rhs.representingValue
    }
    
    @_transparent
    public static func <=(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue <= rhs.representingValue
    }
}

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger, EndianInteger {
    
    public var bitPattern: Base
    
    @_transparent
    public init(bitPattern: Base) {
        self.bitPattern = bitPattern
    }
    
    @_versioned
    @_transparent
    init(representingValue: Base) {
        self.bitPattern = representingValue.bigEndian
    }
    
    @_versioned
    @_transparent
    var representingValue: Base {
        get {
            return Base(bigEndian: bitPattern)
        }
        set {
            bitPattern = newValue.bigEndian
        }
    }
    
    @_transparent
    public init(bigEndian value: BEInteger) {
        self.bitPattern = value.bitPattern
    }
    
    @_transparent
    public init(littleEndian value: BEInteger) {
        self.bitPattern = value.bitPattern.byteSwapped
    }
    
    @_transparent
    public var bigEndian: BEInteger {
        return self
    }
    
    @_transparent
    public var littleEndian: BEInteger {
        return BEInteger(littleEndian: self)
    }
}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger, EndianInteger {
    
    public var bitPattern: Base
    
    @_transparent
    public init(bitPattern: Base) {
        self.bitPattern = bitPattern
    }
    
    @_versioned
    @_transparent
    init(representingValue: Base) {
        self.bitPattern = representingValue.littleEndian
    }
    
    @_versioned
    @_transparent
    var representingValue: Base {
        get {
            return Base(littleEndian: bitPattern)
        }
        set {
            bitPattern = newValue.littleEndian
        }
    }
    
    @_transparent
    public init(bigEndian value: LEInteger) {
        self.bitPattern = value.bitPattern.byteSwapped
    }
    
    @_transparent
    public init(littleEndian value: LEInteger) {
        self.bitPattern = value.bitPattern
    }
    
    @_transparent
    public var bigEndian: LEInteger {
        return LEInteger(bigEndian: self)
    }
    
    @_transparent
    public var littleEndian: LEInteger {
        return self
    }
}
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #4

Hi Susan,

Swift does not currently specify a layout for Swift structs. You shouldn’t be using them for memory mapped i/o or writing to a file, because their layout can change. When ABI stability for fragile structs lands, you will be able to count on it, but until then something like this is probably a bad idea.

-Chris

···

On Jul 7, 2017, at 6:16 PM, Susan Cheng via swift-evolution <swift-evolution@swift.org> wrote:

Here are two problems being fixed.

First, considering the example:

struct MyRawDataStruct {

      var size: UInt32
      var signature: UInt32
      var width: UInt32
      var height: UInt32
}

The type UInt32 doesn't tall us the endianness of the value. Also, if we read the value of it, the value is being byte-swapped when endianness is not matching with the system.

This causes us have to manual convert the value from/to correct endianness.

struct MyRawDataStruct {

      var size: BEInteger<UInt32>
      var signature: BEInteger<UInt32>
      var width: BEInteger<UInt32>
      var height: BEInteger<UInt32>
}

So, my proposal fix the problem. We can easily to get the value.

let header: MyRawDataStruct = data.withUnsafePointer { $0.pointee }

print(header.size) // print the representing value

Second, it's misleading means of bigEndian and littleEndian from FixedWidthInteger

if we do this

let a = 1

print(a.bigEndian.bigEndian)

It's just swap bytes twice but not converting value to big-endian

My proposal solves the problem

let b = a.bigEndian // BEInteger<Int>

b.bigEndian // remain big-endian of a

Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> 於 2017年7月8日 上午1:48 寫道:

Hi Susan,

Was there any motivation for this proposal that I missed? If not then, can you please provide it in a few sentences? Otherwise it’s not clear to me what problem it is supposed to fix.

Thanks,
Max

On Jul 6, 2017, at 8:21 PM, Susan Cheng via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

IMO, it has unclear representation when FixedWidthInteger working with endianness specific type.

so I want to introduce the endianness specific wrapper:

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger {
    
    public var bigEndian: BEInteger { get }
    
    public var littleEndian: LEInteger<Base> { get }
}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger {
    
    public var bigEndian: BEInteger<Base> { get }
    
    public var littleEndian: LEInteger { get }
}

also, we should change the FixedWidthInteger as follow:

public protocol FixedWidthInteger : BinaryInteger {
    
    /// deprecated, we should use value.bigEndian instead
    init(bigEndian value: Self)
    
    /// deprecated, we should use value.littleEndian instead
    init(littleEndian value: Self)
    
    associatedtype EndianRepresentingValue : FixedWidthInteger
    
    var bigEndian: BEInteger<EndianRepresentingValue> { get }
    
    var littleEndian: LEInteger<EndianRepresentingValue> { get }
}

=============================

this is my working alternative implementation:

@_versioned
protocol EndianInteger : FixedWidthInteger {
    
    associatedtype BitPattern : FixedWidthInteger
    
    associatedtype RepresentingValue : FixedWidthInteger
    
    var bitPattern: BitPattern { get }
    
    init(bitPattern: BitPattern)
    
    var representingValue : RepresentingValue { get set }
    
    init(representingValue: RepresentingValue)
}

extension EndianInteger {
    
    @_transparent
    public init(integerLiteral value: RepresentingValue.IntegerLiteralType) {
        self.init(representingValue: RepresentingValue(integerLiteral: value))
    }
    
    @_transparent
    public init?<T>(exactly source: T) where T : BinaryInteger {
        guard let value = RepresentingValue(exactly: source) else { return nil }
        self.init(representingValue: value)
    }
    
    @_transparent
    public init?<T>(exactly source: T) where T : FloatingPoint {
        guard let value = RepresentingValue(exactly: source) else { return nil }
        self.init(representingValue: value)
    }
    
    @_transparent
    public init(_ value: RepresentingValue) {
        self.init(representingValue: value)
    }
    
    @_transparent
    public init<T>(_ source: T) where T : FloatingPoint {
        self.init(representingValue: RepresentingValue(source))
    }
    
    @_transparent
    public init<T>(_ source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(source))
    }
    
    @_transparent
    public init<T>(extendingOrTruncating source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(extendingOrTruncating: source))
    }
    
    @_transparent
    public init<T>(clamping source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(clamping: source))
    }
    
    @_transparent
    public init(_truncatingBits bits: UInt) {
        self.init(representingValue: RepresentingValue(_truncatingBits: bits))
    }
}

extension EndianInteger {
    
    @_transparent
    public static var isSigned: Bool {
        return RepresentingValue.isSigned
    }
    
    @_transparent
    public static var bitWidth: Int {
        return RepresentingValue.bitWidth
    }
    
    @_transparent
    public static var max: Self {
        return Self(representingValue: RepresentingValue.max)
    }
    
    @_transparent
    public static var min: Self {
        return Self(representingValue: RepresentingValue.min)
    }
}

extension EndianInteger {
    
    @_transparent
    public var hashValue: Int {
        return representingValue.hashValue
    }
    
    @_transparent
    public var description: String {
        return representingValue.description
    }
    
    @_transparent
    public var bitWidth: Int {
        return representingValue.bitWidth
    }
    
    @_transparent
    public var magnitude: RepresentingValue.Magnitude {
        return representingValue.magnitude
    }
    
    @_transparent
    public var trailingZeroBitCount: Int {
        return representingValue.trailingZeroBitCount
    }
    
    @_transparent
    public var nonzeroBitCount: Int {
        return representingValue.nonzeroBitCount
    }
    
    @_transparent
    public var leadingZeroBitCount: Int {
        return representingValue.leadingZeroBitCount
    }
    
    @_transparent
    public var byteSwapped: Self {
        return Self(representingValue: representingValue.byteSwapped)
    }
}

extension EndianInteger {
    
    @_transparent
    public func _word(at n: Int) -> UInt {
        return representingValue._word(at: n)
    }
    
    @_transparent
    public func distance(to other: Self) -> RepresentingValue.Stride {
        return self.representingValue.distance(to: other.representingValue)
    }
    
    @_transparent
    public func advanced(by n: RepresentingValue.Stride) -> Self {
        return Self(representingValue: self.representingValue.advanced(by: n))
    }
    
    @_transparent
    public func addingReportingOverflow(_ rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.addingReportingOverflow(rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func subtractingReportingOverflow(_ rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.subtractingReportingOverflow(rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func multipliedReportingOverflow(by rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.multipliedReportingOverflow(by: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func dividedReportingOverflow(by rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.dividedReportingOverflow(by: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func remainderReportingOverflow(dividingBy rhs: Self) -> (partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.remainderReportingOverflow(dividingBy: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }
    
    @_transparent
    public func multipliedFullWidth(by other: Self) -> (high: Self, low: RepresentingValue.Magnitude) {
        let (high, low) = representingValue.multipliedFullWidth(by: other.representingValue)
        return (Self(representingValue: high), low)
    }
    
    @_transparent
    public func dividingFullWidth(_ dividend: (high: Self, low: RepresentingValue.Magnitude)) -> (quotient: Self, remainder: Self) {
        let (quotient, remainder) = representingValue.dividingFullWidth((dividend.high.representingValue, dividend.low))
        return (Self(representingValue: quotient), Self(representingValue: remainder))
    }
}

extension EndianInteger {
    
    @_transparent
    public static prefix func +(x: Self) -> Self {
        return x
    }
    
    @_transparent
    public static func +(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue + rhs.representingValue)
    }
    
    @_transparent
    public static func +=(lhs: inout Self, rhs: Self) {
        lhs.representingValue += rhs.representingValue
    }
    
    @_transparent
    public static func -(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue - rhs.representingValue)
    }
    
    @_transparent
    public static func -=(lhs: inout Self, rhs: Self) {
        lhs.representingValue -= rhs.representingValue
    }
    
    @_transparent
    public static func *(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue * rhs.representingValue)
    }
    
    @_transparent
    public static func *=(lhs: inout Self, rhs: Self) {
        lhs.representingValue *= rhs.representingValue
    }
    
    @_transparent
    public static func /(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue / rhs.representingValue)
    }
    
    @_transparent
    public static func /=(lhs: inout Self, rhs: Self) {
        lhs.representingValue /= rhs.representingValue
    }
    
    @_transparent
    public static func %(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue % rhs.representingValue)
    }
    
    @_transparent
    public static func %=(lhs: inout Self, rhs: Self) {
        lhs.representingValue %= rhs.representingValue
    }
    
    @_transparent
    public static func &(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue & rhs.representingValue)
    }
    
    @_transparent
    public static func &=(lhs: inout Self, rhs: Self) {
        lhs.representingValue &= rhs.representingValue
    }
    
    @_transparent
    public static func |(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue | rhs.representingValue)
    }
    
    @_transparent
    public static func |=(lhs: inout Self, rhs: Self) {
        lhs.representingValue |= rhs.representingValue
    }
    
    @_transparent
    public static func ^(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue ^ rhs.representingValue)
    }
    
    @_transparent
    public static func ^=(lhs: inout Self, rhs: Self) {
        lhs.representingValue ^= rhs.representingValue
    }
    
    @_transparent
    prefix public static func ~(x: Self) -> Self {
        return Self(representingValue: ~x.representingValue)
    }
    
    @_transparent
    public static func &>>(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue &>> rhs.representingValue)
    }
    
    @_transparent
    public static func &<<(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue &<< rhs.representingValue)
    }
    
    @_transparent
    public static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.bitPattern == rhs.bitPattern
    }
    
    @_transparent
    public static func !=(lhs: Self, rhs: Self) -> Bool {
        return lhs.bitPattern != rhs.bitPattern
    }
    
    @_transparent
    public static func >(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue > rhs.representingValue
    }
    
    @_transparent
    public static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue < rhs.representingValue
    }
    
    @_transparent
    public static func >=(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue >= rhs.representingValue
    }
    
    @_transparent
    public static func <=(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue <= rhs.representingValue
    }
}

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger, EndianInteger {
    
    public var bitPattern: Base
    
    @_transparent
    public init(bitPattern: Base) {
        self.bitPattern = bitPattern
    }
    
    @_versioned
    @_transparent
    init(representingValue: Base) {
        self.bitPattern = representingValue.bigEndian
    }
    
    @_versioned
    @_transparent
    var representingValue: Base {
        get {
            return Base(bigEndian: bitPattern)
        }
        set {
            bitPattern = newValue.bigEndian
        }
    }
    
    @_transparent
    public init(bigEndian value: BEInteger) {
        self.bitPattern = value.bitPattern
    }
    
    @_transparent
    public init(littleEndian value: BEInteger) {
        self.bitPattern = value.bitPattern.byteSwapped
    }
    
    @_transparent
    public var bigEndian: BEInteger {
        return self
    }
    
    @_transparent
    public var littleEndian: BEInteger {
        return BEInteger(littleEndian: self)
    }
}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger, EndianInteger {
    
    public var bitPattern: Base
    
    @_transparent
    public init(bitPattern: Base) {
        self.bitPattern = bitPattern
    }
    
    @_versioned
    @_transparent
    init(representingValue: Base) {
        self.bitPattern = representingValue.littleEndian
    }
    
    @_versioned
    @_transparent
    var representingValue: Base {
        get {
            return Base(littleEndian: bitPattern)
        }
        set {
            bitPattern = newValue.littleEndian
        }
    }
    
    @_transparent
    public init(bigEndian value: LEInteger) {
        self.bitPattern = value.bitPattern.byteSwapped
    }
    
    @_transparent
    public init(littleEndian value: LEInteger) {
        self.bitPattern = value.bitPattern
    }
    
    @_transparent
    public var bigEndian: LEInteger {
        return LEInteger(bigEndian: self)
    }
    
    @_transparent
    public var littleEndian: LEInteger {
        return self
    }
}
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jens Persson) #5

Does this imply that you should never use Swift structs to eg interact with
Metal?

This seems to be a very common practice. Here is a typical example (from a
Metal tutorial at raywenderlich.com):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

(
https://www.raywenderlich.com/146416/metal-tutorial-swift-3-part-2-moving-3d
)

Also, does it imply that we cannot use structs (of only primitive types)
like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I vaguely remember a swift evo discussion where it was concluded that such
usage was considered OK provided the stored properties of the structs was
only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms are
known to be only iOS devices?

/Jens

···

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

Hi Susan,

Swift does not currently specify a layout for Swift structs. You
shouldn’t be using them for memory mapped i/o or writing to a file, because
their layout can change. When ABI stability for fragile structs lands, you
will be able to count on it, but until then something like this is probably
a bad idea.

-Chris


#6

Thanks, but we can implement Codable for BEInteger and LEInteger types.

public struct BEInteger<BitPattern : FixedWidthInteger> : FixedWidthInteger
{

    public var bitPattern: BitPattern

    public init(bitPattern: BitPattern)

    public var bigEndian: BEInteger { get }

    public var littleEndian: LEInteger<BitPattern> { get }

}

public struct LEInteger<BitPattern : FixedWidthInteger> : FixedWidthInteger
{

    public var bitPattern: BitPattern

    public init(bitPattern: BitPattern)

    public var bigEndian: BEInteger<BitPattern> { get }

    public var littleEndian: LEInteger { get }

}

extension BEInteger : Encodable where BitPattern : Encodable {

    public func encode(to encoder: Encoder) throws {

        try self.bitPattern.encode(to: encoder)

    }

}

extension BEInteger : Decodable where BitPattern : Decodable {

    public init(from decoder: Decoder) throws {

        self.init(bitPattern: try BitPattern(from: decoder))

    }

}

extension LEInteger : Encodable where BitPattern : Encodable {

    public func encode(to encoder: Encoder) throws {

        try self.bitPattern.encode(to: encoder)

    }

}

extension LEInteger : Decodable where BitPattern : Decodable {

    public init(from decoder: Decoder) throws {

        self.init(bitPattern: try BitPattern(from: decoder))

    }

}

···

2017-07-09 0:27 GMT+08:00 Chris Lattner <clattner@nondot.org>:

Hi Susan,

Swift does not currently specify a layout for Swift structs. You
shouldn’t be using them for memory mapped i/o or writing to a file, because
their layout can change. When ABI stability for fragile structs lands, you
will be able to count on it, but until then something like this is probably
a bad idea.

-Chris

On Jul 7, 2017, at 6:16 PM, Susan Cheng via swift-evolution < > swift-evolution@swift.org> wrote:

Here are two problems being fixed.

First, considering the example:

struct MyRawDataStruct {

      var size: UInt32
      var signature: UInt32
      var width: UInt32
      var height: UInt32
}

The type UInt32 doesn't tall us the endianness of the value. Also, if we
read the value of it, the value is being byte-swapped when endianness is
not matching with the system.

This causes us have to manual convert the value from/to correct endianness.

struct MyRawDataStruct {

      var size: BEInteger<UInt32>
      var signature: BEInteger<UInt32>
      var width: BEInteger<UInt32>
      var height: BEInteger<UInt32>
}

So, my proposal fix the problem. We can easily to get the value.

let header: MyRawDataStruct = data.withUnsafePointer { $0.pointee }

print(header.size) // print the representing value

Second, it's misleading means of bigEndian and littleEndian from
FixedWidthInteger

if we do this

let a = 1

print(a.bigEndian.bigEndian)

It's just swap bytes twice but not converting value to big-endian

My proposal solves the problem

let b = a.bigEndian // BEInteger<Int>

b.bigEndian // remain big-endian of a

Max Moiseev <moiseev@apple.com> 於 2017年7月8日 上午1:48 寫道:

Hi Susan,

Was there any motivation for this proposal that I missed? If not then, can
you please provide it in a few sentences? Otherwise it’s not clear to me
what problem it is supposed to fix.

Thanks,
Max

On Jul 6, 2017, at 8:21 PM, Susan Cheng via swift-evolution < > swift-evolution@swift.org> wrote:

IMO, it has unclear representation when FixedWidthInteger working with
endianness specific type.

so I want to introduce the endianness specific wrapper:

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger {

    public var bigEndian: BEInteger { get }

    public var littleEndian: LEInteger<Base> { get }
}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger {

    public var bigEndian: BEInteger<Base> { get }

    public var littleEndian: LEInteger { get }
}

also, we should change the FixedWidthInteger as follow:

public protocol FixedWidthInteger : BinaryInteger {

    /// deprecated, we should use value.bigEndian instead
    init(bigEndian value: Self)

    /// deprecated, we should use value.littleEndian instead
    init(littleEndian value: Self)

    associatedtype EndianRepresentingValue : FixedWidthInteger

    var bigEndian: BEInteger<EndianRepresentingValue> { get }

    var littleEndian: LEInteger<EndianRepresentingValue> { get }

}

=============================

this is my working alternative implementation:

@_versioned
protocol EndianInteger : FixedWidthInteger {

    associatedtype BitPattern : FixedWidthInteger

    associatedtype RepresentingValue : FixedWidthInteger

    var bitPattern: BitPattern { get }

    init(bitPattern: BitPattern)

    var representingValue : RepresentingValue { get set }

    init(representingValue: RepresentingValue)
}

extension EndianInteger {

    @_transparent
    public init(integerLiteral value: RepresentingValue.IntegerLiteralType)
{
        self.init(representingValue: RepresentingValue(integerLiteral:
value))
    }

    @_transparent
    public init?<T>(exactly source: T) where T : BinaryInteger {
        guard let value = RepresentingValue(exactly: source) else { return
nil }
        self.init(representingValue: value)
    }

    @_transparent
    public init?<T>(exactly source: T) where T : FloatingPoint {
        guard let value = RepresentingValue(exactly: source) else { return
nil }
        self.init(representingValue: value)
    }

    @_transparent
    public init(_ value: RepresentingValue) {
        self.init(representingValue: value)
    }

    @_transparent
    public init<T>(_ source: T) where T : FloatingPoint {
        self.init(representingValue: RepresentingValue(source))
    }

    @_transparent
    public init<T>(_ source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(source))
    }

    @_transparent
    public init<T>(extendingOrTruncating source: T) where T : BinaryInt
eger {
        self.init(representingValue: RepresentingValue(extendingOrTruncating:
source))
    }

    @_transparent
    public init<T>(clamping source: T) where T : BinaryInteger {
        self.init(representingValue: RepresentingValue(clamping: source))
    }

    @_transparent
    public init(_truncatingBits bits: UInt) {
        self.init(representingValue: RepresentingValue(_truncatingBits:
bits))
    }
}

extension EndianInteger {

    @_transparent
    public static var isSigned: Bool {
        return RepresentingValue.isSigned
    }

    @_transparent
    public static var bitWidth: Int {
        return RepresentingValue.bitWidth
    }

    @_transparent
    public static var max: Self {
        return Self(representingValue: RepresentingValue.max)
    }

    @_transparent
    public static var min: Self {
        return Self(representingValue: RepresentingValue.min)
    }
}

extension EndianInteger {

    @_transparent
    public var hashValue: Int {
        return representingValue.hashValue
    }

    @_transparent
    public var description: String {
        return representingValue.description
    }

    @_transparent
    public var bitWidth: Int {
        return representingValue.bitWidth
    }

    @_transparent
    public var magnitude: RepresentingValue.Magnitude {
        return representingValue.magnitude
    }

    @_transparent
    public var trailingZeroBitCount: Int {
        return representingValue.trailingZeroBitCount
    }

    @_transparent
    public var nonzeroBitCount: Int {
        return representingValue.nonzeroBitCount
    }

    @_transparent
    public var leadingZeroBitCount: Int {
        return representingValue.leadingZeroBitCount
    }

    @_transparent
    public var byteSwapped: Self {
        return Self(representingValue: representingValue.byteSwapped)
    }
}

extension EndianInteger {

    @_transparent
    public func _word(at n: Int) -> UInt {
        return representingValue._word(at: n)
    }

    @_transparent
    public func distance(to other: Self) -> RepresentingValue.Stride {
        return self.representingValue.distance(to: other.representingValue
)
    }

    @_transparent
    public func advanced(by n: RepresentingValue.Stride) -> Self {
        return Self(representingValue: self.representingValue.advanced(by:
n))
    }

    @_transparent
    public func addingReportingOverflow(_ rhs: Self) -> (partialValue:
Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.addingRepo
rtingOverflow(rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }

    @_transparent
    public func subtractingReportingOverflow(_ rhs: Self) ->
(partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.subtractin
gReportingOverflow(rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }

    @_transparent
    public func multipliedReportingOverflow(by rhs: Self) ->
(partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.multiplied
ReportingOverflow(by: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }

    @_transparent
    public func dividedReportingOverflow(by rhs: Self) -> (partialValue:
Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.dividedRep
ortingOverflow(by: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }

    @_transparent
    public func remainderReportingOverflow(dividingBy rhs: Self) ->
(partialValue: Self, overflow: ArithmeticOverflow) {
        let (partialValue, overflow) = representingValue.remainderR
eportingOverflow(dividingBy: rhs.representingValue)
        return (Self(representingValue: partialValue), overflow)
    }

    @_transparent
    public func multipliedFullWidth(by other: Self) -> (high: Self, low:
RepresentingValue.Magnitude) {
        let (high, low) = representingValue.multipliedFullWidth(by: other.
representingValue)
        return (Self(representingValue: high), low)
    }

    @_transparent
    public func dividingFullWidth(_ dividend: (high: Self, low:
RepresentingValue.Magnitude)) -> (quotient: Self, remainder: Self) {
        let (quotient, remainder) = representingValue.dividingFullWidth
((dividend.high.representingValue, dividend.low))
        return (Self(representingValue: quotient), Self(representingValue:
remainder))
    }
}

extension EndianInteger {

    @_transparent
    public static prefix func +(x: Self) -> Self {
        return x
    }

    @_transparent
    public static func +(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue + rhs.
representingValue)
    }

    @_transparent
    public static func +=(lhs: inout Self, rhs: Self) {
        lhs.representingValue += rhs.representingValue
    }

    @_transparent
    public static func -(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue - rhs.
representingValue)
    }

    @_transparent
    public static func -=(lhs: inout Self, rhs: Self) {
        lhs.representingValue -= rhs.representingValue
    }

    @_transparent
    public static func *(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue * rhs.
representingValue)
    }

    @_transparent
    public static func *=(lhs: inout Self, rhs: Self) {
        lhs.representingValue *= rhs.representingValue
    }

    @_transparent
    public static func /(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue / rhs.
representingValue)
    }

    @_transparent
    public static func /=(lhs: inout Self, rhs: Self) {
        lhs.representingValue /= rhs.representingValue
    }

    @_transparent
    public static func %(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue % rhs.
representingValue)
    }

    @_transparent
    public static func %=(lhs: inout Self, rhs: Self) {
        lhs.representingValue %= rhs.representingValue
    }

    @_transparent
    public static func &(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue & rhs.
representingValue)
    }

    @_transparent
    public static func &=(lhs: inout Self, rhs: Self) {
        lhs.representingValue &= rhs.representingValue
    }

    @_transparent
    public static func |(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue | rhs.
representingValue)
    }

    @_transparent
    public static func |=(lhs: inout Self, rhs: Self) {
        lhs.representingValue |= rhs.representingValue
    }

    @_transparent
    public static func ^(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue ^ rhs.
representingValue)
    }

    @_transparent
    public static func ^=(lhs: inout Self, rhs: Self) {
        lhs.representingValue ^= rhs.representingValue
    }

    @_transparent
    prefix public static func ~(x: Self) -> Self {
        return Self(representingValue: ~x.representingValue)
    }

    @_transparent
    public static func &>>(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue &>> rhs.
representingValue)
    }

    @_transparent
    public static func &<<(lhs: Self, rhs: Self) -> Self {
        return Self(representingValue: lhs.representingValue &<< rhs.
representingValue)
    }

    @_transparent
    public static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.bitPattern == rhs.bitPattern
    }

    @_transparent
    public static func !=(lhs: Self, rhs: Self) -> Bool {
        return lhs.bitPattern != rhs.bitPattern
    }

    @_transparent
    public static func >(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue > rhs.representingValue
    }

    @_transparent
    public static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue < rhs.representingValue
    }

    @_transparent
    public static func >=(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue >= rhs.representingValue
    }

    @_transparent
    public static func <=(lhs: Self, rhs: Self) -> Bool {
        return lhs.representingValue <= rhs.representingValue
    }
}

public struct BEInteger<Base : FixedWidthInteger> : FixedWidthInteger,
EndianInteger {

    public var bitPattern: Base

    @_transparent
    public init(bitPattern: Base) {
        self.bitPattern = bitPattern
    }

    @_versioned
    @_transparent
    init(representingValue: Base) {
        self.bitPattern = representingValue.bigEndian
    }

    @_versioned
    @_transparent
    var representingValue: Base {
        get {
            return Base(bigEndian: bitPattern)
        }
        set {
            bitPattern = newValue.bigEndian
        }
    }

    @_transparent
    public init(bigEndian value: BEInteger) {
        self.bitPattern = value.bitPattern
    }

    @_transparent
    public init(littleEndian value: BEInteger) {
        self.bitPattern = value.bitPattern.byteSwapped
    }

    @_transparent
    public var bigEndian: BEInteger {
        return self
    }

    @_transparent
    public var littleEndian: BEInteger {
        return BEInteger(littleEndian: self)
    }
}

public struct LEInteger<Base : FixedWidthInteger> : FixedWidthInteger,
EndianInteger {

    public var bitPattern: Base

    @_transparent
    public init(bitPattern: Base) {
        self.bitPattern = bitPattern
    }

    @_versioned
    @_transparent
    init(representingValue: Base) {
        self.bitPattern = representingValue.littleEndian
    }

    @_versioned
    @_transparent
    var representingValue: Base {
        get {
            return Base(littleEndian: bitPattern)
        }
        set {
            bitPattern = newValue.littleEndian
        }
    }

    @_transparent
    public init(bigEndian value: LEInteger) {
        self.bitPattern = value.bitPattern.byteSwapped
    }

    @_transparent
    public init(littleEndian value: LEInteger) {
        self.bitPattern = value.bitPattern
    }

    @_transparent
    public var bigEndian: LEInteger {
        return LEInteger(bigEndian: self)
    }

    @_transparent
    public var littleEndian: LEInteger {
        return self
    }

}
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #7

Hi Susan,

Swift does not currently specify a layout for Swift structs. You shouldn’t be using them for memory mapped i/o or writing to a file, because their layout can change. When ABI stability for fragile structs lands, you will be able to count on it, but until then something like this is probably a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact with Metal?

No.

This seems to be a very common practice. Here is a typical example (from a Metal tutorial at raywenderlich.com <http://raywenderlich.com/>):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive types) like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that such usage was considered OK provided the stored properties of the structs was only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms are known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to take (e.g.) an unsafepointer to the beginning of a struct, and serialize that out to disk, and expect that the fields are emitted in some order with some specific padding between them. None of the uses above try to do this.

-Chris

···

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com> wrote:
On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Jens Persson) #8

I don't think I'm misunderstanding you, but I might be, so I'll add more
detail:

If you look at the Metal article, you'll see that the (Swift) struct
"Vertex" is used to specify the data that is sent to Metal for creating a
buffer (using MTLDevice.makeBuffer). The result that the GPU will produce
surely depends on the fields of the Vertex struct (x, y, z, r, g, b, a)
being in the specified order (ie swapping the red channel with the x
coordinate would produce an unexpected result).

And regarding the second example, pixel structs used for manipulating
raster image data. Manipulating raster image data presumably includes stuff
like displaying to screen, loading and saving raster images.
I currently use this way of doing this right now without any problems, but
if the order of the fields (eg a, r, g, b) should change in the future,
then my code would break (the colors of the images would at least not come
out as expected).

/Jens

···

On Sun, Jul 9, 2017 at 5:53 PM, Chris Lattner <clattner@nondot.org> wrote:

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com> wrote:

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

Hi Susan,

Swift does not currently specify a layout for Swift structs. You
shouldn’t be using them for memory mapped i/o or writing to a file, because
their layout can change. When ABI stability for fragile structs lands, you
will be able to count on it, but until then something like this is probably
a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact
with Metal?

No.

This seems to be a very common practice. Here is a typical example (from a
Metal tutorial at raywenderlich.com):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive types)
like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that such
usage was considered OK provided the stored properties of the structs was
only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms are
known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to take
(e.g.) an unsafepointer to the beginning of a struct, and serialize that
out to disk, and expect that the fields are emitted in some order with some
specific padding between them. None of the uses above try to do this.

-Chris


(Jens Persson) #9

I should perhaps add that in my image processing code, I use code like this:

func withVImageBuffer<Data, R>(for table: Table<Data>, body:
(vImage_Buffer) -> R) -> R
    where
    Data.Coordinate.Index == VectorIndex2
{
    let vib = vImage_Buffer(
        data: table.baseAddress,
        height: vImagePixelCount(table.size.e1),
        width: vImagePixelCount(table.size.e0),
        rowBytes: table.stride.e1
    )
    return withExtendedLifetime(table) { body(vib) }
}

Here, Table<Data> is the raster image. Data.Coordinate == VectorIndex2
makes it a 2D table, and a Table's Data also has a type parameter
Data.Value which can be eg one of the "pixel"-struct I showed before.
This works without any problems (I've tested and used the some variant of
this type of code for years) but it would surely break if the memory layout
of simple structs changed.

I can't see how this usage is much different from the one in the Metal
tutorial. It too uses pointers to point into a data created using the
(Swift) struct "Vertex", and the GPU hardware has its expectations on the
memory layout of that data, so the code would break if the memory layout of
the Vertex struct changed.

/Jens

···

On Sun, Jul 9, 2017 at 6:35 PM, Jens Persson <jens@bitcycle.com> wrote:

I don't think I'm misunderstanding you, but I might be, so I'll add more
detail:

If you look at the Metal article, you'll see that the (Swift) struct
"Vertex" is used to specify the data that is sent to Metal for creating a
buffer (using MTLDevice.makeBuffer). The result that the GPU will produce
surely depends on the fields of the Vertex struct (x, y, z, r, g, b, a)
being in the specified order (ie swapping the red channel with the x
coordinate would produce an unexpected result).

And regarding the second example, pixel structs used for manipulating
raster image data. Manipulating raster image data presumably includes stuff
like displaying to screen, loading and saving raster images.
I currently use this way of doing this right now without any problems, but
if the order of the fields (eg a, r, g, b) should change in the future,
then my code would break (the colors of the images would at least not come
out as expected).

/Jens

On Sun, Jul 9, 2017 at 5:53 PM, Chris Lattner <clattner@nondot.org> wrote:

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com> wrote:

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution < >> swift-evolution@swift.org> wrote:

Hi Susan,

Swift does not currently specify a layout for Swift structs. You
shouldn’t be using them for memory mapped i/o or writing to a file, because
their layout can change. When ABI stability for fragile structs lands, you
will be able to count on it, but until then something like this is probably
a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact
with Metal?

No.

This seems to be a very common practice. Here is a typical example (from
a Metal tutorial at raywenderlich.com):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive types)
like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that
such usage was considered OK provided the stored properties of the structs
was only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms
are known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to
take (e.g.) an unsafepointer to the beginning of a struct, and serialize
that out to disk, and expect that the fields are emitted in some order with
some specific padding between them. None of the uses above try to do this.

-Chris


(Jens Persson) #10

Sorry for making so much off topic noise in this thread, but I made a
mistake regarding the Metal tutorial:
Looking more carefully I see now that they rebuild a vertedData: [Float]
from their vertices: [Vertex] using the floatBuffer() method of the Vertex
struct, which returns an Array with the stored properties of Vertex in
correct order.

While searching the internet about this I saw Joe Groff mentioning on
Twitter that:
"We plan to sort fields in padding order to minimize size, and may also
automatically pack bools and enums in bitfields."

So AFAICS my current image processing code is making the possibly invalid
assumption that eg
struct S {
    var a, b, c, d: Float
}
will have a memory layout of 4*4=16 bytes (stride and size == 16) and an
alignment of 4, and most importantly that a, b, c, d will be in that order.

It looks like I should be defining my structs (the ones for which memory
layout is important) in C and import them.

Although I would be surprised if a Swift-struct containing only same-sized
fields (all of the same primitive type) would be reordered, and such
changes to the language would probably include some per-struct way to
express some sort of layout control (since being able to define structs to
be used for low level data manipulation is important in a systems language).

/Jens

···

On Sun, Jul 9, 2017 at 7:01 PM, Jens Persson via swift-evolution < swift-evolution@swift.org> wrote:

I should perhaps add that in my image processing code, I use code like
this:

func withVImageBuffer<Data, R>(for table: Table<Data>, body:
(vImage_Buffer) -> R) -> R
    where
    Data.Coordinate.Index == VectorIndex2
{
    let vib = vImage_Buffer(
        data: table.baseAddress,
        height: vImagePixelCount(table.size.e1),
        width: vImagePixelCount(table.size.e0),
        rowBytes: table.stride.e1
    )
    return withExtendedLifetime(table) { body(vib) }
}

Here, Table<Data> is the raster image. Data.Coordinate == VectorIndex2
makes it a 2D table, and a Table's Data also has a type parameter
Data.Value which can be eg one of the "pixel"-struct I showed before.
This works without any problems (I've tested and used the some variant of
this type of code for years) but it would surely break if the memory layout
of simple structs changed.

I can't see how this usage is much different from the one in the Metal
tutorial. It too uses pointers to point into a data created using the
(Swift) struct "Vertex", and the GPU hardware has its expectations on the
memory layout of that data, so the code would break if the memory layout of
the Vertex struct changed.

/Jens

On Sun, Jul 9, 2017 at 6:35 PM, Jens Persson <jens@bitcycle.com> wrote:

I don't think I'm misunderstanding you, but I might be, so I'll add more
detail:

If you look at the Metal article, you'll see that the (Swift) struct
"Vertex" is used to specify the data that is sent to Metal for creating a
buffer (using MTLDevice.makeBuffer). The result that the GPU will
produce surely depends on the fields of the Vertex struct (x, y, z, r, g,
b, a) being in the specified order (ie swapping the red channel with the x
coordinate would produce an unexpected result).

And regarding the second example, pixel structs used for manipulating
raster image data. Manipulating raster image data presumably includes stuff
like displaying to screen, loading and saving raster images.
I currently use this way of doing this right now without any problems,
but if the order of the fields (eg a, r, g, b) should change in the future,
then my code would break (the colors of the images would at least not come
out as expected).

/Jens

On Sun, Jul 9, 2017 at 5:53 PM, Chris Lattner <clattner@nondot.org> >> wrote:

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com> wrote:

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution < >>> swift-evolution@swift.org> wrote:

Hi Susan,

Swift does not currently specify a layout for Swift structs. You
shouldn’t be using them for memory mapped i/o or writing to a file, because
their layout can change. When ABI stability for fragile structs lands, you
will be able to count on it, but until then something like this is probably
a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact
with Metal?

No.

This seems to be a very common practice. Here is a typical example (from
a Metal tutorial at raywenderlich.com):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive types)
like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that
such usage was considered OK provided the stored properties of the structs
was only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms
are known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to
take (e.g.) an unsafepointer to the beginning of a struct, and serialize
that out to disk, and expect that the fields are emitted in some order with
some specific padding between them. None of the uses above try to do this.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(John McCall) #11

Sorry for making so much off topic noise in this thread, but I made a mistake regarding the Metal tutorial:
Looking more carefully I see now that they rebuild a vertedData: [Float] from their vertices: [Vertex] using the floatBuffer() method of the Vertex struct, which returns an Array with the stored properties of Vertex in correct order.

While searching the internet about this I saw Joe Groff mentioning on Twitter that:
"We plan to sort fields in padding order to minimize size, and may also automatically pack bools and enums in bitfields."

So AFAICS my current image processing code is making the possibly invalid assumption that eg
struct S {
    var a, b, c, d: Float
}
will have a memory layout of 4*4=16 bytes (stride and size == 16) and an alignment of 4, and most importantly that a, b, c, d will be in that order.

This is currently true, but may not always be. We want to reserve the right to re-order the fields even if it doesn't improve packing — for example, if two fields are frequently accessed together, field reordering could yield substantial locality benefits. We've also discussed reordering fields to put them in a canonical order for resilience, since it's a little counter-intuitive that reordering the fields of a struct should be ABI-breaking. (There are arguments against doing that as well, of course — for example, the programmer may have chosen the current order for their own locality optimizations.)

It looks like I should be defining my structs (the ones for which memory layout is important) in C and import them.

This should always work, yes.

Although I would be surprised if a Swift-struct containing only same-sized fields (all of the same primitive type) would be reordered, and such changes to the language would probably include some per-struct way to express some sort of layout control (since being able to define structs to be used for low level data manipulation is important in a systems language).

Exactly. In the long term, Swift will have some explicit tools for layout control.

John.

···

On Jul 9, 2017, at 4:49 PM, Jens Persson via swift-evolution <swift-evolution@swift.org> wrote:

/Jens

On Sun, Jul 9, 2017 at 7:01 PM, Jens Persson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I should perhaps add that in my image processing code, I use code like this:

func withVImageBuffer<Data, R>(for table: Table<Data>, body: (vImage_Buffer) -> R) -> R
    where
    Data.Coordinate.Index == VectorIndex2
{
    let vib = vImage_Buffer(
        data: table.baseAddress,
        height: vImagePixelCount(table.size.e1),
        width: vImagePixelCount(table.size.e0),
        rowBytes: table.stride.e1
    )
    return withExtendedLifetime(table) { body(vib) }
}

Here, Table<Data> is the raster image. Data.Coordinate == VectorIndex2 makes it a 2D table, and a Table's Data also has a type parameter Data.Value which can be eg one of the "pixel"-struct I showed before.
This works without any problems (I've tested and used the some variant of this type of code for years) but it would surely break if the memory layout of simple structs changed.

I can't see how this usage is much different from the one in the Metal tutorial. It too uses pointers to point into a data created using the (Swift) struct "Vertex", and the GPU hardware has its expectations on the memory layout of that data, so the code would break if the memory layout of the Vertex struct changed.

/Jens

On Sun, Jul 9, 2017 at 6:35 PM, Jens Persson <jens@bitcycle.com <mailto:jens@bitcycle.com>> wrote:
I don't think I'm misunderstanding you, but I might be, so I'll add more detail:

If you look at the Metal article, you'll see that the (Swift) struct "Vertex" is used to specify the data that is sent to Metal for creating a buffer (using MTLDevice.makeBuffer). The result that the GPU will produce surely depends on the fields of the Vertex struct (x, y, z, r, g, b, a) being in the specified order (ie swapping the red channel with the x coordinate would produce an unexpected result).

And regarding the second example, pixel structs used for manipulating raster image data. Manipulating raster image data presumably includes stuff like displaying to screen, loading and saving raster images.
I currently use this way of doing this right now without any problems, but if the order of the fields (eg a, r, g, b) should change in the future, then my code would break (the colors of the images would at least not come out as expected).

/Jens

On Sun, Jul 9, 2017 at 5:53 PM, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com <mailto:jens@bitcycle.com>> wrote:

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi Susan,

Swift does not currently specify a layout for Swift structs. You shouldn’t be using them for memory mapped i/o or writing to a file, because their layout can change. When ABI stability for fragile structs lands, you will be able to count on it, but until then something like this is probably a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact with Metal?

No.

This seems to be a very common practice. Here is a typical example (from a Metal tutorial at raywenderlich.com <http://raywenderlich.com/>):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive types) like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that such usage was considered OK provided the stored properties of the structs was only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms are known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to take (e.g.) an unsafepointer to the beginning of a struct, and serialize that out to disk, and expect that the fields are emitted in some order with some specific padding between them. None of the uses above try to do this.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jens Persson) #12

Thanks for that clarification John McCall.
My code is using a lot of generic structs (in which memory layout is
important) though, an example would be:
struct Vector4<E> : Vector {
    typealias Index = VectorIndex4
    typealias Element = E
    var e0, e1, e2, e3: Element
    …
}
And I guess I'm out of luck trying to represent those as C structs?
So AFAICS it looks like it is currently impossible to write generic low
level code in Swift, unless I just keep doing what I've been doing (It does
currently work after all) knowing that it will probably break in some
future versions of Swift. But in that possible future version of Swift, I
could probably find a way to make it work again (using some possible
explicit tools for layout control present in that version of Swift).
Correct?
/Jens

···

On Sun, Jul 9, 2017 at 11:41 PM, John McCall <rjmccall@apple.com> wrote:

On Jul 9, 2017, at 4:49 PM, Jens Persson via swift-evolution < > swift-evolution@swift.org> wrote:

Sorry for making so much off topic noise in this thread, but I made a
mistake regarding the Metal tutorial:
Looking more carefully I see now that they rebuild a vertedData: [Float]
from their vertices: [Vertex] using the floatBuffer() method of the Vertex
struct, which returns an Array with the stored properties of Vertex in
correct order.

While searching the internet about this I saw Joe Groff mentioning on
Twitter that:
"We plan to sort fields in padding order to minimize size, and may also
automatically pack bools and enums in bitfields."

So AFAICS my current image processing code is making the possibly invalid
assumption that eg
struct S {
    var a, b, c, d: Float
}
will have a memory layout of 4*4=16 bytes (stride and size == 16) and an
alignment of 4, and most importantly that a, b, c, d will be in that order.

This is currently true, but may not always be. We want to reserve the
right to re-order the fields even if it doesn't improve packing — for
example, if two fields are frequently accessed together, field reordering
could yield substantial locality benefits. We've also discussed reordering
fields to put them in a canonical order for resilience, since it's a little
counter-intuitive that reordering the fields of a struct should be
ABI-breaking. (There are arguments against doing that as well, of course —
for example, the programmer may have chosen the current order for their own
locality optimizations.)

It looks like I should be defining my structs (the ones for which memory
layout is important) in C and import them.

This should always work, yes.

Although I would be surprised if a Swift-struct containing only same-sized
fields (all of the same primitive type) would be reordered, and such
changes to the language would probably include some per-struct way to
express some sort of layout control (since being able to define structs to
be used for low level data manipulation is important in a systems language).

Exactly. In the long term, Swift will have some explicit tools for layout
control.

John.

/Jens

On Sun, Jul 9, 2017 at 7:01 PM, Jens Persson via swift-evolution < > swift-evolution@swift.org> wrote:

I should perhaps add that in my image processing code, I use code like
this:

func withVImageBuffer<Data, R>(for table: Table<Data>, body:
(vImage_Buffer) -> R) -> R
    where
    Data.Coordinate.Index == VectorIndex2
{
    let vib = vImage_Buffer(
        data: table.baseAddress,
        height: vImagePixelCount(table.size.e1),
        width: vImagePixelCount(table.size.e0),
        rowBytes: table.stride.e1
    )
    return withExtendedLifetime(table) { body(vib) }
}

Here, Table<Data> is the raster image. Data.Coordinate == VectorIndex2
makes it a 2D table, and a Table's Data also has a type parameter
Data.Value which can be eg one of the "pixel"-struct I showed before.
This works without any problems (I've tested and used the some variant of
this type of code for years) but it would surely break if the memory layout
of simple structs changed.

I can't see how this usage is much different from the one in the Metal
tutorial. It too uses pointers to point into a data created using the
(Swift) struct "Vertex", and the GPU hardware has its expectations on the
memory layout of that data, so the code would break if the memory layout of
the Vertex struct changed.

/Jens

On Sun, Jul 9, 2017 at 6:35 PM, Jens Persson <jens@bitcycle.com> wrote:

I don't think I'm misunderstanding you, but I might be, so I'll add more
detail:

If you look at the Metal article, you'll see that the (Swift) struct
"Vertex" is used to specify the data that is sent to Metal for creating a
buffer (using MTLDevice.makeBuffer). The result that the GPU will
produce surely depends on the fields of the Vertex struct (x, y, z, r, g,
b, a) being in the specified order (ie swapping the red channel with the x
coordinate would produce an unexpected result).

And regarding the second example, pixel structs used for manipulating
raster image data. Manipulating raster image data presumably includes stuff
like displaying to screen, loading and saving raster images.
I currently use this way of doing this right now without any problems,
but if the order of the fields (eg a, r, g, b) should change in the future,
then my code would break (the colors of the images would at least not come
out as expected).

/Jens

On Sun, Jul 9, 2017 at 5:53 PM, Chris Lattner <clattner@nondot.org> >>> wrote:

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com> wrote:

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution < >>>> swift-evolution@swift.org> wrote:

Hi Susan,

Swift does not currently specify a layout for Swift structs. You
shouldn’t be using them for memory mapped i/o or writing to a file, because
their layout can change. When ABI stability for fragile structs lands, you
will be able to count on it, but until then something like this is probably
a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact
with Metal?

No.

This seems to be a very common practice. Here is a typical example
(from a Metal tutorial at raywenderlich.com):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive
types) like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that
such usage was considered OK provided the stored properties of the structs
was only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms
are known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to
take (e.g.) an unsafepointer to the beginning of a struct, and serialize
that out to disk, and expect that the fields are emitted in some order with
some specific padding between them. None of the uses above try to do this.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(John McCall) #13

Thanks for that clarification John McCall.
My code is using a lot of generic structs (in which memory layout is important) though, an example would be:
struct Vector4<E> : Vector {
    typealias Index = VectorIndex4
    typealias Element = E
    var e0, e1, e2, e3: Element
    …
}
And I guess I'm out of luck trying to represent those as C structs?
So AFAICS it looks like it is currently impossible to write generic low level code in Swift, unless I just keep doing what I've been doing (It does currently work after all) knowing that it will probably break in some future versions of Swift. But in that possible future version of Swift, I could probably find a way to make it work again (using some possible explicit tools for layout control present in that version of Swift).
Correct?

Correct.

John.

···

On Jul 9, 2017, at 6:14 PM, Jens Persson <jens@bitcycle.com> wrote:

/Jens

On Sun, Jul 9, 2017 at 11:41 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jul 9, 2017, at 4:49 PM, Jens Persson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sorry for making so much off topic noise in this thread, but I made a mistake regarding the Metal tutorial:
Looking more carefully I see now that they rebuild a vertedData: [Float] from their vertices: [Vertex] using the floatBuffer() method of the Vertex struct, which returns an Array with the stored properties of Vertex in correct order.

While searching the internet about this I saw Joe Groff mentioning on Twitter that:
"We plan to sort fields in padding order to minimize size, and may also automatically pack bools and enums in bitfields."

So AFAICS my current image processing code is making the possibly invalid assumption that eg
struct S {
    var a, b, c, d: Float
}
will have a memory layout of 4*4=16 bytes (stride and size == 16) and an alignment of 4, and most importantly that a, b, c, d will be in that order.

This is currently true, but may not always be. We want to reserve the right to re-order the fields even if it doesn't improve packing — for example, if two fields are frequently accessed together, field reordering could yield substantial locality benefits. We've also discussed reordering fields to put them in a canonical order for resilience, since it's a little counter-intuitive that reordering the fields of a struct should be ABI-breaking. (There are arguments against doing that as well, of course — for example, the programmer may have chosen the current order for their own locality optimizations.)

It looks like I should be defining my structs (the ones for which memory layout is important) in C and import them.

This should always work, yes.

Although I would be surprised if a Swift-struct containing only same-sized fields (all of the same primitive type) would be reordered, and such changes to the language would probably include some per-struct way to express some sort of layout control (since being able to define structs to be used for low level data manipulation is important in a systems language).

Exactly. In the long term, Swift will have some explicit tools for layout control.

John.

/Jens

On Sun, Jul 9, 2017 at 7:01 PM, Jens Persson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I should perhaps add that in my image processing code, I use code like this:

func withVImageBuffer<Data, R>(for table: Table<Data>, body: (vImage_Buffer) -> R) -> R
    where
    Data.Coordinate.Index == VectorIndex2
{
    let vib = vImage_Buffer(
        data: table.baseAddress,
        height: vImagePixelCount(table.size.e1),
        width: vImagePixelCount(table.size.e0),
        rowBytes: table.stride.e1
    )
    return withExtendedLifetime(table) { body(vib) }
}

Here, Table<Data> is the raster image. Data.Coordinate == VectorIndex2 makes it a 2D table, and a Table's Data also has a type parameter Data.Value which can be eg one of the "pixel"-struct I showed before.
This works without any problems (I've tested and used the some variant of this type of code for years) but it would surely break if the memory layout of simple structs changed.

I can't see how this usage is much different from the one in the Metal tutorial. It too uses pointers to point into a data created using the (Swift) struct "Vertex", and the GPU hardware has its expectations on the memory layout of that data, so the code would break if the memory layout of the Vertex struct changed.

/Jens

On Sun, Jul 9, 2017 at 6:35 PM, Jens Persson <jens@bitcycle.com <mailto:jens@bitcycle.com>> wrote:
I don't think I'm misunderstanding you, but I might be, so I'll add more detail:

If you look at the Metal article, you'll see that the (Swift) struct "Vertex" is used to specify the data that is sent to Metal for creating a buffer (using MTLDevice.makeBuffer). The result that the GPU will produce surely depends on the fields of the Vertex struct (x, y, z, r, g, b, a) being in the specified order (ie swapping the red channel with the x coordinate would produce an unexpected result).

And regarding the second example, pixel structs used for manipulating raster image data. Manipulating raster image data presumably includes stuff like displaying to screen, loading and saving raster images.
I currently use this way of doing this right now without any problems, but if the order of the fields (eg a, r, g, b) should change in the future, then my code would break (the colors of the images would at least not come out as expected).

/Jens

On Sun, Jul 9, 2017 at 5:53 PM, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com <mailto:jens@bitcycle.com>> wrote:

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi Susan,

Swift does not currently specify a layout for Swift structs. You shouldn’t be using them for memory mapped i/o or writing to a file, because their layout can change. When ABI stability for fragile structs lands, you will be able to count on it, but until then something like this is probably a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact with Metal?

No.

This seems to be a very common practice. Here is a typical example (from a Metal tutorial at raywenderlich.com <http://raywenderlich.com/>):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive types) like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that such usage was considered OK provided the stored properties of the structs was only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms are known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to take (e.g.) an unsafepointer to the beginning of a struct, and serialize that out to disk, and expect that the fields are emitted in some order with some specific padding between them. None of the uses above try to do this.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


#14

Just a question: how would/does allowing the reordering of fields affect the correctness and performance of the (de)serialization API added in Swift 4?

···

On Jul 9, 2017, at 6:21 PM, Jens Persson via swift-evolution <swift-evolution@swift.org> wrote:

Thanks for that clarification John McCall.
My code is using a lot of generic structs (in which memory layout is important) though, an example would be:
struct Vector4<E> : Vector {
    typealias Index = VectorIndex4
    typealias Element = E
    var e0, e1, e2, e3: Element
    …
}
And I guess I'm out of luck trying to represent those as C structs?
So AFAICS it looks like it is currently impossible to write generic low level code in Swift, unless I just keep doing what I've been doing (It does currently work after all) knowing that it will probably break in some future versions of Swift. But in that possible future version of Swift, I could probably find a way to make it work again (using some possible explicit tools for layout control present in that version of Swift).
Correct?
/Jens

On Sun, Jul 9, 2017 at 11:41 PM, John McCall <rjmccall@apple.com> wrote:

On Jul 9, 2017, at 4:49 PM, Jens Persson via swift-evolution <swift-evolution@swift.org> wrote:

Sorry for making so much off topic noise in this thread, but I made a mistake regarding the Metal tutorial:
Looking more carefully I see now that they rebuild a vertedData: [Float] from their vertices: [Vertex] using the floatBuffer() method of the Vertex struct, which returns an Array with the stored properties of Vertex in correct order.

While searching the internet about this I saw Joe Groff mentioning on Twitter that:
"We plan to sort fields in padding order to minimize size, and may also automatically pack bools and enums in bitfields."

So AFAICS my current image processing code is making the possibly invalid assumption that eg
struct S {
    var a, b, c, d: Float
}
will have a memory layout of 4*4=16 bytes (stride and size == 16) and an alignment of 4, and most importantly that a, b, c, d will be in that order.

This is currently true, but may not always be. We want to reserve the right to re-order the fields even if it doesn't improve packing — for example, if two fields are frequently accessed together, field reordering could yield substantial locality benefits. We've also discussed reordering fields to put them in a canonical order for resilience, since it's a little counter-intuitive that reordering the fields of a struct should be ABI-breaking. (There are arguments against doing that as well, of course — for example, the programmer may have chosen the current order for their own locality optimizations.)

It looks like I should be defining my structs (the ones for which memory layout is important) in C and import them.

This should always work, yes.

Although I would be surprised if a Swift-struct containing only same-sized fields (all of the same primitive type) would be reordered, and such changes to the language would probably include some per-struct way to express some sort of layout control (since being able to define structs to be used for low level data manipulation is important in a systems language).

Exactly. In the long term, Swift will have some explicit tools for layout control.

John.

/Jens

On Sun, Jul 9, 2017 at 7:01 PM, Jens Persson via swift-evolution <swift-evolution@swift.org> wrote:
I should perhaps add that in my image processing code, I use code like this:

func withVImageBuffer<Data, R>(for table: Table<Data>, body: (vImage_Buffer) -> R) -> R
    where
    Data.Coordinate.Index == VectorIndex2
{
    let vib = vImage_Buffer(
        data: table.baseAddress,
        height: vImagePixelCount(table.size.e1),
        width: vImagePixelCount(table.size.e0),
        rowBytes: table.stride.e1
    )
    return withExtendedLifetime(table) { body(vib) }
}

Here, Table<Data> is the raster image. Data.Coordinate == VectorIndex2 makes it a 2D table, and a Table's Data also has a type parameter Data.Value which can be eg one of the "pixel"-struct I showed before.
This works without any problems (I've tested and used the some variant of this type of code for years) but it would surely break if the memory layout of simple structs changed.

I can't see how this usage is much different from the one in the Metal tutorial. It too uses pointers to point into a data created using the (Swift) struct "Vertex", and the GPU hardware has its expectations on the memory layout of that data, so the code would break if the memory layout of the Vertex struct changed.

/Jens

On Sun, Jul 9, 2017 at 6:35 PM, Jens Persson <jens@bitcycle.com> wrote:

I don't think I'm misunderstanding you, but I might be, so I'll add more detail:

If you look at the Metal article, you'll see that the (Swift) struct "Vertex" is used to specify the data that is sent to Metal for creating a buffer (using MTLDevice.makeBuffer). The result that the GPU will produce surely depends on the fields of the Vertex struct (x, y, z, r, g, b, a) being in the specified order (ie swapping the red channel with the x coordinate would produce an unexpected result).

And regarding the second example, pixel structs used for manipulating raster image data. Manipulating raster image data presumably includes stuff like displaying to screen, loading and saving raster images.
I currently use this way of doing this right now without any problems, but if the order of the fields (eg a, r, g, b) should change in the future, then my code would break (the colors of the images would at least not come out as expected).

/Jens

On Sun, Jul 9, 2017 at 5:53 PM, Chris Lattner <clattner@nondot.org> wrote:

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com> wrote:

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:
Hi Susan,

Swift does not currently specify a layout for Swift structs. You shouldn’t be using them for memory mapped i/o or writing to a file, because their layout can change. When ABI stability for fragile structs lands, you will be able to count on it, but until then something like this is probably a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact with Metal?

No.

This seems to be a very common practice. Here is a typical example (from a Metal tutorial at raywenderlich.com):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive types) like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that such usage was considered OK provided the stored properties of the structs was only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms are known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to take (e.g.) an unsafepointer to the beginning of a struct, and serialize that out to disk, and expect that the fields are emitted in some order with some specific padding between them. None of the uses above try to do this.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(John McCall) #15

Just a question: how would/does allowing the reordering of fields affect the correctness and performance of the (de)serialization API added in Swift 4?

The design of that is definitely proof against changes to the in-memory layout of the type. I believe it's also proof against reordering but I'm not entirely certain about that.

John.

···

On Jul 9, 2017, at 6:57 PM, Robert Bennett <rltbennett@icloud.com> wrote:

On Jul 9, 2017, at 6:21 PM, Jens Persson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thanks for that clarification John McCall.
My code is using a lot of generic structs (in which memory layout is important) though, an example would be:
struct Vector4<E> : Vector {
    typealias Index = VectorIndex4
    typealias Element = E
    var e0, e1, e2, e3: Element
    …
}
And I guess I'm out of luck trying to represent those as C structs?
So AFAICS it looks like it is currently impossible to write generic low level code in Swift, unless I just keep doing what I've been doing (It does currently work after all) knowing that it will probably break in some future versions of Swift. But in that possible future version of Swift, I could probably find a way to make it work again (using some possible explicit tools for layout control present in that version of Swift).
Correct?
/Jens

On Sun, Jul 9, 2017 at 11:41 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jul 9, 2017, at 4:49 PM, Jens Persson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sorry for making so much off topic noise in this thread, but I made a mistake regarding the Metal tutorial:
Looking more carefully I see now that they rebuild a vertedData: [Float] from their vertices: [Vertex] using the floatBuffer() method of the Vertex struct, which returns an Array with the stored properties of Vertex in correct order.

While searching the internet about this I saw Joe Groff mentioning on Twitter that:
"We plan to sort fields in padding order to minimize size, and may also automatically pack bools and enums in bitfields."

So AFAICS my current image processing code is making the possibly invalid assumption that eg
struct S {
    var a, b, c, d: Float
}
will have a memory layout of 4*4=16 bytes (stride and size == 16) and an alignment of 4, and most importantly that a, b, c, d will be in that order.

This is currently true, but may not always be. We want to reserve the right to re-order the fields even if it doesn't improve packing — for example, if two fields are frequently accessed together, field reordering could yield substantial locality benefits. We've also discussed reordering fields to put them in a canonical order for resilience, since it's a little counter-intuitive that reordering the fields of a struct should be ABI-breaking. (There are arguments against doing that as well, of course — for example, the programmer may have chosen the current order for their own locality optimizations.)

It looks like I should be defining my structs (the ones for which memory layout is important) in C and import them.

This should always work, yes.

Although I would be surprised if a Swift-struct containing only same-sized fields (all of the same primitive type) would be reordered, and such changes to the language would probably include some per-struct way to express some sort of layout control (since being able to define structs to be used for low level data manipulation is important in a systems language).

Exactly. In the long term, Swift will have some explicit tools for layout control.

John.

/Jens

On Sun, Jul 9, 2017 at 7:01 PM, Jens Persson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I should perhaps add that in my image processing code, I use code like this:

func withVImageBuffer<Data, R>(for table: Table<Data>, body: (vImage_Buffer) -> R) -> R
    where
    Data.Coordinate.Index == VectorIndex2
{
    let vib = vImage_Buffer(
        data: table.baseAddress,
        height: vImagePixelCount(table.size.e1),
        width: vImagePixelCount(table.size.e0),
        rowBytes: table.stride.e1
    )
    return withExtendedLifetime(table) { body(vib) }
}

Here, Table<Data> is the raster image. Data.Coordinate == VectorIndex2 makes it a 2D table, and a Table's Data also has a type parameter Data.Value which can be eg one of the "pixel"-struct I showed before.
This works without any problems (I've tested and used the some variant of this type of code for years) but it would surely break if the memory layout of simple structs changed.

I can't see how this usage is much different from the one in the Metal tutorial. It too uses pointers to point into a data created using the (Swift) struct "Vertex", and the GPU hardware has its expectations on the memory layout of that data, so the code would break if the memory layout of the Vertex struct changed.

/Jens

On Sun, Jul 9, 2017 at 6:35 PM, Jens Persson <jens@bitcycle.com <mailto:jens@bitcycle.com>> wrote:
I don't think I'm misunderstanding you, but I might be, so I'll add more detail:

If you look at the Metal article, you'll see that the (Swift) struct "Vertex" is used to specify the data that is sent to Metal for creating a buffer (using MTLDevice.makeBuffer). The result that the GPU will produce surely depends on the fields of the Vertex struct (x, y, z, r, g, b, a) being in the specified order (ie swapping the red channel with the x coordinate would produce an unexpected result).

And regarding the second example, pixel structs used for manipulating raster image data. Manipulating raster image data presumably includes stuff like displaying to screen, loading and saving raster images.
I currently use this way of doing this right now without any problems, but if the order of the fields (eg a, r, g, b) should change in the future, then my code would break (the colors of the images would at least not come out as expected).

/Jens

On Sun, Jul 9, 2017 at 5:53 PM, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:

On Jul 9, 2017, at 12:23 AM, Jens Persson <jens@bitcycle.com <mailto:jens@bitcycle.com>> wrote:

On Sat, Jul 8, 2017 at 6:28 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi Susan,

Swift does not currently specify a layout for Swift structs. You shouldn’t be using them for memory mapped i/o or writing to a file, because their layout can change. When ABI stability for fragile structs lands, you will be able to count on it, but until then something like this is probably a bad idea.

-Chris

Does this imply that you should never use Swift structs to eg interact with Metal?

No.

This seems to be a very common practice. Here is a typical example (from a Metal tutorial at raywenderlich.com <http://raywenderlich.com/>):

struct Vertex {
  var x,y,z: Float // position data
  var r,g,b,a: Float // color data

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a]
  }
}

This doesn’t appear to expose the layout of the struct.

Also, does it imply that we cannot use structs (of only primitive types) like:

struct RgbaFloatsLinearGamma {
    var r, g, b, a: Float
    …
}
struct BgraBytesSrgbGamma {
    var b, g, r, a: UInt8
}

for manipulating raster image data?

I don’t see why that would be a problem.

I vaguely remember a swift evo discussion where it was concluded that such usage was considered OK provided the stored properties of the structs was only primitive types, but I can't find it now.

Perhaps it could be considered OK at least when the intended platforms are known to be only iOS devices?

I think you’re misunderstanding what I’m saying. It isn’t correct to take (e.g.) an unsafepointer to the beginning of a struct, and serialize that out to disk, and expect that the fields are emitted in some order with some specific padding between them. None of the uses above try to do this.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution