Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check if there are overlapped bit ranges #132

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ extension ErrorDiagnostic {
}
}

extension ErrorDiagnostic {
static func cannotHaveOverlappedBitRanges(_ bitRanges: [BitRange]) -> Self {
.init("'\(Macro.signature)' the specified bit ranges are overlapped with each other: \(bitRanges)")
}
}

extension FixIt {
static func replaceExpressionWithTypeReference(
node: ExprSyntax
Expand Down
14 changes: 14 additions & 0 deletions Sources/MMIOMacros/Macros/Arguments/BitRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,17 @@ extension ErrorDiagnostic {
.init("'\(Macro.signature)' requires expression to be a range literal")
}
}

extension Array where Element == BitRange {
var isOverlapped: Bool {
guard count > 1 else { return false }
let sorted = map { $0.canonicalizedClosedRange }
.sorted(by: { $0.lowerBound < $1.lowerBound })
for i in 0..<(sorted.count - 1) {
if sorted[i].upperBound >= sorted[i+1].lowerBound {
return true
}
}
return false
}
}
8 changes: 8 additions & 0 deletions Sources/MMIOMacros/Macros/RegisterMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ extension RegisterMacro: MMIOExtensionMacro {
// Only create extension when applied to struct decls.
guard declaration.is(StructDeclSyntax.self) else { return [] }

// Check if there are bit ranges overlapping with each other
let bitRanges = try declaration.allBitRanges(with: context)
guard !bitRanges.isOverlapped else {
throw context.error(
at: node,
message: .cannotHaveOverlappedBitRanges(bitRanges))
}

let `extension`: DeclSyntax = "extension \(type.trimmed): RegisterValue {}"

return [try `extension`.requireAs(ExtensionDeclSyntax.self, context)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift MMIO open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
import SwiftSyntax
import SwiftSyntaxMacros

extension DeclGroupSyntax {
func allBitRanges(
with context: MacroContext<some ParsableMacro, some MacroExpansionContext>
) throws -> [BitRange] {
let attributeNames = [
"ReadOnly",
"ReadWrite",
"Reserved",
"WriteOnly",
]
return try memberBlock.members
.compactMap {
VariableDeclSyntax($0.decl)?.attributes
.compactMap {
AttributeSyntax($0)
}
.filter {
attributeNames.contains(IdentifierTypeSyntax($0.attributeName)?.name.text ?? "" )
}
.compactMap {
LabeledExprListSyntax($0.arguments)
}
}
.flatMap({ $0 }) // All the LabeledExprLists in interest
.reduce(into: [BitRange](), { partialResult, labeledExprList in
var bits = false
for labeledExpr in labeledExprList {
if let label = labeledExpr.label {
bits = label.text == "bits"
}
if bits {
partialResult.append(try BitRange(expression: labeledExpr.expression, in: context))
}
}
})
}
}
237 changes: 237 additions & 0 deletions Tests/MMIOMacrosTests/Macros/RegisterMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1037,5 +1037,242 @@ final class RegisterMacroTests: XCTestCase {
macros: Self.macros,
indentationWidth: Self.indentationWidth)
}

func test_bitRange_overlap() {
XCTAssertTrue(![BitRange]().isOverlapped)
let bitRange1 = BitRange("[1, 1]")
let bitRange2 = BitRange("[1, 1]")
XCTAssertTrue([bitRange1, bitRange2].isOverlapped)
let bitRange3 = BitRange("(-∞, 1]")
let bitRange4 = BitRange("(-∞, 1]")
XCTAssertTrue([bitRange3, bitRange4].isOverlapped)
let bitRange5 = BitRange("(1, +∞)")
let bitRange6 = BitRange("(1, +∞)")
XCTAssertTrue([bitRange5, bitRange6].isOverlapped)
let bitRange7 = BitRange("(1, 2]")
let bitRange8 = BitRange("[2, +∞)")
XCTAssertTrue([bitRange7, bitRange8].isOverlapped)
XCTAssertTrue([bitRange8, bitRange7].isOverlapped)
let bitRange9 = BitRange("[1, 2)")
let bitRange10 = BitRange("(1, +∞)")
XCTAssertTrue(![bitRange9, bitRange10].isOverlapped)
XCTAssertTrue(![bitRange10, bitRange9].isOverlapped)
}

func test_overlapped_bitRanges_detection_multipleFields() {
let bitRanges: [BitRange] = [
.init("[0, 2)")!,
.init("[1, 2)")!,
]
assertMacroExpansion(
"""
@Register(bitWidth: 0x8)
public struct S {
@ReadOnly(bits: 0..<2, as: Bool.self)
var v1: V1
@WriteOnly(bits: 1..<2, as: Bool.self)
var v2: V2
}
""",
expandedSource: """
public struct S {
@available(*, unavailable)
var v1: V1 {
get {
fatalError()
}
}
@available(*, unavailable)
var v2: V2 {
get {
fatalError()
}
}

private init() {
fatalError()
}

private var _never: Never

public enum V1: ContiguousBitField {
public typealias Storage = UInt8
public typealias Projection = Bool
public static let bitRange = 0 ..< 2
}

public enum V2: ContiguousBitField {
public typealias Storage = UInt8
public typealias Projection = Bool
public static let bitRange = 1 ..< 2
}

public struct Raw: RegisterValueRaw {
public typealias Value = S
public typealias Storage = UInt8
public var storage: Storage
public init(_ storage: Storage) {
self.storage = storage
}
public init(_ value: Value.Read) {
self.storage = value.storage
}
public init(_ value: Value.Write) {
self.storage = value.storage
}
public var v1: UInt8 {
@inlinable @inline(__always) get {
V1.extractBits(from: self.storage)
}
@inlinable @inline(__always) set {
V1.insertBits(newValue, into: &self.storage)
}
}
public var v2: UInt8 {
@inlinable @inline(__always) get {
V2.extractBits(from: self.storage)
}
@inlinable @inline(__always) set {
V2.insertBits(newValue, into: &self.storage)
}
}
}

public struct Read: RegisterValueRead {
public typealias Value = S
var storage: UInt8
public init(_ value: Raw) {
self.storage = value.storage
}
public var v1: Bool {
@inlinable @inline(__always) get {
V1.extract(from: self.storage)
}
}
}

public struct Write: RegisterValueWrite {
public typealias Value = S
var storage: UInt8
public init(_ value: Raw) {
self.storage = value.storage
}
public init(_ value: Read) {
// FIXME: mask off bits
self.storage = value.storage
}
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 {
V2.extract(from: self.storage)
}
@inlinable @inline(__always) set {
V2.insert(newValue, into: &self.storage)
}
}
}
}

""",
diagnostics: [
.init(
message: ErrorDiagnostic.cannotHaveOverlappedBitRanges(bitRanges).message,
line: 1,
column: 1,
highlights: ["@Register(bitWidth: 0x8)"]),
],
macros: Self.macros,
indentationWidth: Self.indentationWidth)
}

func test_overlapped_bitRanges_detection_multipleBitRangesInOneFields() {
let bitRanges: [BitRange] = [
.init("[0, 4)")!,
.init("[3, 4)")!,
]
assertMacroExpansion(
"""
@Register(bitWidth: 0x8)
struct S {
@ReadWrite(bits: 0..<4, 3..<4, as: UInt16.self)
var v1: V1
}
""",
expandedSource: """
struct S {
@available(*, unavailable)
var v1: V1 {
get {
fatalError()
}
}

private init() {
fatalError()
}

private var _never: Never

enum V1: DiscontiguousBitField {
typealias Storage = UInt8
typealias Projection = UInt16
static let bitRanges = [0 ..< 4, 3 ..< 4]
}

struct Raw: RegisterValueRaw {
typealias Value = S
typealias Storage = UInt8
var storage: Storage
init(_ storage: Storage) {
self.storage = storage
}
init(_ value: Value.ReadWrite) {
self.storage = value.storage
}
var v1: UInt8 {
@inlinable @inline(__always) get {
V1.extractBits(from: self.storage)
}
@inlinable @inline(__always) set {
V1.insertBits(newValue, into: &self.storage)
}
}
}

typealias Read = ReadWrite

typealias Write = ReadWrite

struct ReadWrite: RegisterValueRead, RegisterValueWrite {
typealias Value = S
var storage: UInt8
init(_ value: ReadWrite) {
self.storage = value.storage
}
init(_ value: Raw) {
self.storage = value.storage
}
var v1: UInt16 {
@inlinable @inline(__always) get {
V1.extract(from: self.storage)
}
@inlinable @inline(__always) set {
V1.insert(newValue, into: &self.storage)
}
}
}
}

""",
diagnostics: [
.init(
message: ErrorDiagnostic.cannotHaveOverlappedBitRanges(bitRanges).message,
line: 1,
column: 1,
highlights: ["@Register(bitWidth: 0x8)"]),
],
macros: Self.macros,
indentationWidth: Self.indentationWidth)
}
}
#endif
Loading