Skip to content

Commit

Permalink
Update SVD2Swift to generate RegisterArrays (#116)
Browse files Browse the repository at this point in the history
Updates `SVD2Swift` to automatically generate `RegisterArrays` for
dimensioned SVD instances (excluding fields) instead of N instances
being generated as peers suffixed by an index.

Introduces `RegisterProtocol` to standardize the API for register types
needed for `RegisterArray` to work with both `@RegisterBlocks` and
`Registers`.

Updates the XML decoding library and macro implementation to handle
decoding optional `@XMLInlineElement` properties and Updates the SVD
model structures, such as `SVDCluster`, to make `dimensionElement`
optional with non-optional child properties.

Consolidates the `@XMLAttribute` and `@XMLInlineElement` macro
implementations.
  • Loading branch information
rauhul authored Aug 23, 2024
1 parent 47a19f5 commit e54c11d
Show file tree
Hide file tree
Showing 25 changed files with 315 additions and 127 deletions.
1 change: 1 addition & 0 deletions Sources/MMIO/MMIOMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

// RegisterBlock macros
@attached(member, names: named(unsafeAddress), named(init), named(interposer))
@attached(extension, conformances: RegisterProtocol)
public macro RegisterBlock() =
#externalMacro(module: "MMIOMacros", type: "RegisterBlockMacro")

Expand Down
2 changes: 1 addition & 1 deletion Sources/MMIO/Register.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

/// A container type referencing of a region of memory whose layout is defined
/// by another type.
public struct Register<Value> where Value: RegisterValue {
public struct Register<Value>: RegisterProtocol where Value: RegisterValue {
public var unsafeAddress: UInt

#if FEATURE_INTERPOSABLE
Expand Down
55 changes: 29 additions & 26 deletions Sources/MMIO/RegisterArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

/// A container type referencing of a region of memory whose layout is defined
/// by another type.
public struct RegisterArray<Value> where Value: RegisterValue {
public struct RegisterArray<Value> {
public var unsafeAddress: UInt
public var stride: UInt
public var count: UInt
Expand All @@ -20,27 +20,6 @@ public struct RegisterArray<Value> where Value: RegisterValue {
public var interposer: (any MMIOInterposer)?
#endif

@inlinable @inline(__always)
static func preconditionAligned(unsafeAddress: UInt, stride: UInt) {
let alignment = MemoryLayout<Value.Raw.Storage>.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(
Expand All @@ -49,7 +28,6 @@ public struct RegisterArray<Value> where Value: RegisterValue {
count: UInt,
interposer: (any MMIOInterposer)?
) {
Self.preconditionAligned(unsafeAddress: unsafeAddress, stride: stride)
self.unsafeAddress = unsafeAddress
self.stride = stride
self.count = count
Expand All @@ -62,17 +40,16 @@ public struct RegisterArray<Value> where Value: RegisterValue {
stride: UInt,
count: UInt
) {
Self.preconditionAligned(unsafeAddress: unsafeAddress, stride: stride)
self.unsafeAddress = unsafeAddress
self.stride = stride
self.count = count
}
#endif
}

extension RegisterArray {
extension RegisterArray where Value: RegisterValue {
@inlinable @inline(__always)
subscript<Index>(
public subscript<Index>(
_ index: Index
) -> Register<Value> where Index: BinaryInteger {
#if $Embedded
Expand All @@ -95,3 +72,29 @@ extension RegisterArray {
#endif
}
}

extension RegisterArray where Value: RegisterProtocol {
@inlinable @inline(__always)
public subscript<Index>(
_ index: Index
) -> Value 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
}
}
20 changes: 20 additions & 0 deletions Sources/MMIO/RegisterProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift MMIO open source project
//
// Copyright (c) 2024 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
//
//===----------------------------------------------------------------------===//

// This only exists because RegisterBlock is not a concrete type, ideally
// RegisterBlock and Register would both be the same Register type
public protocol RegisterProtocol {
#if FEATURE_INTERPOSABLE
init(unsafeAddress: UInt, interposer: (any MMIOInterposer)?)
#else
init(unsafeAddress: UInt)
#endif
}
21 changes: 21 additions & 0 deletions Sources/MMIOMacros/Macros/RegisterBlockMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,24 @@ extension RegisterBlockMacro: MMIOMemberMacro {
]
}
}

extension RegisterBlockMacro: MMIOExtensionMacro {
static var extensionMacroSuppressParsingDiagnostics: Bool { true }

func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: MacroContext<Self, some MacroExpansionContext>
) throws -> [ExtensionDeclSyntax] {
// Avoid duplicating diagnostics produced by `MemberMacro` conformance.
// Only create extension when applied to struct decls.
guard declaration.is(StructDeclSyntax.self) else { return [] }

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

return [try `extension`.requireAs(ExtensionDeclSyntax.self, context)]
}
}
4 changes: 2 additions & 2 deletions Sources/SVD/Macros/SVDMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

@attached(peer)
macro XMLAttribute() =
#externalMacro(module: "SVDMacros", type: "XMLAttributeMacro")
#externalMacro(module: "SVDMacros", type: "XMLMarkerMacro")

@attached(extension, names: named(init(_:)), conformances: XMLElementInitializable)
macro XMLElement() =
#externalMacro(module: "SVDMacros", type: "XMLElementMacro")

@attached(peer)
macro XMLInlineElement() =
#externalMacro(module: "SVDMacros", type: "XMLInlineElementMacro")
#externalMacro(module: "SVDMacros", type: "XMLMarkerMacro")
31 changes: 28 additions & 3 deletions Sources/SVD/Macros/XMLElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,30 @@ enum Errors: Error, @unchecked Sendable {
case unknownElement(XMLElement)
}

// XMLElement.child(element) -> T
// Support for @XMLElement properties
// where: @XMLInlineElement & XMLElementInitializable
extension XMLElement {
func decode<T>(
_: T.Type = T.self
) throws -> T where T: XMLElementInitializable {
try self
.decode(T?.self)
.unwrap()
}

func decode<T>(
_: T?.Type = T?.self
) throws -> T? where T: XMLElementInitializable {
do {
return try T.init(self)
} catch Errors.missingValue {
return nil
}
}
}

// Support for @XMLElement properties
// where: implied @XMLChild & XMLElementInitializable
extension XMLElement {
func decode<T>(
_: T.Type = T.self,
Expand Down Expand Up @@ -64,7 +87,8 @@ extension XMLElement {
}
}

// XMLElement.child(node) -> T
// Support for @XMLElement properties
// where: implied @XMLChild & XMLNodeInitializable
extension XMLElement {
func decode<T>(
_: T.Type = T.self,
Expand All @@ -86,7 +110,8 @@ extension XMLElement {
}
}

// XMLElement.attribute(node) -> T
// Support for @XMLElement properties
// where: @XMLAttribute & XMLNodeInitializable
extension XMLElement {
func decode<T>(
_: T.Type = T.self,
Expand Down
2 changes: 1 addition & 1 deletion Sources/SVD/Models/SVDCluster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public struct SVDCluster {
/// consecutive array elements and a comma separated list of strings used to
/// identify each element in the array.
@XMLInlineElement
public var dimensionElement: SVDDimensionElement = .init()
public var dimensionElement: SVDDimensionElement?
/// String to identify the cluster. Cluster names are required to be unique
/// within the scope of a peripheral. A list of cluster names can be build
/// using the placeholder `%s`. Use the placeholder `[%s]` at the end of the
Expand Down
5 changes: 3 additions & 2 deletions Sources/SVD/Models/SVDDimensionElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import FoundationXML
@XMLElement
public struct SVDDimensionElement {
/// Define the number of elements in an array.
public var dim: UInt64?
public var dim: UInt64
/// Specify the address increment, in Bytes, between two neighboring array
/// members in the address map.
public var dimIncrement: UInt64?
public var dimIncrement: UInt64
// FIXME: this should be `DimIndexType?`
/// Do not define on peripheral level. By default, `<dimIndex>` is an
/// integer value starting at 0.
public var dimIndex: String?
Expand Down
2 changes: 1 addition & 1 deletion Sources/SVD/Models/SVDField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public struct SVDField {
/// consecutive array elements and a comma separated list of strings used to
/// identify each element in the array.
@XMLInlineElement
public var dimensionElement: SVDDimensionElement = .init()
public var dimensionElement: SVDDimensionElement?
/// Name string used to identify the field. Field names must be unique
/// within a register.
public var name: String
Expand Down
2 changes: 1 addition & 1 deletion Sources/SVD/Models/SVDPeripheral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public struct SVDPeripheral {
/// consecutive array elements and a comma separated list of strings used to
/// identify each element in the array.
@XMLInlineElement
public var dimensionElement: SVDDimensionElement = .init()
public var dimensionElement: SVDDimensionElement?
/// The string identifies the peripheral. Peripheral names are required to
/// be unique for a device. The name needs to be an ANSI C identifier to
/// generate the header file. You can use the placeholder [%s] to create
Expand Down
2 changes: 1 addition & 1 deletion Sources/SVD/Models/SVDRegister.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct SVDRegister {
/// consecutive array elements and a comma separated list of strings used to
/// identify each element in the array.
@XMLInlineElement
public var dimensionElement: SVDDimensionElement = .init()
public var dimensionElement: SVDDimensionElement?
/// String to identify the register. Register names are required to be
/// unique within the scope of a peripheral. You can use the placeholder
/// `%s`, which is replaced by the dimIndex substring. Use the placeholder
Expand Down
Loading

0 comments on commit e54c11d

Please sign in to comment.