From c24e0349e1bbdff74aa88908cdfd39bcd9d4fe1b Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Tue, 8 Oct 2024 16:13:23 -0700 Subject: [PATCH] Add Projection associated type to BitField (#130) Updates the `BitField` associated type to require a `Projection` associated type which conforms to `BitFieldProjectable`. Renames existing `insert` and `extract` methods to `insertBits` and `extractBits` respectively. Adds new typed variants of `insert` and `extract` which operate on `Projection`. The result of this change is that `preconditionMatchingBitWidth` can be hidden from the MMIO module's public API and the macro expansion code is simpler. --- Sources/MMIO/BitField.swift | 55 ++++++++- Sources/MMIO/BitFieldProjectable.swift | 26 ----- .../Descriptions/BitFieldDescription.swift | 21 ++-- .../MMIOFileCheckTestCase.swift | 19 ++-- .../Macros/RegisterMacroTests.swift | 104 +++++++++--------- 5 files changed, 124 insertions(+), 101 deletions(-) diff --git a/Sources/MMIO/BitField.swift b/Sources/MMIO/BitField.swift index 23ba188..4d57cd8 100644 --- a/Sources/MMIO/BitField.swift +++ b/Sources/MMIO/BitField.swift @@ -99,10 +99,53 @@ extension FixedWidthInteger { public protocol BitField { associatedtype Storage: FixedWidthInteger & UnsignedInteger + associatedtype Projection: BitFieldProjectable + static var bitWidth: Int { get } - static func insert(_ value: Storage, into storage: inout Storage) - static func extract(from storage: Storage) -> Storage + static func insertBits(_ value: Storage, into storage: inout Storage) + static func extractBits(from storage: Storage) -> Storage + + static func insert(_ value: Projection, into storage: inout Storage) + static func extract(from storage: Storage) -> Projection +} + +extension BitField { + @inlinable @inline(__always) + static func preconditionMatchingBitWidth( + file: StaticString = #file, + line: UInt = #line + ) { + #if hasFeature(Embedded) + // FIXME: Embedded doesn't have static interpolated strings yet + precondition( + Self.bitWidth == Projection.bitWidth, + "Illegal projection of bit-field as type of differing bit-width", + file: file, + line: line) + #else + precondition( + Self.bitWidth == Projection.bitWidth, + """ + Illegal projection of \(Self.bitWidth) bit bit-field '\(Self.self)' \ + as \(Projection.bitWidth) bit type '\(Projection.self)' + """, + file: file, + line: line) + #endif + } + + @inlinable @inline(__always) + public static func insert(_ value: Projection, into storage: inout Storage) { + Self.preconditionMatchingBitWidth() + Self.insertBits(value.storage(Storage.self), into: &storage) + } + + @inlinable @inline(__always) + public static func extract(from storage: Self.Storage) -> Projection { + Self.preconditionMatchingBitWidth() + return Projection(storage: Self.extractBits(from: storage)) + } } public protocol ContiguousBitField: BitField { @@ -125,12 +168,12 @@ extension ContiguousBitField { // FIXME: value.bitWidth <= Self.bitWidth <= Storage.bitWidth @inlinable @inline(__always) - public static func insert(_ value: Storage, into storage: inout Storage) { + public static func insertBits(_ value: Storage, into storage: inout Storage) { storage[bits: Self.bitRange] = value } @inlinable @inline(__always) - public static func extract(from storage: Storage) -> Storage { + public static func extractBits(from storage: Storage) -> Storage { storage[bits: Self.bitRange] } } @@ -152,12 +195,12 @@ extension DiscontiguousBitField { } @inlinable @inline(__always) - public static func insert(_ value: Storage, into storage: inout Storage) { + public static func insertBits(_ value: Storage, into storage: inout Storage) { storage[bits: Self.bitRanges] = value } @inlinable @inline(__always) - public static func extract(from storage: Storage) -> Storage { + public static func extractBits(from storage: Storage) -> Storage { storage[bits: Self.bitRanges] } } diff --git a/Sources/MMIO/BitFieldProjectable.swift b/Sources/MMIO/BitFieldProjectable.swift index f6c5a73..3e132f9 100644 --- a/Sources/MMIO/BitFieldProjectable.swift +++ b/Sources/MMIO/BitFieldProjectable.swift @@ -186,29 +186,3 @@ where Self: RawRepresentable, RawValue: FixedWidthInteger { return Storage(rawValue) } } - -@inlinable @inline(__always) -public func preconditionMatchingBitWidth( - _ fieldType: (some BitField).Type, - _ projectedType: (some BitFieldProjectable).Type, - file: StaticString = #file, - line: UInt = #line -) { - #if hasFeature(Embedded) - // FIXME: Embedded doesn't have static interpolated strings yet - precondition( - fieldType.bitWidth == projectedType.bitWidth, - "Illegal projection of bit-field as type of differing bit-width", - file: file, - line: line) - #else - precondition( - fieldType.bitWidth == projectedType.bitWidth, - """ - Illegal projection of \(fieldType.bitWidth) bit bit-field '\(fieldType)' \ - as \(projectedType.bitWidth) bit type '\(projectedType)' - """, - file: file, - line: line) - #endif -} diff --git a/Sources/MMIOMacros/Macros/Descriptions/BitFieldDescription.swift b/Sources/MMIOMacros/Macros/Descriptions/BitFieldDescription.swift index 2a654e4..530a765 100644 --- a/Sources/MMIOMacros/Macros/Descriptions/BitFieldDescription.swift +++ b/Sources/MMIOMacros/Macros/Descriptions/BitFieldDescription.swift @@ -59,6 +59,7 @@ extension BitFieldDescription { """ \(self.accessLevel)enum \(self.fieldType): ContiguousBitField { \(self.accessLevel)typealias Storage = \(self.storageType()) + \(self.accessLevel)typealias Projection = \(self.projectedType ?? "Never") \(self.accessLevel)static let bitRange = \(bitRangeExpression) } """ @@ -71,6 +72,7 @@ extension BitFieldDescription { """ \(self.accessLevel)enum \(self.fieldType): DiscontiguousBitField { \(self.accessLevel)typealias Storage = \(self.storageType()) + \(self.accessLevel)typealias Projection = \(self.projectedType ?? "Never") \(self.accessLevel)static let bitRanges = \(ArrayExprSyntax(expressions: bitRangeExpressions)) } """ @@ -84,10 +86,10 @@ extension BitFieldDescription { """ \(self.accessLevel)var \(self.fieldName): \(self.storageType()) { @inlinable @inline(__always) get { - \(self.fieldType).extract(from: self.storage) + \(self.fieldType).extractBits(from: self.storage) } @inlinable @inline(__always) set { - \(self.fieldType).insert(newValue, into: &self.storage) + \(self.fieldType).insertBits(newValue, into: &self.storage) } } """ @@ -105,12 +107,10 @@ extension BitFieldDescription { return """ \(self.accessLevel)var \(self.fieldName): \(projectedType) { @inlinable @inline(__always) get { - preconditionMatchingBitWidth(\(self.fieldType).self, \(projectedType).self) - return \(projectedType)(storage: self.raw.\(self.fieldName)) + \(self.fieldType).extract(from: self.storage) } @inlinable @inline(__always) set { - preconditionMatchingBitWidth(\(self.fieldType).self, \(projectedType).self) - self.raw.\(self.fieldName) = newValue.storage(Self.Value.Raw.Storage.self) + \(self.fieldType).insert(newValue, into: &self.storage) } } """ @@ -127,8 +127,7 @@ extension BitFieldDescription { return """ \(self.accessLevel)var \(self.fieldName): \(projectedType) { @inlinable @inline(__always) get { - preconditionMatchingBitWidth(\(self.fieldType).self, \(projectedType).self) - return \(projectedType)(storage: self.raw.\(self.fieldName)) + \(self.fieldType).extract(from: self.storage) } } """ @@ -149,12 +148,10 @@ extension BitFieldDescription { \(self.accessLevel)var \(self.fieldName): \(projectedType) { @available(*, deprecated, message: "API misuse; read from write view returns the value to be written, not the value initially read.") @inlinable @inline(__always) get { - preconditionMatchingBitWidth(\(self.fieldType).self, \(projectedType).self) - return \(projectedType)(storage: self.raw.\(self.fieldName)) + \(self.fieldType).extract(from: self.storage) } @inlinable @inline(__always) set { - preconditionMatchingBitWidth(\(self.fieldType).self, \(projectedType).self) - self.raw.\(self.fieldName) = newValue.storage(Self.Value.Raw.Storage.self) + \(self.fieldType).insert(newValue, into: &self.storage) } } """ diff --git a/Tests/MMIOFileCheckTests/MMIOFileCheckTestCase.swift b/Tests/MMIOFileCheckTests/MMIOFileCheckTestCase.swift index af421d0..94adb7c 100644 --- a/Tests/MMIOFileCheckTests/MMIOFileCheckTestCase.swift +++ b/Tests/MMIOFileCheckTests/MMIOFileCheckTestCase.swift @@ -91,13 +91,18 @@ final class MMIOFileCheckTests: XCTestCase, @unchecked Sendable { print("TOOLCHAINS not set.") #if os(macOS) print("Searching for swift-latest toolchain") - toolchainID = try sh( - """ - plutil \ - -extract CFBundleIdentifier raw \ - -o - \ - /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist - """) + do { + toolchainID = try sh( + """ + plutil \ + -extract CFBundleIdentifier raw \ + -o - \ + /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist + """) + } catch { + print("Failed to locate toolchain by plist: \(error)") + toolchainID = "" + } #else toolchainID = "" #endif diff --git a/Tests/MMIOMacrosTests/Macros/RegisterMacroTests.swift b/Tests/MMIOMacrosTests/Macros/RegisterMacroTests.swift index 9c668b1..68b5e92 100644 --- a/Tests/MMIOMacrosTests/Macros/RegisterMacroTests.swift +++ b/Tests/MMIOMacrosTests/Macros/RegisterMacroTests.swift @@ -292,11 +292,13 @@ final class RegisterMacroTests: XCTestCase { enum V1: ContiguousBitField { typealias Storage = UInt8 + typealias Projection = Never static let bitRange = 0 ..< 1 } enum V2: ContiguousBitField { typealias Storage = UInt8 + typealias Projection = Never static let bitRange = 1 ..< 2 } @@ -312,18 +314,18 @@ final class RegisterMacroTests: XCTestCase { } var v1: UInt8 { @inlinable @inline(__always) get { - V1.extract(from: self.storage) + V1.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V1.insert(newValue, into: &self.storage) + V1.insertBits(newValue, into: &self.storage) } } var v2: UInt8 { @inlinable @inline(__always) get { - V2.extract(from: self.storage) + V2.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V2.insert(newValue, into: &self.storage) + V2.insertBits(newValue, into: &self.storage) } } } @@ -386,11 +388,13 @@ final class RegisterMacroTests: XCTestCase { enum V1: ContiguousBitField { typealias Storage = UInt8 + typealias Projection = Bool static let bitRange = 0 ..< 1 } enum V2: ContiguousBitField { typealias Storage = UInt8 + typealias Projection = Never static let bitRange = 1 ..< 2 } @@ -406,18 +410,18 @@ final class RegisterMacroTests: XCTestCase { } var v1: UInt8 { @inlinable @inline(__always) get { - V1.extract(from: self.storage) + V1.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V1.insert(newValue, into: &self.storage) + V1.insertBits(newValue, into: &self.storage) } } var v2: UInt8 { @inlinable @inline(__always) get { - V2.extract(from: self.storage) + V2.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V2.insert(newValue, into: &self.storage) + V2.insertBits(newValue, into: &self.storage) } } } @@ -437,12 +441,10 @@ final class RegisterMacroTests: XCTestCase { } var v1: Bool { @inlinable @inline(__always) get { - preconditionMatchingBitWidth(V1.self, Bool.self) - return Bool(storage: self.raw.v1) + V1.extract(from: self.storage) } @inlinable @inline(__always) set { - preconditionMatchingBitWidth(V1.self, Bool.self) - self.raw.v1 = newValue.storage(Self.Value.Raw.Storage.self) + V1.insert(newValue, into: &self.storage) } } } @@ -460,7 +462,7 @@ final class RegisterMacroTests: XCTestCase { """ @Register(bitWidth: 0x8) struct S { - @ReadWrite(bits: 0..<1, 3..<4, as: UInt8.self) + @ReadWrite(bits: 0..<1, 3..<4, as: UInt16.self) var v1: V1 } """, @@ -481,6 +483,7 @@ final class RegisterMacroTests: XCTestCase { enum V1: DiscontiguousBitField { typealias Storage = UInt8 + typealias Projection = UInt16 static let bitRanges = [0 ..< 1, 3 ..< 4] } @@ -496,10 +499,10 @@ final class RegisterMacroTests: XCTestCase { } var v1: UInt8 { @inlinable @inline(__always) get { - V1.extract(from: self.storage) + V1.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V1.insert(newValue, into: &self.storage) + V1.insertBits(newValue, into: &self.storage) } } } @@ -517,14 +520,12 @@ final class RegisterMacroTests: XCTestCase { init(_ value: Raw) { self.storage = value.storage } - var v1: UInt8 { + var v1: UInt16 { @inlinable @inline(__always) get { - preconditionMatchingBitWidth(V1.self, UInt8.self) - return UInt8(storage: self.raw.v1) + V1.extract(from: self.storage) } @inlinable @inline(__always) set { - preconditionMatchingBitWidth(V1.self, UInt8.self) - self.raw.v1 = newValue.storage(Self.Value.Raw.Storage.self) + V1.insert(newValue, into: &self.storage) } } } @@ -571,11 +572,13 @@ final class RegisterMacroTests: XCTestCase { enum V1: ContiguousBitField { typealias Storage = UInt8 + typealias Projection = Bool static let bitRange = 0 ..< 1 } enum V2: ContiguousBitField { typealias Storage = UInt8 + typealias Projection = Bool static let bitRange = 1 ..< 2 } @@ -594,18 +597,18 @@ final class RegisterMacroTests: XCTestCase { } var v1: UInt8 { @inlinable @inline(__always) get { - V1.extract(from: self.storage) + V1.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V1.insert(newValue, into: &self.storage) + V1.insertBits(newValue, into: &self.storage) } } var v2: UInt8 { @inlinable @inline(__always) get { - V2.extract(from: self.storage) + V2.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V2.insert(newValue, into: &self.storage) + V2.insertBits(newValue, into: &self.storage) } } } @@ -618,8 +621,7 @@ final class RegisterMacroTests: XCTestCase { } var v1: Bool { @inlinable @inline(__always) get { - preconditionMatchingBitWidth(V1.self, Bool.self) - return Bool(storage: self.raw.v1) + V1.extract(from: self.storage) } } } @@ -637,12 +639,10 @@ final class RegisterMacroTests: XCTestCase { var v2: Bool { @available(*, deprecated, message: "API misuse; read from write view returns the value to be written, not the value initially read.") @inlinable @inline(__always) get { - preconditionMatchingBitWidth(V2.self, Bool.self) - return Bool(storage: self.raw.v2) + V2.extract(from: self.storage) } @inlinable @inline(__always) set { - preconditionMatchingBitWidth(V2.self, Bool.self) - self.raw.v2 = newValue.storage(Self.Value.Raw.Storage.self) + V2.insert(newValue, into: &self.storage) } } } @@ -681,6 +681,7 @@ final class RegisterMacroTests: XCTestCase { enum Unbounded: ContiguousBitField { typealias Storage = UInt32 + typealias Projection = Never static let bitRange = 0 ..< 32 } @@ -696,10 +697,10 @@ final class RegisterMacroTests: XCTestCase { } var unbounded: UInt32 { @inlinable @inline(__always) get { - Unbounded.extract(from: self.storage) + Unbounded.extractBits(from: self.storage) } @inlinable @inline(__always) set { - Unbounded.insert(newValue, into: &self.storage) + Unbounded.insertBits(newValue, into: &self.storage) } } } @@ -762,11 +763,13 @@ final class RegisterMacroTests: XCTestCase { enum PartialThrough: ContiguousBitField { typealias Storage = UInt32 + typealias Projection = Never static let bitRange = 0 ..< 17 } enum PartialFrom: ContiguousBitField { typealias Storage = UInt32 + typealias Projection = Never static let bitRange = 17 ..< 32 } @@ -782,18 +785,18 @@ final class RegisterMacroTests: XCTestCase { } var partialThrough: UInt32 { @inlinable @inline(__always) get { - PartialThrough.extract(from: self.storage) + PartialThrough.extractBits(from: self.storage) } @inlinable @inline(__always) set { - PartialThrough.insert(newValue, into: &self.storage) + PartialThrough.insertBits(newValue, into: &self.storage) } } var partialFrom: UInt32 { @inlinable @inline(__always) get { - PartialFrom.extract(from: self.storage) + PartialFrom.extractBits(from: self.storage) } @inlinable @inline(__always) set { - PartialFrom.insert(newValue, into: &self.storage) + PartialFrom.insertBits(newValue, into: &self.storage) } } } @@ -856,11 +859,13 @@ final class RegisterMacroTests: XCTestCase { enum PartialUpTo: ContiguousBitField { typealias Storage = UInt32 + typealias Projection = Never static let bitRange = 0 ..< 16 } enum Closed: ContiguousBitField { typealias Storage = UInt32 + typealias Projection = Never static let bitRange = 16 ..< 32 } @@ -876,18 +881,18 @@ final class RegisterMacroTests: XCTestCase { } var partialUpTo: UInt32 { @inlinable @inline(__always) get { - PartialUpTo.extract(from: self.storage) + PartialUpTo.extractBits(from: self.storage) } @inlinable @inline(__always) set { - PartialUpTo.insert(newValue, into: &self.storage) + PartialUpTo.insertBits(newValue, into: &self.storage) } } var closed: UInt32 { @inlinable @inline(__always) get { - Closed.extract(from: self.storage) + Closed.extractBits(from: self.storage) } @inlinable @inline(__always) set { - Closed.insert(newValue, into: &self.storage) + Closed.insertBits(newValue, into: &self.storage) } } } @@ -950,11 +955,13 @@ final class RegisterMacroTests: XCTestCase { public enum V1: ContiguousBitField { public typealias Storage = UInt8 + public typealias Projection = Bool public static let bitRange = 0 ..< 1 } public enum V2: ContiguousBitField { public typealias Storage = UInt8 + public typealias Projection = Bool public static let bitRange = 1 ..< 2 } @@ -973,18 +980,18 @@ final class RegisterMacroTests: XCTestCase { } public var v1: UInt8 { @inlinable @inline(__always) get { - V1.extract(from: self.storage) + V1.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V1.insert(newValue, into: &self.storage) + V1.insertBits(newValue, into: &self.storage) } } public var v2: UInt8 { @inlinable @inline(__always) get { - V2.extract(from: self.storage) + V2.extractBits(from: self.storage) } @inlinable @inline(__always) set { - V2.insert(newValue, into: &self.storage) + V2.insertBits(newValue, into: &self.storage) } } } @@ -997,8 +1004,7 @@ final class RegisterMacroTests: XCTestCase { } public var v1: Bool { @inlinable @inline(__always) get { - preconditionMatchingBitWidth(V1.self, Bool.self) - return Bool(storage: self.raw.v1) + V1.extract(from: self.storage) } } } @@ -1016,12 +1022,10 @@ final class RegisterMacroTests: XCTestCase { public var v2: Bool { @available(*, deprecated, message: "API misuse; read from write view returns the value to be written, not the value initially read.") @inlinable @inline(__always) get { - preconditionMatchingBitWidth(V2.self, Bool.self) - return Bool(storage: self.raw.v2) + V2.extract(from: self.storage) } @inlinable @inline(__always) set { - preconditionMatchingBitWidth(V2.self, Bool.self) - self.raw.v2 = newValue.storage(Self.Value.Raw.Storage.self) + V2.insert(newValue, into: &self.storage) } } }