Skip to content

Commit

Permalink
Add diagnostics for bit field ranges (#140)
Browse files Browse the repository at this point in the history
Validates the bit ranges passed to bit field macros do not overlap nor
do they extend past the register bounds. In either case, diagnostics are
emitted indicating the offending attribute and offending range[s].

Given the example bit field:
```swift
@bitfield(bits: 0..<24, 8..<32, 16..<48, 36..<44)
var field: Field
```

The ranges visually look like:
```
0       8       16      24      32  36      44  48
╎       ╎       ╎       ╎       ╎   ╎       ╎   ╎
•───────────────────────◦       ╎   ╎       ╎   ╎
╎       •───────────────────────◦   ╎       ╎   ╎
╎       ╎       •───────────────────────────────◦
╎       ╎       ╎       ╎       ╎   •───────◦   ╎
╎       ╎       ╎       ╎       ╎   ╎       ╎   ╎
0       8       16      24      32  36      44  48
```

The following diagnostics will be emitted:
```
<location> error: overlapping bit ranges in '@bitfield(bits: 0..<24, 8..<40, 16..<48, 36..<44)'
@bitfield(bits: 0..<24, 8..<40, 16..<48, 36..<44)
 ^~~~~~~~

<location> note: bit range '0..<24' overlaps bit ranges '8..<32' and '16..<48' over subrange '8..<24'
@bitfield(bits: 0..<24, 8..<40, 16..<48, 36..<44)
                ^~~~~~

<location> note: bit range '8..<32' overlaps bit ranges '0..<24' and '16..<48'
@bitfield(bits: 0..<24, 8..<32, 16..<48, 36..<44)
                        ^~~~~~

<location> note: bit range '16..<48' overlaps bit ranges '0..<24', '8..<32', and '36..<44' over anges '16..<32' and '36..<44'
@bitfield(bits: 0..<24, 8..<40, 16..<48, 36..<44)
                                ^~~~~~~

<location> note: bit range '36..<44' overlaps bit range '16..<48'
@bitfield(bits: 0..<24, 8..<40, 16..<48, 36..<44)
                                         ^~~~~~~
```
  • Loading branch information
rauhul authored Dec 16, 2024
1 parent e8f0806 commit 58d4935
Show file tree
Hide file tree
Showing 12 changed files with 658 additions and 35 deletions.
59 changes: 53 additions & 6 deletions Sources/MMIOMacros/Macros/Arguments/BitRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,72 @@ struct BitRange {
}

extension BitRange {
/// Returns the lower bound as if the bound were inclusive.
var inclusiveLowerBound: Int? {
guard let lowerBound = self.lowerBound else { return nil }
return lowerBound.inclusive ? lowerBound.value : lowerBound.value + 1
}

/// Returns the lower bound as if this bit-range were a `ClosedRange<Int>`.
var canonicalizedLowerBound: Int {
guard let lowerBound = self.lowerBound else { return .min }
return lowerBound.inclusive ? lowerBound.value : lowerBound.value + 1
self.inclusiveLowerBound ?? .min
}

/// Returns the upper bound as if this bit-range were a `ClosedRange<Int>`
/// if present.
var canonicalizedUpperBound: Int {
guard let upperBound = self.upperBound else { return .max }
/// Returns the upper bound as if the bound were inclusive.
var inclusiveUpperBound: Int? {
guard let upperBound = self.upperBound else { return nil }
return upperBound.inclusive ? upperBound.value : upperBound.value - 1
}

/// Returns the upper bound as if this bit-range were a `ClosedRange<Int>`.
var canonicalizedUpperBound: Int {
self.inclusiveUpperBound ?? .max
}

/// Returns the `ClosedRange<Int>` represented by this bit-range.
var canonicalizedClosedRange: ClosedRange<Int> {
self.canonicalizedLowerBound...self.canonicalizedUpperBound
}
}

extension BitRange {
func rangeOverlapping(_ other: Self) -> Range<Int>? {
let (lhs, rhs) = self <= other ? (self, other) : (other, self)

let lhsLowerBound = lhs.canonicalizedLowerBound
let lhsUpperBound = lhs.canonicalizedUpperBound
let rhsLowerBound = rhs.canonicalizedLowerBound
let rhsUpperBound = rhs.canonicalizedUpperBound

// lower lower upper upper
// ╎ ╎ ╎ ╎
// lhs: •───────────────────────◦ ╎
// rhs: ╎ •───────────────────────◦
guard rhsLowerBound <= lhsUpperBound else {
return nil
}

let lowerBound = max(lhsLowerBound, rhsLowerBound)
var upperBound = min(lhsUpperBound, rhsUpperBound)
if upperBound < .max {
upperBound += 1
}
return lowerBound..<upperBound
}
}

extension BitRange: Comparable {
static func < (lhs: BitRange, rhs: BitRange) -> Bool {
guard lhs.canonicalizedLowerBound == rhs.canonicalizedLowerBound else {
return lhs.canonicalizedLowerBound < rhs.canonicalizedLowerBound
}
guard lhs.canonicalizedUpperBound == rhs.canonicalizedUpperBound else {
return lhs.canonicalizedUpperBound < rhs.canonicalizedUpperBound
}
return false
}
}

extension BitRange: CustomStringConvertible {
var description: String {
var description = ""
Expand Down
5 changes: 5 additions & 0 deletions Sources/MMIOMacros/Macros/BitFieldMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ protocol BitFieldMacro: MMIOAccessorMacro, ParsableMacro {
static var isSymmetric: Bool { get }

var bitRanges: [BitRange] { get }
var bitRangeExpressions: [ExprSyntax] { get }
var projectedType: BitFieldTypeProjection? { get }
}

Expand Down Expand Up @@ -69,6 +70,7 @@ public struct ReservedMacro: BitFieldMacro {

@Argument(label: "bits")
var bitRanges: [BitRange]
var bitRangeExpressions: [ExprSyntax] { self.$bitRanges }

var projectedType: BitFieldTypeProjection?

Expand All @@ -95,6 +97,7 @@ public struct ReadWriteMacro: BitFieldMacro {

@Argument(label: "bits")
var bitRanges: [BitRange]
var bitRangeExpressions: [ExprSyntax] { self.$bitRanges }

@Argument(label: "as")
var projectedType: BitFieldTypeProjection?
Expand Down Expand Up @@ -124,6 +127,7 @@ public struct ReadOnlyMacro: BitFieldMacro {

@Argument(label: "bits")
var bitRanges: [BitRange]
var bitRangeExpressions: [ExprSyntax] { self.$bitRanges }

@Argument(label: "as")
var projectedType: BitFieldTypeProjection?
Expand Down Expand Up @@ -153,6 +157,7 @@ public struct WriteOnlyMacro: BitFieldMacro {

@Argument(label: "bits")
var bitRanges: [BitRange]
var bitRangeExpressions: [ExprSyntax] { self.$bitRanges }

@Argument(label: "as")
var projectedType: BitFieldTypeProjection?
Expand Down
Loading

0 comments on commit 58d4935

Please sign in to comment.