Skip to content

Commit 55d21b6

Browse files
author
Tim Wang
committed
Check if there are overlapped bit ranges
1 parent c24e034 commit 55d21b6

File tree

5 files changed

+315
-0
lines changed

5 files changed

+315
-0
lines changed

Sources/MMIOMacros/Macros/Arguments/BitFieldTypeProjection.swift

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ extension ErrorDiagnostic {
4444
}
4545
}
4646

47+
extension ErrorDiagnostic {
48+
static func cannotHaveOverlappedBitRanges(_ bitRanges: [BitRange]) -> Self {
49+
.init("'\(Macro.signature)' the specified bit ranges are overlapped with each other: \(bitRanges)")
50+
}
51+
}
52+
4753
extension FixIt {
4854
static func replaceExpressionWithTypeReference(
4955
node: ExprSyntax

Sources/MMIOMacros/Macros/Arguments/BitRange.swift

+14
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,17 @@ extension ErrorDiagnostic {
202202
.init("'\(Macro.signature)' requires expression to be a range literal")
203203
}
204204
}
205+
206+
extension Array where Element == BitRange {
207+
var isOverlapped: Bool {
208+
guard count > 1 else { return false }
209+
let sorted = map { $0.canonicalizedClosedRange }
210+
.sorted(by: { $0.lowerBound < $1.lowerBound })
211+
for i in 0..<(sorted.count - 1) {
212+
if sorted[i].upperBound >= sorted[i+1].lowerBound {
213+
return true
214+
}
215+
}
216+
return false
217+
}
218+
}

Sources/MMIOMacros/Macros/RegisterMacro.swift

+8
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ extension RegisterMacro: MMIOExtensionMacro {
147147
// Only create extension when applied to struct decls.
148148
guard declaration.is(StructDeclSyntax.self) else { return [] }
149149

150+
// Check if there are bit ranges overlapping with each other
151+
let bitRanges = try declaration.allBitRanges(with: context)
152+
guard !bitRanges.isOverlapped else {
153+
throw context.error(
154+
at: node,
155+
message: .cannotHaveOverlappedBitRanges(bitRanges))
156+
}
157+
150158
let `extension`: DeclSyntax = "extension \(type.trimmed): RegisterValue {}"
151159

152160
return [try `extension`.requireAs(ExtensionDeclSyntax.self, context)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
import SwiftSyntax
12+
import SwiftSyntaxMacros
13+
14+
extension DeclGroupSyntax {
15+
func allBitRanges(
16+
with context: MacroContext<some ParsableMacro, some MacroExpansionContext>
17+
) throws -> [BitRange] {
18+
let attributeNames = [
19+
"ReadOnly",
20+
"ReadWrite",
21+
"Reserved",
22+
"WriteOnly",
23+
]
24+
return try memberBlock.members
25+
.compactMap {
26+
VariableDeclSyntax($0.decl)?.attributes
27+
.compactMap {
28+
AttributeSyntax($0)
29+
}
30+
.filter {
31+
attributeNames.contains(IdentifierTypeSyntax($0.attributeName)?.name.text ?? "" )
32+
}
33+
.compactMap {
34+
LabeledExprListSyntax($0.arguments)
35+
}
36+
}
37+
.flatMap({ $0 }) // All the LabeledExprLists in interest
38+
.reduce(into: [BitRange](), { partialResult, labeledExprList in
39+
var bits = false
40+
for labeledExpr in labeledExprList {
41+
if let label = labeledExpr.label {
42+
bits = label.text == "bits"
43+
}
44+
if bits {
45+
partialResult.append(try BitRange(expression: labeledExpr.expression, in: context))
46+
}
47+
}
48+
})
49+
}
50+
}

Tests/MMIOMacrosTests/Macros/RegisterMacroTests.swift

+237
Original file line numberDiff line numberDiff line change
@@ -1037,5 +1037,242 @@ final class RegisterMacroTests: XCTestCase {
10371037
macros: Self.macros,
10381038
indentationWidth: Self.indentationWidth)
10391039
}
1040+
1041+
func test_bitRange_overlap() {
1042+
XCTAssertTrue(![BitRange]().isOverlapped)
1043+
let bitRange1 = BitRange("[1, 1]")
1044+
let bitRange2 = BitRange("[1, 1]")
1045+
XCTAssertTrue([bitRange1, bitRange2].isOverlapped)
1046+
let bitRange3 = BitRange("(-∞, 1]")
1047+
let bitRange4 = BitRange("(-∞, 1]")
1048+
XCTAssertTrue([bitRange3, bitRange4].isOverlapped)
1049+
let bitRange5 = BitRange("(1, +∞)")
1050+
let bitRange6 = BitRange("(1, +∞)")
1051+
XCTAssertTrue([bitRange5, bitRange6].isOverlapped)
1052+
let bitRange7 = BitRange("(1, 2]")
1053+
let bitRange8 = BitRange("[2, +∞)")
1054+
XCTAssertTrue([bitRange7, bitRange8].isOverlapped)
1055+
XCTAssertTrue([bitRange8, bitRange7].isOverlapped)
1056+
let bitRange9 = BitRange("[1, 2)")
1057+
let bitRange10 = BitRange("(1, +∞)")
1058+
XCTAssertTrue(![bitRange9, bitRange10].isOverlapped)
1059+
XCTAssertTrue(![bitRange10, bitRange9].isOverlapped)
1060+
}
1061+
1062+
func test_overlapped_bitRanges_detection_multipleFields() {
1063+
let bitRanges: [BitRange] = [
1064+
.init("[0, 2)")!,
1065+
.init("[1, 2)")!,
1066+
]
1067+
assertMacroExpansion(
1068+
"""
1069+
@Register(bitWidth: 0x8)
1070+
public struct S {
1071+
@ReadOnly(bits: 0..<2, as: Bool.self)
1072+
var v1: V1
1073+
@WriteOnly(bits: 1..<2, as: Bool.self)
1074+
var v2: V2
1075+
}
1076+
""",
1077+
expandedSource: """
1078+
public struct S {
1079+
@available(*, unavailable)
1080+
var v1: V1 {
1081+
get {
1082+
fatalError()
1083+
}
1084+
}
1085+
@available(*, unavailable)
1086+
var v2: V2 {
1087+
get {
1088+
fatalError()
1089+
}
1090+
}
1091+
1092+
private init() {
1093+
fatalError()
1094+
}
1095+
1096+
private var _never: Never
1097+
1098+
public enum V1: ContiguousBitField {
1099+
public typealias Storage = UInt8
1100+
public typealias Projection = Bool
1101+
public static let bitRange = 0 ..< 2
1102+
}
1103+
1104+
public enum V2: ContiguousBitField {
1105+
public typealias Storage = UInt8
1106+
public typealias Projection = Bool
1107+
public static let bitRange = 1 ..< 2
1108+
}
1109+
1110+
public struct Raw: RegisterValueRaw {
1111+
public typealias Value = S
1112+
public typealias Storage = UInt8
1113+
public var storage: Storage
1114+
public init(_ storage: Storage) {
1115+
self.storage = storage
1116+
}
1117+
public init(_ value: Value.Read) {
1118+
self.storage = value.storage
1119+
}
1120+
public init(_ value: Value.Write) {
1121+
self.storage = value.storage
1122+
}
1123+
public var v1: UInt8 {
1124+
@inlinable @inline(__always) get {
1125+
V1.extractBits(from: self.storage)
1126+
}
1127+
@inlinable @inline(__always) set {
1128+
V1.insertBits(newValue, into: &self.storage)
1129+
}
1130+
}
1131+
public var v2: UInt8 {
1132+
@inlinable @inline(__always) get {
1133+
V2.extractBits(from: self.storage)
1134+
}
1135+
@inlinable @inline(__always) set {
1136+
V2.insertBits(newValue, into: &self.storage)
1137+
}
1138+
}
1139+
}
1140+
1141+
public struct Read: RegisterValueRead {
1142+
public typealias Value = S
1143+
var storage: UInt8
1144+
public init(_ value: Raw) {
1145+
self.storage = value.storage
1146+
}
1147+
public var v1: Bool {
1148+
@inlinable @inline(__always) get {
1149+
V1.extract(from: self.storage)
1150+
}
1151+
}
1152+
}
1153+
1154+
public struct Write: RegisterValueWrite {
1155+
public typealias Value = S
1156+
var storage: UInt8
1157+
public init(_ value: Raw) {
1158+
self.storage = value.storage
1159+
}
1160+
public init(_ value: Read) {
1161+
// FIXME: mask off bits
1162+
self.storage = value.storage
1163+
}
1164+
public var v2: Bool {
1165+
@available(*, deprecated, message: "API misuse; read from write view returns the value to be written, not the value initially read.")
1166+
@inlinable @inline(__always) get {
1167+
V2.extract(from: self.storage)
1168+
}
1169+
@inlinable @inline(__always) set {
1170+
V2.insert(newValue, into: &self.storage)
1171+
}
1172+
}
1173+
}
1174+
}
1175+
1176+
""",
1177+
diagnostics: [
1178+
.init(
1179+
message: ErrorDiagnostic.cannotHaveOverlappedBitRanges(bitRanges).message,
1180+
line: 1,
1181+
column: 1,
1182+
highlights: ["@Register(bitWidth: 0x8)"]),
1183+
],
1184+
macros: Self.macros,
1185+
indentationWidth: Self.indentationWidth)
1186+
}
1187+
1188+
func test_overlapped_bitRanges_detection_multipleBitRangesInOneFields() {
1189+
let bitRanges: [BitRange] = [
1190+
.init("[0, 4)")!,
1191+
.init("[3, 4)")!,
1192+
]
1193+
assertMacroExpansion(
1194+
"""
1195+
@Register(bitWidth: 0x8)
1196+
struct S {
1197+
@ReadWrite(bits: 0..<4, 3..<4, as: UInt16.self)
1198+
var v1: V1
1199+
}
1200+
""",
1201+
expandedSource: """
1202+
struct S {
1203+
@available(*, unavailable)
1204+
var v1: V1 {
1205+
get {
1206+
fatalError()
1207+
}
1208+
}
1209+
1210+
private init() {
1211+
fatalError()
1212+
}
1213+
1214+
private var _never: Never
1215+
1216+
enum V1: DiscontiguousBitField {
1217+
typealias Storage = UInt8
1218+
typealias Projection = UInt16
1219+
static let bitRanges = [0 ..< 4, 3 ..< 4]
1220+
}
1221+
1222+
struct Raw: RegisterValueRaw {
1223+
typealias Value = S
1224+
typealias Storage = UInt8
1225+
var storage: Storage
1226+
init(_ storage: Storage) {
1227+
self.storage = storage
1228+
}
1229+
init(_ value: Value.ReadWrite) {
1230+
self.storage = value.storage
1231+
}
1232+
var v1: UInt8 {
1233+
@inlinable @inline(__always) get {
1234+
V1.extractBits(from: self.storage)
1235+
}
1236+
@inlinable @inline(__always) set {
1237+
V1.insertBits(newValue, into: &self.storage)
1238+
}
1239+
}
1240+
}
1241+
1242+
typealias Read = ReadWrite
1243+
1244+
typealias Write = ReadWrite
1245+
1246+
struct ReadWrite: RegisterValueRead, RegisterValueWrite {
1247+
typealias Value = S
1248+
var storage: UInt8
1249+
init(_ value: ReadWrite) {
1250+
self.storage = value.storage
1251+
}
1252+
init(_ value: Raw) {
1253+
self.storage = value.storage
1254+
}
1255+
var v1: UInt16 {
1256+
@inlinable @inline(__always) get {
1257+
V1.extract(from: self.storage)
1258+
}
1259+
@inlinable @inline(__always) set {
1260+
V1.insert(newValue, into: &self.storage)
1261+
}
1262+
}
1263+
}
1264+
}
1265+
1266+
""",
1267+
diagnostics: [
1268+
.init(
1269+
message: ErrorDiagnostic.cannotHaveOverlappedBitRanges(bitRanges).message,
1270+
line: 1,
1271+
column: 1,
1272+
highlights: ["@Register(bitWidth: 0x8)"]),
1273+
],
1274+
macros: Self.macros,
1275+
indentationWidth: Self.indentationWidth)
1276+
}
10401277
}
10411278
#endif

0 commit comments

Comments
 (0)