diff --git a/Sources/MMIO/MMIOMacros.swift b/Sources/MMIO/MMIOMacros.swift index 7f259df..a3c554e 100644 --- a/Sources/MMIO/MMIOMacros.swift +++ b/Sources/MMIO/MMIOMacros.swift @@ -16,7 +16,11 @@ public macro RegisterBank() = @attached(accessor) public macro RegisterBank(offset: Int) = - #externalMacro(module: "MMIOMacros", type: "RegisterBankOffsetMacro") + #externalMacro(module: "MMIOMacros", type: "RegisterBankScalarMemberMacro") + +@attached(accessor) +public macro RegisterBank(offset: Int, stride: Int, count: Int) = + #externalMacro(module: "MMIOMacros", type: "RegisterBankArrayMemberMacro") // Register macros @attached(member, names: arbitrary) diff --git a/Sources/MMIO/RegisterArray.swift b/Sources/MMIO/RegisterArray.swift new file mode 100644 index 0000000..770365d --- /dev/null +++ b/Sources/MMIO/RegisterArray.swift @@ -0,0 +1,97 @@ +//===----------------------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +/// A container type referencing of a region of memory whose layout is defined +/// by another type. +public struct RegisterArray where Value: RegisterValue { + public var unsafeAddress: UInt + public var stride: UInt + public var count: UInt + + #if FEATURE_INTERPOSABLE + public var interposer: (any MMIOInterposer)? + #endif + + @inlinable @inline(__always) + static func preconditionAligned(unsafeAddress: UInt, stride: UInt) { + let alignment = MemoryLayout.alignment + #if $Embedded + // FIXME: Embedded doesn't have static interpolated strings yet + precondition( + unsafeAddress.isMultiple(of: UInt(alignment)), + "Misaligned address") + precondition( + stride.isMultiple(of: UInt(alignment)), + "Misaligned stride") + #else + precondition( + unsafeAddress.isMultiple(of: UInt(alignment)), + "Misaligned address '\(unsafeAddress)' for data of type '\(Value.self)'") + precondition( + stride.isMultiple(of: UInt(alignment)), + "Misaligned stride '\(unsafeAddress)' for data of type '\(Value.self)'") + #endif + } + + #if FEATURE_INTERPOSABLE + @inlinable @inline(__always) + public init( + unsafeAddress: UInt, + stride: UInt, + count: UInt, + interposer: (any MMIOInterposer)? + ) { + Self.preconditionAligned(unsafeAddress: unsafeAddress, stride: stride) + self.unsafeAddress = unsafeAddress + self.stride = stride + self.count = count + self.interposer = interposer + } + #else + @inlinable @inline(__always) + public init( + unsafeAddress: UInt, + stride: UInt, + count: UInt + ) { + Self.preconditionAligned(unsafeAddress: unsafeAddress, stride: stride) + self.unsafeAddress = unsafeAddress + self.stride = stride + self.count = count + } + #endif +} + +extension RegisterArray { + @inlinable @inline(__always) + subscript( + _ index: Index + ) -> Register where Index: BinaryInteger { + #if $Embedded + // FIXME: Embedded doesn't have static interpolated strings yet + precondition( + 0 <= index && index < self.count, + "Index out of bounds") + #else + precondition( + 0 <= index && index < self.count, + "Index '\(index)' out of bounds '0..<\(self.count)'") + #endif + let index = UInt(index) + #if FEATURE_INTERPOSABLE + return .init( + unsafeAddress: self.unsafeAddress + (index * self.stride), + interposer: self.interposer) + #else + return .init(unsafeAddress: self.unsafeAddress + (index * self.stride)) + #endif + } +} diff --git a/Sources/MMIOMacros/CompilerPluginMain.swift b/Sources/MMIOMacros/CompilerPluginMain.swift index f1bdf97..684b301 100644 --- a/Sources/MMIOMacros/CompilerPluginMain.swift +++ b/Sources/MMIOMacros/CompilerPluginMain.swift @@ -17,7 +17,8 @@ struct CompilerPluginMain: CompilerPlugin { let providingMacros: [Macro.Type] = [ // RegisterBank macros RegisterBankMacro.self, - RegisterBankOffsetMacro.self, + RegisterBankScalarMemberMacro.self, + RegisterBankArrayMemberMacro.self, // Register macros RegisterMacro.self, ReservedMacro.self, diff --git a/Sources/MMIOMacros/Macros/RegisterBankMacro.swift b/Sources/MMIOMacros/Macros/RegisterBankMacro.swift index 0827f18..2e239bc 100644 --- a/Sources/MMIOMacros/Macros/RegisterBankMacro.swift +++ b/Sources/MMIOMacros/Macros/RegisterBankMacro.swift @@ -59,7 +59,7 @@ extension RegisterBankMacro: MMIOMemberMacro { // RegisterBankOffsetMacro. Further syntactic checking will be performed // by that macro. do { - try variableDecl.requireMacro([RegisterBankOffsetMacro.self], context) + try variableDecl.requireMacro(registerBankMemberMacros, context) } catch _ { error = true } diff --git a/Sources/MMIOMacros/Macros/RegisterBankMemberMacro.swift b/Sources/MMIOMacros/Macros/RegisterBankMemberMacro.swift new file mode 100644 index 0000000..e27fde5 --- /dev/null +++ b/Sources/MMIOMacros/Macros/RegisterBankMemberMacro.swift @@ -0,0 +1,171 @@ +//===----------------------------------------------------------*- 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 SwiftDiagnostics +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacroExpansion +import SwiftSyntaxMacros + +protocol RegisterBankMemberMacro: ParsableMacro {} + +extension RegisterBankMemberMacro { + func expansion( + of node: AttributeSyntax, + offset: ExprSyntax, + array: (stride: ExprSyntax, count: ExprSyntax)?, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: MacroContext + ) throws -> [AccessorDeclSyntax] { + // Can only applied to variables. + let variableDecl = + try declaration.requireAs(VariableDeclSyntax.self, context) + + // Must be `var` binding. + try variableDecl.requireBindingSpecifier(.var, context) + + // Exactly one binding for the variable. + let binding = try variableDecl.requireSingleBinding(context) + + // Binding identifier must be a simple identifier. + _ = try binding.requireSimpleBindingIdentifier(context) + + // Binding must have a simple type annotation. + _ = try binding.requireSimpleTypeIdentifier(context) + + // Binding must not have any accessors. + try binding.requireNoAccessor(context) + + guard let array = array else { + return [ + """ + @inlinable @inline(__always) get { + #if FEATURE_INTERPOSABLE + return .init(unsafeAddress: self.unsafeAddress + (\(offset)), interposer: self.interposer) + #else + return .init(unsafeAddress: self.unsafeAddress + (\(offset))) + #endif + } + """ + ] + } + return [ + """ + @inlinable @inline(__always) get { + #if FEATURE_INTERPOSABLE + return .init(unsafeAddress: self.unsafeAddress + (\(offset)), stride: \(array.stride), count: \(array.count), interposer: self.interposer) + #else + return .init(unsafeAddress: self.unsafeAddress + (\(offset)), stride: \(array.stride), count: \(array.count)) + #endif + } + """ + ] + } +} + +let registerBankMemberMacros: [any RegisterBankMemberMacro.Type] = [ + RegisterBankScalarMemberMacro.self, + RegisterBankArrayMemberMacro.self, +] + +public struct RegisterBankScalarMemberMacro { + @Argument(label: "offset") + var offset: Int +} + +extension RegisterBankScalarMemberMacro: Sendable {} + +extension RegisterBankScalarMemberMacro: ParsableMacro { + static let baseName = "RegisterBank" + + mutating func update( + label: String, + from expression: ExprSyntax, + in context: MacroContext + ) throws { + switch label { + case "offset": + try self._offset.update(from: expression, in: context) + default: + fatalError() + } + } +} + +extension RegisterBankScalarMemberMacro: MMIOAccessorMacro { + static var accessorMacroSuppressParsingDiagnostics: Bool { false } + + func expansion( + of node: AttributeSyntax, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: MacroContext + ) throws -> [AccessorDeclSyntax] { + try self.expansion( + of: node, + offset: self.$offset, + array: nil, + providingAccessorsOf: declaration, + in: context) + } +} + +extension RegisterBankScalarMemberMacro: RegisterBankMemberMacro {} + +public struct RegisterBankArrayMemberMacro { + @Argument(label: "offset") + var offset: Int + @Argument(label: "stride") + var stride: Int + @Argument(label: "count") + var count: Int +} + +extension RegisterBankArrayMemberMacro: Sendable {} + +extension RegisterBankArrayMemberMacro: ParsableMacro { + static let baseName = "RegisterBank" + + mutating func update( + label: String, + from expression: ExprSyntax, + in context: MacroContext + ) throws { + switch label { + case "offset": + try self._offset.update(from: expression, in: context) + case "stride": + try self._stride.update(from: expression, in: context) + case "count": + try self._count.update(from: expression, in: context) + default: + fatalError() + } + } +} + +extension RegisterBankArrayMemberMacro: MMIOAccessorMacro { + static var accessorMacroSuppressParsingDiagnostics: Bool { false } + + func expansion( + of node: AttributeSyntax, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: MacroContext + ) throws -> [AccessorDeclSyntax] { + try self.expansion( + of: node, + offset: self.$offset, + array: (stride: self.$stride, count: self.$count), + providingAccessorsOf: declaration, + in: context) + } +} + +extension RegisterBankArrayMemberMacro: RegisterBankMemberMacro {} diff --git a/Sources/MMIOMacros/Macros/RegisterBankOffsetMacro.swift b/Sources/MMIOMacros/Macros/RegisterBankOffsetMacro.swift deleted file mode 100644 index aed6a4c..0000000 --- a/Sources/MMIOMacros/Macros/RegisterBankOffsetMacro.swift +++ /dev/null @@ -1,81 +0,0 @@ -//===----------------------------------------------------------*- 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 SwiftDiagnostics -import SwiftSyntax -import SwiftSyntaxBuilder -import SwiftSyntaxMacroExpansion -import SwiftSyntaxMacros - -public struct RegisterBankOffsetMacro { - @Argument(label: "offset") - var offset: Int -} - -extension RegisterBankOffsetMacro: Sendable {} - -extension RegisterBankOffsetMacro: ParsableMacro { - static let baseName = "RegisterBank" - - mutating func update( - label: String, - from expression: ExprSyntax, - in context: MacroContext - ) throws { - switch label { - case "offset": - try self._offset.update(from: expression, in: context) - default: - fatalError() - } - } -} - -extension RegisterBankOffsetMacro: MMIOAccessorMacro { - static var accessorMacroSuppressParsingDiagnostics: Bool { false } - - func expansion( - of node: AttributeSyntax, - providingAccessorsOf declaration: some DeclSyntaxProtocol, - in context: MacroContext - ) throws -> [AccessorDeclSyntax] { - // Can only applied to variables. - let variableDecl = - try declaration.requireAs(VariableDeclSyntax.self, context) - - // Must be `var` binding. - try variableDecl.requireBindingSpecifier(.var, context) - - // Exactly one binding for the variable. - let binding = try variableDecl.requireSingleBinding(context) - - // Binding identifier must be a simple identifier. - _ = try binding.requireSimpleBindingIdentifier(context) - - // Binding must have a simple type annotation. - _ = try binding.requireSimpleTypeIdentifier(context) - - // Binding must not have any accessors. - try binding.requireNoAccessor(context) - - return [ - """ - @inlinable @inline(__always) get { - #if FEATURE_INTERPOSABLE - return .init(unsafeAddress: self.unsafeAddress + (\(raw: self.$offset)), interposer: self.interposer) - #else - return .init(unsafeAddress: self.unsafeAddress + (\(raw: self.$offset))) - #endif - } - """ - ] - } -} diff --git a/Tests/MMIOMacrosTests/Macros/RegisterBankAndOffsetMacroTests.swift b/Tests/MMIOMacrosTests/Macros/RegisterBankAndOffsetMacroTests.swift index 06a9c09..366aacb 100644 --- a/Tests/MMIOMacrosTests/Macros/RegisterBankAndOffsetMacroTests.swift +++ b/Tests/MMIOMacrosTests/Macros/RegisterBankAndOffsetMacroTests.swift @@ -17,13 +17,19 @@ import XCTest @testable import MMIOMacros final class RegisterBankAndOffsetMacroTests: XCTestCase { - static let macros: [String: Macro.Type] = [ + static let scalarMacros: [String: Macro.Type] = [ "RegisterBankType": RegisterBankMacro.self, - "RegisterBank": RegisterBankOffsetMacro.self, + "RegisterBank": RegisterBankScalarMemberMacro.self, ] + + static let arrayMacros: [String: Macro.Type] = [ + "RegisterBankType": RegisterBankMacro.self, + "RegisterBank": RegisterBankArrayMemberMacro.self, + ] + static let indentationWidth = Trivia.spaces(2) - func test_expansion() { + func test_expansion_scalarMembers() { assertMacroExpansion( """ @RegisterBankType @@ -75,7 +81,63 @@ final class RegisterBankAndOffsetMacroTests: XCTestCase { #endif } """, - macros: Self.macros, + macros: Self.scalarMacros, + indentationWidth: Self.indentationWidth) + } + + func test_expansion_arrayMembers() { + assertMacroExpansion( + """ + @RegisterBankType + struct I2C { + @RegisterBank(offset: 0x000, stride: 0x10, count: 0x08) + var control: Control + @RegisterBank(offset: 0x100, stride: 0x10, count: 0x10) + var dr: Register + } + """, + expandedSource: """ + struct I2C { + var control: Control { + @inlinable @inline(__always) get { + #if FEATURE_INTERPOSABLE + return .init(unsafeAddress: self.unsafeAddress + (0x000), stride: 0x10, count: 0x08, interposer: self.interposer) + #else + return .init(unsafeAddress: self.unsafeAddress + (0x000), stride: 0x10, count: 0x08) + #endif + } + } + var dr: Register { + @inlinable @inline(__always) get { + #if FEATURE_INTERPOSABLE + return .init(unsafeAddress: self.unsafeAddress + (0x100), stride: 0x10, count: 0x10, interposer: self.interposer) + #else + return .init(unsafeAddress: self.unsafeAddress + (0x100), stride: 0x10, count: 0x10) + #endif + } + } + + let unsafeAddress: UInt + + #if FEATURE_INTERPOSABLE + var interposer: (any MMIOInterposer)? + #endif + + #if FEATURE_INTERPOSABLE + @inlinable @inline(__always) + init(unsafeAddress: UInt, interposer: (any MMIOInterposer)?) { + self.unsafeAddress = unsafeAddress + self.interposer = interposer + } + #else + @inlinable @inline(__always) + init(unsafeAddress: UInt) { + self.unsafeAddress = unsafeAddress + } + #endif + } + """, + macros: Self.arrayMacros, indentationWidth: Self.indentationWidth) } } diff --git a/Tests/MMIOMacrosTests/Macros/RegisterBankMacroTests.swift b/Tests/MMIOMacrosTests/Macros/RegisterBankMacroTests.swift index 931c3f4..e85ef2d 100644 --- a/Tests/MMIOMacrosTests/Macros/RegisterBankMacroTests.swift +++ b/Tests/MMIOMacrosTests/Macros/RegisterBankMacroTests.swift @@ -94,36 +94,33 @@ final class RegisterBankMacroTests: XCTestCase { diagnostics: [ .init( message: - ErrorDiagnostic - .expectedMemberAnnotatedWithMacro([RegisterBankOffsetMacro.self]) - .message, + ErrorDiagnostic.expectedMemberAnnotatedWithMacro(registerBankMemberMacros).message, line: 3, column: 3, highlight: "var v1: Int", fixIts: [ - .init(message: "Insert '@RegisterBank(offset:)' macro") + .init(message: "Insert '@RegisterBank(offset:)' macro"), + .init(message: "Insert '@RegisterBank(offset:stride:count:)' macro"), ]), .init( message: - ErrorDiagnostic - .expectedMemberAnnotatedWithMacro([RegisterBankOffsetMacro.self]) - .message, + ErrorDiagnostic.expectedMemberAnnotatedWithMacro(registerBankMemberMacros).message, line: 4, column: 3, highlight: "@OtherAttribute var v2: Int", fixIts: [ - .init(message: "Insert '@RegisterBank(offset:)' macro") + .init(message: "Insert '@RegisterBank(offset:)' macro"), + .init(message: "Insert '@RegisterBank(offset:stride:count:)' macro"), ]), .init( message: - ErrorDiagnostic - .expectedMemberAnnotatedWithMacro([RegisterBankOffsetMacro.self]) - .message, + ErrorDiagnostic.expectedMemberAnnotatedWithMacro(registerBankMemberMacros).message, line: 5, column: 3, highlight: "var v3: Int { willSet {} }", fixIts: [ - .init(message: "Insert '@RegisterBank(offset:)' macro") + .init(message: "Insert '@RegisterBank(offset:)' macro"), + .init(message: "Insert '@RegisterBank(offset:stride:count:)' macro"), ]), ], macros: Self.macros, diff --git a/Tests/MMIOMacrosTests/Macros/RegisterBankOffsetMacroTests.swift b/Tests/MMIOMacrosTests/Macros/RegisterBankOffsetMacroTests.swift index 2f963be..d919053 100644 --- a/Tests/MMIOMacrosTests/Macros/RegisterBankOffsetMacroTests.swift +++ b/Tests/MMIOMacrosTests/Macros/RegisterBankOffsetMacroTests.swift @@ -17,10 +17,10 @@ import XCTest @testable import MMIOMacros final class RegisterBankOffsetMacroTests: XCTestCase { - typealias ErrorDiagnostic = MMIOMacros.ErrorDiagnostic + typealias ErrorDiagnostic = MMIOMacros.ErrorDiagnostic static let macros: [String: Macro.Type] = [ - "RegisterBank": RegisterBankOffsetMacro.self + "RegisterBank": RegisterBankScalarMemberMacro.self ] static let indentationWidth = Trivia.spaces(2) diff --git a/Tests/MMIOMacrosTests/SwiftSyntaxExtensions/ArgumentParsingTests/ArgumentParsing/ParsableMacroTests.swift b/Tests/MMIOMacrosTests/SwiftSyntaxExtensions/ArgumentParsing/ParsableMacroTests.swift similarity index 100% rename from Tests/MMIOMacrosTests/SwiftSyntaxExtensions/ArgumentParsingTests/ArgumentParsing/ParsableMacroTests.swift rename to Tests/MMIOMacrosTests/SwiftSyntaxExtensions/ArgumentParsing/ParsableMacroTests.swift diff --git a/Tests/MMIOTests/ExpansionTypeCheckTests.swift b/Tests/MMIOTests/ExpansionTypeCheckTests.swift index 72df945..5e13ea0 100644 --- a/Tests/MMIOTests/ExpansionTypeCheckTests.swift +++ b/Tests/MMIOTests/ExpansionTypeCheckTests.swift @@ -84,6 +84,6 @@ struct OtherRangeTypes2 { struct Bank { @RegisterBank(offset: 0x4) var otgHprt: Register - @RegisterBank(offset: 0x8) - var asym: Register + @RegisterBank(offset: 0x8, stride: 0x10, count: 100) + var asym: RegisterArray }