Skip to content

Commit 25c246f

Browse files
committed
Add initial support for discontiguous bit fields (#10)
Updates the bit field macros 'bits' argument to accept a list of bit ranges. The register macro uses this information to generate a bit field descriptor type conforming to either ContiguousBitField or DiscontiguousBitField. The public api for users remains unchanged. This commit doesn't introduce any diagnostics for overlapping bit fields within a discontiguous bit fields. A future commit will include those diagnostics as well as additional diagnostics for overlapping bit fields across fields in a register. In the future the implementation of bit field descriptors could be improved. At a high level we would introduce a BitField protocol with two requirements: least significant bit and most significant bit. We would then extend FixedWidthInteger with a subscript taking a variadic list of these fields. This change would allow us to remove the distinction between contiguous and discontiguous bit fields. In order to make this change we need variadic pack iteration to land and we need to be able to use variadic packs without using runtime metadata.
1 parent 0090da0 commit 25c246f

12 files changed

+771
-323
lines changed

Package.swift

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ let package = Package(
2020
],
2121
targets: [
2222
.target(name: "MMIO", dependencies: ["MMIOMacros", "MMIOVolatile"]),
23+
.testTarget(name: "MMIOTests", dependencies: ["MMIO"]),
24+
2325
.macro(
2426
name: "MMIOMacros",
2527
dependencies: [
@@ -38,5 +40,6 @@ let package = Package(
3840
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
3941
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
4042
]),
43+
4144
.target(name: "MMIOVolatile"),
4245
])

Sources/MMIO/BitField.swift

+117-7
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,128 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
public typealias BitFieldStorage = FixedWidthInteger & UnsignedInteger
12+
// Explore bit field refactor:
13+
// * requires variadic pack iteration
14+
// * requires no metadata-less variadic packs
15+
// - protocol BitField with (least|most) significant bit requirements
16+
// - FixedWidthInteger.subscript[(variadic T: BitField)] -> Storage
17+
18+
extension FixedWidthInteger {
19+
@inline(__always)
20+
static func bitRangeWithinBounds(bits bitRange: Range<Int>) -> Bool {
21+
bitRange.lowerBound >= 0 && bitRange.upperBound <= Self.bitWidth
22+
}
23+
24+
subscript(bits bitRange: Range<Int>) -> Self {
25+
@inline(__always) get {
26+
precondition(Self.bitRangeWithinBounds(bits: bitRange))
27+
let bitWidth = bitRange.upperBound - bitRange.lowerBound
28+
let bitMask: Self = 1 << bitWidth &- 1
29+
return (self >> bitRange.lowerBound) & bitMask
30+
}
31+
32+
@inline(__always) set {
33+
precondition(Self.bitRangeWithinBounds(bits: bitRange))
34+
let bitWidth = bitRange.upperBound - bitRange.lowerBound
35+
let bitMask: Self = 1 << bitWidth &- 1
36+
self &= ~(bitMask << bitRange.lowerBound)
37+
self |= (newValue & bitMask) << bitRange.lowerBound
38+
}
39+
}
40+
}
41+
42+
extension FixedWidthInteger {
43+
static func bitRangesCoalesced(bits bitRanges: [Range<Int>]) -> Bool {
44+
let bitRanges = bitRanges.sorted { $0.lowerBound < $1.lowerBound }
45+
var lowerBound = -1
46+
for bitRange in bitRanges {
47+
// Specifically ensure that the bit ranges dont overlap, e.g. the
48+
// following ranges are not valid: 0..<1, 0..<2. This is to ensure ranges
49+
// are coalesced before iterating reduce the number of mask and shift
50+
// operations needed.
51+
guard lowerBound <= bitRange.lowerBound else { return false }
52+
lowerBound = bitRange.upperBound
53+
}
54+
return true
55+
}
56+
57+
subscript(bits bitRanges: [Range<Int>]) -> Self {
58+
@inline(__always) get {
59+
precondition(Self.bitRangesCoalesced(bits: bitRanges))
60+
61+
var currentShift = 0
62+
var value: Self = 0
63+
for bitRange in bitRanges {
64+
let valueSlice = self[bits: bitRange]
65+
value |= valueSlice << currentShift
66+
let bitWidth = bitRange.upperBound - bitRange.lowerBound
67+
currentShift += bitWidth
68+
}
69+
return value
70+
}
71+
72+
@inline(__always) set {
73+
precondition(Self.bitRangesCoalesced(bits: bitRanges))
74+
75+
var newValue = newValue
76+
for bitRange in bitRanges {
77+
self[bits: bitRange] = newValue
78+
let bitWidth = bitRange.upperBound - bitRange.lowerBound
79+
newValue >>= bitWidth
80+
}
81+
}
82+
}
83+
}
1384

1485
public protocol BitField {
15-
associatedtype RawStorage: BitFieldStorage
86+
associatedtype Storage: FixedWidthInteger & UnsignedInteger
87+
88+
static func insert(_ value: Storage, into storage: inout Storage)
89+
static func extract(from storage: Storage) -> Storage
90+
}
91+
92+
public protocol ContiguousBitField: BitField {
1693
static var bitRange: Range<Int> { get }
1794
static var bitWidth: Int { get }
1895
static var bitOffset: Int { get }
19-
static var bitMask: RawStorage { get }
96+
static var bitMask: Storage { get }
97+
}
98+
99+
extension ContiguousBitField {
100+
public static var bitWidth: Int {
101+
Self.bitRange.upperBound - Self.bitRange.lowerBound
102+
}
103+
public static var bitOffset: Int { Self.bitRange.lowerBound }
104+
public static var bitMask: Storage { (1 << Self.bitWidth) &- 1 }
105+
}
106+
107+
extension ContiguousBitField {
108+
// FIXME: value.bitWidth <= Self.bitWidth <= Storage.bitWidth
109+
@inline(__always)
110+
public static func insert(_ value: Storage, into storage: inout Storage) {
111+
storage[bits: Self.bitRange] = value
112+
}
113+
114+
@inline(__always)
115+
public static func extract(from storage: Storage) -> Storage {
116+
storage[bits: Self.bitRange]
117+
}
20118
}
21119

22-
extension BitField {
23-
public static var bitWidth: Int { self.bitRange.count }
24-
public static var bitOffset: Int { self.bitRange.lowerBound }
25-
public static var bitMask: RawStorage { (1 << self.bitWidth) - 1 }
120+
public protocol DiscontiguousBitField: BitField {
121+
/// - Precondition: Bit bitRanges must not overlap and must be sorted by from
122+
/// lowest to highest bit index
123+
static var bitRanges: [Range<Int>] { get }
124+
}
125+
126+
extension DiscontiguousBitField {
127+
@inline(__always)
128+
public static func insert(_ value: Storage, into storage: inout Storage) {
129+
storage[bits: Self.bitRanges] = value
130+
}
131+
132+
@inline(__always)
133+
public static func extract(from storage: Storage) -> Storage {
134+
storage[bits: Self.bitRanges]
135+
}
26136
}

Sources/MMIO/Extensions/FixedWidthInteger.swift

-39
This file was deleted.

Sources/MMIO/MMIOVolatileStorage.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import MMIOVolatile
1616
/// The set of types which conform to this protocol restricts the the set of
1717
/// volatile operations available on the platform. As such, user code must
1818
/// _never_ conform new types to this protocol.
19-
public protocol MMIOVolatileStorage: BitFieldStorage {
19+
public protocol MMIOVolatileStorage: FixedWidthInteger & UnsignedInteger {
2020
/// Loads an instance of `self` from the address pointed to by pointer.
2121
static func load(from pointer: UnsafePointer<Self>) -> Self
2222
/// Stores an instance of `self` to the address pointed to by pointer.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===----------------------------------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift MMIO open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import SwiftSyntax
13+
14+
struct BitFieldDescription {
15+
var accessLevel: AccessLevel?
16+
var bitWidth: Int
17+
var type: any BitFieldMacro.Type
18+
var fieldName: IdentifierPatternSyntax
19+
var fieldType: TypeSyntax
20+
var bitRanges: [Range<Int>]
21+
var bitRangeExpressions: [ExprSyntax]
22+
var projectedType: Int?
23+
}
24+
25+
extension BitFieldDescription {
26+
func validate() throws {
27+
// FIXME: Validate bit range overlap
28+
}
29+
30+
func declarations() -> [DeclSyntax] {
31+
switch bitRangeExpressions.count {
32+
case 0:
33+
preconditionFailure()
34+
case 1:
35+
let bitRange = self.bitRangeExpressions[0]
36+
return [
37+
"""
38+
\(self.accessLevel)enum \(self.fieldType): ContiguousBitField {
39+
\(self.accessLevel)typealias Storage = UInt\(raw: self.bitWidth)
40+
\(self.accessLevel)static let bitRange = \(bitRange)
41+
}
42+
"""
43+
]
44+
default:
45+
let bitRanges = ArrayExprSyntax(expressions: self.bitRangeExpressions)
46+
return [
47+
"""
48+
\(self.accessLevel)enum \(self.fieldType): DiscontiguousBitField {
49+
\(self.accessLevel)typealias Storage = UInt\(raw: self.bitWidth)
50+
\(self.accessLevel)static let bitRange = \(bitRanges)
51+
}
52+
"""
53+
]
54+
}
55+
}
56+
}

Sources/MMIOMacros/Macros/BitFieldMacro.swift

+16-18
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,15 @@ import SwiftSyntaxBuilder
1515
import SwiftSyntaxMacroExpansion
1616
import SwiftSyntaxMacros
1717

18-
struct BitField {
19-
var type: any BitFieldMacro.Type
20-
var fieldName: IdentifierPatternSyntax
21-
var fieldType: TypeSyntax
22-
var bitRange: Range<Int>
23-
var projectedType: Int?
24-
}
25-
26-
// @BaseName(bits: 0..<1, 3..<4, as: Bool.self)
18+
// @BaseName(bits: 3..<4, 0..<1, as: Bool.self)
2719
protocol BitFieldMacro: MMIOAccessorMacro, ParsableMacro {
2820
static var isReadable: Bool { get }
2921
static var isWriteable: Bool { get }
3022
static var isSymmetric: Bool { get }
3123

32-
var bitRange: Range<Int> { get }
24+
var bitRanges: [Range<Int>] { get }
25+
var bitRangeExpressions: [ExprSyntax] { get }
26+
3327
var projectedType: Int? { get }
3428
}
3529

@@ -130,7 +124,8 @@ struct ReservedMacro: BitFieldMacro, Sendable {
130124
static let isSymmetric = true
131125

132126
@Argument(label: "bits")
133-
var bitRange: Range<Int>
127+
var bitRanges: [Range<Int>]
128+
var bitRangeExpressions: [ExprSyntax] { self.$bitRanges }
134129

135130
var projectedType: Int?
136131

@@ -141,7 +136,7 @@ struct ReservedMacro: BitFieldMacro, Sendable {
141136
) throws {
142137
switch label {
143138
case "bits":
144-
try self._bitRange.update(from: expression, in: context)
139+
try self._bitRanges.update(from: expression, in: context)
145140
default:
146141
fatalError()
147142
}
@@ -156,7 +151,8 @@ struct ReadWriteMacro: BitFieldMacro, Sendable {
156151
static let isSymmetric = true
157152

158153
@Argument(label: "bits")
159-
var bitRange: Range<Int>
154+
var bitRanges: [Range<Int>]
155+
var bitRangeExpressions: [ExprSyntax] { self.$bitRanges }
160156

161157
@Argument(label: "as")
162158
var projectedType: Int?
@@ -168,7 +164,7 @@ struct ReadWriteMacro: BitFieldMacro, Sendable {
168164
) throws {
169165
switch label {
170166
case "bits":
171-
try self._bitRange.update(from: expression, in: context)
167+
try self._bitRanges.update(from: expression, in: context)
172168
case "as":
173169
try self._projectedType.update(from: expression, in: context)
174170
default:
@@ -185,7 +181,8 @@ struct ReadOnlyMacro: BitFieldMacro, Sendable {
185181
static let isSymmetric = false
186182

187183
@Argument(label: "bits")
188-
var bitRange: Range<Int>
184+
var bitRanges: [Range<Int>]
185+
var bitRangeExpressions: [ExprSyntax] { self.$bitRanges }
189186

190187
@Argument(label: "as")
191188
var projectedType: Int?
@@ -197,7 +194,7 @@ struct ReadOnlyMacro: BitFieldMacro, Sendable {
197194
) throws {
198195
switch label {
199196
case "bits":
200-
try self._bitRange.update(from: expression, in: context)
197+
try self._bitRanges.update(from: expression, in: context)
201198
case "as":
202199
try self._projectedType.update(from: expression, in: context)
203200
default:
@@ -214,7 +211,8 @@ struct WriteOnlyMacro: BitFieldMacro, Sendable {
214211
static let isSymmetric = false
215212

216213
@Argument(label: "bits")
217-
var bitRange: Range<Int>
214+
var bitRanges: [Range<Int>]
215+
var bitRangeExpressions: [ExprSyntax] { self.$bitRanges }
218216

219217
@Argument(label: "as")
220218
var projectedType: Int?
@@ -226,7 +224,7 @@ struct WriteOnlyMacro: BitFieldMacro, Sendable {
226224
) throws {
227225
switch label {
228226
case "bits":
229-
try self._bitRange.update(from: expression, in: context)
227+
try self._bitRanges.update(from: expression, in: context)
230228
case "as":
231229
try self._projectedType.update(from: expression, in: context)
232230
default:

0 commit comments

Comments
 (0)