@@ -79,123 +79,272 @@ macro_rules! register_fields {
79
79
} ;
80
80
}
81
81
82
+ // TODO: All of the rustdoc tests below use a `should_fail` attribute instead of
83
+ // `should_panic` because a const panic will result in a failure to evaluate a
84
+ // constant value, and thus a compiler error. However, this means that these
85
+ // examples could break for unrelated reasons, trigger a compiler error, but not
86
+ // test the desired assertion any longer. This should be switched to a
87
+ // `should_panic`-akin attribute which works for const panics, once that is
88
+ // available.
89
+ /// Statically validate the size and offsets of the fields defined
90
+ /// within the register struct through the [`register_structs!`]
91
+ /// macro.
92
+ ///
93
+ /// This macro expands to an expression which contains static
94
+ /// assertions about various parameters of the individual fields in
95
+ /// the register struct definition. It will test for:
96
+ ///
97
+ /// - Proper start offset of padding fields. It will fail in cases
98
+ /// such as
99
+ ///
100
+ /// ```should_fail
101
+ /// # #[macro_use]
102
+ /// # extern crate tock_registers;
103
+ /// # use tock_registers::register_structs;
104
+ /// # use tock_registers::registers::ReadWrite;
105
+ /// register_structs! {
106
+ /// UartRegisters {
107
+ /// (0x04 => _reserved),
108
+ /// (0x08 => foo: ReadWrite<u32>),
109
+ /// (0x0C => @END),
110
+ /// }
111
+ /// }
112
+ /// # // This is required for rustdoc to not place this code snipped into an
113
+ /// # // fn main() {...} function.
114
+ /// # fn main() { }
115
+ /// ```
116
+ ///
117
+ /// In this example, the start offset of `_reserved` should have been `0x00`
118
+ /// instead of `0x04`.
119
+ ///
120
+ /// - Correct start offset and end offset (start offset of next field) in actual
121
+ /// fields. It will fail in cases such as
122
+ ///
123
+ /// ```should_fail
124
+ /// # #[macro_use]
125
+ /// # extern crate tock_registers;
126
+ /// # use tock_registers::register_structs;
127
+ /// # use tock_registers::registers::ReadWrite;
128
+ /// register_structs! {
129
+ /// UartRegisters {
130
+ /// (0x00 => foo: ReadWrite<u32>),
131
+ /// (0x05 => bar: ReadWrite<u32>),
132
+ /// (0x08 => @END),
133
+ /// }
134
+ /// }
135
+ /// # // This is required for rustdoc to not place this code snipped into an
136
+ /// # // fn main() {...} function.
137
+ /// # fn main() { }
138
+ /// ```
139
+ ///
140
+ /// In this example, the start offset of `bar` and thus the end offset of
141
+ /// `foo` should have been `0x04` instead of `0x05`.
142
+ ///
143
+ /// - Invalid alignment of fields.
144
+ ///
145
+ /// - That the end marker matches the actual generated struct size. This will
146
+ /// fail in cases such as
147
+ ///
148
+ /// ```should_fail
149
+ /// # #[macro_use]
150
+ /// # extern crate tock_registers;
151
+ /// # use tock_registers::register_structs;
152
+ /// # use tock_registers::registers::ReadWrite;
153
+ /// register_structs! {
154
+ /// UartRegisters {
155
+ /// (0x00 => foo: ReadWrite<u32>),
156
+ /// (0x04 => bar: ReadWrite<u32>),
157
+ /// (0x10 => @END),
158
+ /// }
159
+ /// }
160
+ /// # // This is required for rustdoc to not place this code snipped into an
161
+ /// # // fn main() {...} function.
162
+ /// # fn main() { }
163
+ /// ```
82
164
#[ macro_export]
83
165
macro_rules! test_fields {
166
+ // This macro works by iterating over all defined fields, until it hits an
167
+ // ($size:expr => @END) field. Each iteration generates an expression which,
168
+ // when evaluated, yields the current byte offset in the fields. Thus, when
169
+ // reading a field or padding, the field or padding length must be added to
170
+ // the returned size.
171
+ //
172
+ // By feeding this expression recursively into the macro, deeper invocations
173
+ // can continue validating fields through knowledge of the current offset
174
+ // and the remaining fields.
175
+ //
176
+ // The nested expression returned by this macro is guaranteed to be
177
+ // const-evaluable.
178
+
84
179
// Macro entry point.
85
180
( @root $struct: ident $( <$life: lifetime>) ? { $( $input: tt) * } ) => {
86
- $crate:: test_fields!( @munch $struct $( <$life>) ? sum ( $( $input) * ) -> { } ) ;
181
+ // Start recursion at offset 0.
182
+ $crate:: test_fields!( @munch $struct $( <$life>) ? ( $( $input) * ) : 0 ) ;
87
183
} ;
88
184
89
- // Print the tests once all fields have been munched.
90
- // We wrap the tests in a "detail" function that potentially takes a lifetime parameter, so that
91
- // the lifetime is declared inside it - therefore all types using the lifetime are well-defined.
92
- ( @munch $struct: ident $( <$life: lifetime>) ? $sum: ident
185
+ // Consume the ($size:expr => @END) field, which MUST be the last field in
186
+ // the register struct.
187
+ ( @munch $struct: ident $( <$life: lifetime>) ?
93
188
(
94
189
$( #[ $attr_end: meta] ) *
95
190
( $size: expr => @END ) ,
96
191
)
97
- -> { $ ( $ stmts: block ) * }
192
+ : $ stmts: expr
98
193
) => {
99
- {
100
- fn detail $( <$life>) ? ( )
101
- {
102
- let mut $sum: usize = 0 ;
103
- $( $stmts) *
104
- let size = core:: mem:: size_of:: <$struct $( <$life>) ?>( ) ;
194
+ const _: ( ) = {
195
+ // We've reached the end! Normally it is sufficient to compare the
196
+ // struct's size to the reported end offet. However, we must
197
+ // evaluate the previous iterations' expressions for them to have an
198
+ // effect anyways, so we can perform an internal sanity check on
199
+ // this value as well.
200
+ const sum: usize = $stmts;
201
+
202
+ const fn struct_size $( <$life>) ? ( ) -> usize
203
+ {
204
+ core:: mem:: size_of:: <$struct $( <$life>) ?>( )
205
+ }
206
+
105
207
assert!(
106
- size == $size,
107
- "Invalid size for struct {} (expected {:#X} but was {:#X})" ,
108
- stringify!( $struct) ,
109
- $size,
110
- size
208
+ struct_size( ) == $size,
209
+ "{}" ,
210
+ concat!(
211
+ "Invalid size for struct " ,
212
+ stringify!( $struct) ,
213
+ " (expected " ,
214
+ $size,
215
+ ", actual struct size differs)" ,
216
+ ) ,
111
217
) ;
112
- }
113
218
114
- detail( ) ;
115
- }
219
+ // Internal sanity check. If we have reached this point and
220
+ // correctly iterated over the struct's fields, the current offset
221
+ // and the claimed end offset MUST be equal.
222
+ assert!( sum == $size) ;
223
+ } ;
116
224
} ;
117
225
118
- // Munch field.
119
- ( @munch $struct: ident $( <$life: lifetime>) ? $sum : ident
226
+ // Consume a proper ($offset:expr => $field:ident: $ty:ty) field.
227
+ ( @munch $struct: ident $( <$life: lifetime>) ?
120
228
(
121
229
$( #[ $attr: meta] ) *
122
230
( $offset_start: expr => $vis: vis $field: ident: $ty: ty) ,
123
231
$( #[ $attr_next: meta] ) *
124
232
( $offset_end: expr => $( $next: tt) * ) ,
125
233
$( $after: tt) *
126
234
)
127
- -> { $ ( $ output: block ) * }
235
+ : $ output: expr
128
236
) => {
129
237
$crate:: test_fields!(
130
- @munch $struct $( <$life>) ? $sum (
238
+ @munch $struct $( <$life>) ? (
131
239
$( #[ $attr_next] ) *
132
240
( $offset_end => $( $next) * ) ,
133
241
$( $after) *
134
- ) -> {
135
- $( $output) *
136
- {
137
- assert!(
138
- $sum == $offset_start,
139
- "Invalid start offset for field {} (expected {:#X} but was {:#X})" ,
242
+ ) : {
243
+ // Evaluate the previous iterations' expression to determine the
244
+ // current offset.
245
+ const sum: usize = $output;
246
+
247
+ // Validate the start offset of the current field. This check is
248
+ // mostly relevant for when this is the first field in the
249
+ // struct, as any subsequent start offset error will be detected
250
+ // by an end offset error of the previous field.
251
+ assert!(
252
+ sum == $offset_start,
253
+ "{}" ,
254
+ concat!(
255
+ "Invalid start offset for field " ,
140
256
stringify!( $field) ,
257
+ " (expected " ,
141
258
$offset_start,
142
- $sum
143
- ) ;
144
- let align = core:: mem:: align_of:: <$ty>( ) ;
259
+ " but actual value differs)" ,
260
+ ) ,
261
+ ) ;
262
+
263
+ // Validate that the start offset of the current field within
264
+ // the struct matches the type's minimum alignment constraint.
265
+ const align: usize = core:: mem:: align_of:: <$ty>( ) ;
266
+ // Clippy can tell that (align - 1) is zero for some fields, so
267
+ // we allow this lint and further encapsule the assert! as an
268
+ // expression, such that the allow attr can apply.
269
+ #[ allow( clippy:: bad_bit_mask) ]
270
+ {
145
271
assert!(
146
- $sum & ( align - 1 ) == 0 ,
147
- "Invalid alignment for field {} (expected alignment of {:#X} but offset was {:#X})" ,
148
- stringify!( $field) ,
149
- align,
150
- $sum
272
+ sum & ( align - 1 ) == 0 ,
273
+ "{}" ,
274
+ concat!(
275
+ "Invalid alignment for field " ,
276
+ stringify!( $field) ,
277
+ " (offset differs from expected)" ,
278
+ ) ,
151
279
) ;
152
- $sum += core:: mem:: size_of:: <$ty>( ) ;
153
- assert!(
154
- $sum == $offset_end,
155
- "Invalid end offset for field {} (expected {:#X} but was {:#X})" ,
280
+ }
281
+
282
+ // Add the current field's length to the offset and validate the
283
+ // end offset of the field based on the next field's claimed
284
+ // start offset.
285
+ const new_sum: usize = sum + core:: mem:: size_of:: <$ty>( ) ;
286
+ assert!(
287
+ new_sum == $offset_end,
288
+ "{}" ,
289
+ concat!(
290
+ "Invalid end offset for field " ,
156
291
stringify!( $field) ,
292
+ " (expected " ,
157
293
$offset_end,
158
- $sum
159
- ) ;
160
- }
294
+ " but actual value differs)" ,
295
+ ) ,
296
+ ) ;
297
+
298
+ // Provide the updated offset to the next iteration
299
+ new_sum
161
300
}
162
301
) ;
163
302
} ;
164
303
165
- // Munch padding.
166
- ( @munch $struct: ident $( <$life: lifetime>) ? $sum : ident
304
+ // Consume a padding ($offset:expr => $padding:ident) field .
305
+ ( @munch $struct: ident $( <$life: lifetime>) ?
167
306
(
168
307
$( #[ $attr: meta] ) *
169
308
( $offset_start: expr => $padding: ident) ,
170
309
$( #[ $attr_next: meta] ) *
171
310
( $offset_end: expr => $( $next: tt) * ) ,
172
311
$( $after: tt) *
173
312
)
174
- -> { $ ( $ output: block ) * }
313
+ : $ output: expr
175
314
) => {
176
315
$crate:: test_fields!(
177
- @munch $struct $( <$life>) ? $sum (
316
+ @munch $struct $( <$life>) ? (
178
317
$( #[ $attr_next] ) *
179
318
( $offset_end => $( $next) * ) ,
180
319
$( $after) *
181
- ) -> {
182
- $( $output) *
183
- {
184
- assert!(
185
- $sum == $offset_start,
186
- "Invalid start offset for padding {} (expected {:#X} but was {:#X})" ,
320
+ ) : {
321
+ // Evaluate the previous iterations' expression to determine the
322
+ // current offset.
323
+ const sum: usize = $output;
324
+
325
+ // Validate the start offset of the current padding field. This
326
+ // check is mostly relevant for when this is the first field in
327
+ // the struct, as any subsequent start offset error will be
328
+ // detected by an end offset error of the previous field.
329
+ assert!(
330
+ sum == $offset_start,
331
+ concat!(
332
+ "Invalid start offset for padding " ,
187
333
stringify!( $padding) ,
334
+ " (expected " ,
188
335
$offset_start,
189
- $sum
190
- ) ;
191
- $sum = $offset_end;
192
- }
336
+ " but actual value differs)" ,
337
+ ) ,
338
+ ) ;
339
+
340
+ // The padding field is automatically sized. Provide the start
341
+ // offset of the next field to the next iteration.
342
+ $offset_end
193
343
}
194
344
) ;
195
345
} ;
196
346
}
197
347
198
- #[ cfg( feature = "std_unit_tests" ) ]
199
348
#[ macro_export]
200
349
macro_rules! register_structs {
201
350
{
@@ -208,33 +357,15 @@ macro_rules! register_structs {
208
357
} => {
209
358
$( $crate:: register_fields!( @root $( #[ $attr] ) * $vis_struct $name $( <$life>) ? { $( $fields) * } ) ; ) *
210
359
211
- #[ cfg( test) ]
212
- mod test_register_structs {
360
+ mod static_validate_register_structs {
213
361
$(
214
362
#[ allow( non_snake_case) ]
215
363
mod $name {
216
364
use super :: super :: * ;
217
- #[ test]
218
- fn test_offsets( ) {
219
- $crate:: test_fields!( @root $name $( <$life>) ? { $( $fields) * } )
220
- }
365
+
366
+ $crate:: test_fields!( @root $name $( <$life>) ? { $( $fields) * } ) ;
221
367
}
222
368
) *
223
369
}
224
370
} ;
225
371
}
226
-
227
- #[ cfg( not( feature = "std_unit_tests" ) ) ]
228
- #[ macro_export]
229
- macro_rules! register_structs {
230
- {
231
- $(
232
- $( #[ $attr: meta] ) *
233
- $vis_struct: vis $name: ident $( <$life: lifetime>) ? {
234
- $( $fields: tt ) *
235
- }
236
- ) ,*
237
- } => {
238
- $( $crate:: register_fields!( @root $( #[ $attr] ) * $vis_struct $name $( <$life>) ? { $( $fields) * } ) ; ) *
239
- } ;
240
- }
0 commit comments