Skip to content

Commit 0477fd0

Browse files
committed
Remove Clone requirement for argument types
- Rework construction of the internal type-checking function to remove the clone requirement. Construct and call the type-check-function right at the site we already expand the probe argument closure, rather than outside. This means we don't need to expand things twice, and don't need to clone the closure, and so don't need the argument types to be cloneable either. - Fixup tests for new method, add some comments. - Update trybuild tests - Closes #136
1 parent 7a168c6 commit 0477fd0

File tree

7 files changed

+79
-111
lines changed

7 files changed

+79
-111
lines changed

.github/workflows/rust.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
runs-on: ${{ matrix.os }}
4747
strategy:
4848
matrix:
49-
os: [ "macos-15" ]
49+
os: [ "macos-15", "ubuntu-latest" ]
5050
# Test on the pinned rust-toolchain version.
5151
toolchain: [ "1.80.0" ]
5252
steps:
@@ -78,10 +78,10 @@ jobs:
7878
--release
7979
--verbose
8080
--workspace
81+
--exclude compile-errors
8182
--exclude does-it-work
8283
--exclude test-json
8384
--exclude test-unique-id
84-
--exclude compile-errors
8585
8686
stable-test-no-op:
8787
name: Test with probes disabled

tests/compile-errors/src/different-serializable-type.stderr

-18
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,3 @@
1-
error[E0277]: the trait bound `Expected: Clone` is not satisfied
2-
--> src/different-serializable-type.rs:31:20
3-
|
4-
31 | fn my_probe(_: Expected) {}
5-
| ^^^^^^^^ the trait `Clone` is not implemented for `Expected`
6-
|
7-
note: required by a bound in `usdt_types_must_be_clone_and_serialize`
8-
--> src/different-serializable-type.rs:28:1
9-
|
10-
28 | #[usdt::provider]
11-
| ^^^^^^^^^^^^^^^^^ required by this bound in `usdt_types_must_be_clone_and_serialize`
12-
= note: this error originates in the attribute macro `usdt::provider` (in Nightly builds, run with -Z macro-backtrace for more info)
13-
help: consider annotating `Expected` with `#[derive(Clone)]`
14-
|
15-
19 + #[derive(Clone)]
16-
20 | struct Expected {
17-
|
18-
191
error[E0277]: the trait bound `Different: Borrow<Expected>` is not satisfied
202
--> src/different-serializable-type.rs:28:1
213
|

usdt-attr-macro/src/lib.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Generate USDT probes from an attribute macro
22
3-
// Copyright 2021 Oxide Computer Company
3+
// Copyright 2024 Oxide Computer Company
44
//
55
// Licensed under the Apache License, Version 2.0 (the "License");
66
// you may not use this file except in compliance with the License.
@@ -144,7 +144,7 @@ fn generate_provider_item(
144144
quote! {
145145
const _: fn() = || {
146146
#(#use_statements)*
147-
fn usdt_types_must_be_clone_and_serialize<T: ?Sized + Clone + ::serde::Serialize>() {}
147+
fn usdt_types_must_be_serialize<T: ?Sized + ::serde::Serialize>() {}
148148
#(#check_fns)*
149149
};
150150
}
@@ -269,16 +269,12 @@ fn build_serializable_check_function<T>(ident: &T, fn_index: usize, arg_index: u
269269
where
270270
T: quote::ToTokens,
271271
{
272-
let fn_name = quote::format_ident!(
273-
"usdt_types_must_be_clone_and_serialize_{}_{}",
274-
fn_index,
275-
arg_index
276-
);
272+
let fn_name = quote::format_ident!("usdt_types_must_be_serialize_{}_{}", fn_index, arg_index);
277273
quote! {
278274
fn #fn_name() {
279275
// #ident must be in scope here, because this function is defined in the same module as
280276
// the actual probe functions, and thus shares any imports the consumer wants.
281-
usdt_types_must_be_clone_and_serialize::<#ident>()
277+
usdt_types_must_be_serialize::<#ident>()
282278
}
283279
}
284280
}

usdt-impl/src/common.rs

+43-75
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Shared code used in both the linker and no-linker implementations of this crate.
22
3-
// Copyright 2021 Oxide Computer Company
3+
// Copyright 2024 Oxide Computer Company
44
//
55
// Licensed under the Apache License, Version 2.0 (the "License");
66
// you may not use this file except in compliance with the License.
@@ -14,39 +14,29 @@
1414
// See the License for the specific language governing permissions and
1515
// limitations under the License.
1616

17-
use crate::{DataType, Provider};
17+
use crate::DataType;
1818
use proc_macro2::TokenStream;
1919
use quote::{format_ident, quote};
2020

21-
// Construct function call that is used internally in the UDST-generated macros, to allow
22-
// compile-time type checking of the lambda arguments.
23-
pub fn generate_type_check(
21+
/// Construct a function to type-check the argument closure.
22+
///
23+
/// This constructs a function that is never called, but is used to ensure that
24+
/// the closure provided to each probe macro returns arguments of the right
25+
/// type.
26+
pub fn construct_type_check(
2427
provider_name: &str,
25-
use_statements: &[syn::ItemUse],
2628
probe_name: &str,
29+
use_statements: &[syn::ItemUse],
2730
types: &[DataType],
2831
) -> TokenStream {
29-
// If the probe has zero arguments, verify that the result of calling the closure is `()`
30-
// Note that there's no need to clone the closure here, since () is Copy.
32+
// If there are zero arguments, we need to make sure we can assign the
33+
// result of the closure to ().
3134
if types.is_empty() {
3235
return quote! {
33-
let __usdt_private_args_lambda = $args_lambda;
34-
let _ = || {
35-
let _: () = __usdt_private_args_lambda();
36-
};
36+
let _: () = ($args_lambda)();
3737
};
3838
}
39-
40-
// For one or more arguments, verify that we can unpack the closure into a tuple of type
41-
// `(arg0, arg1, ...)`. We verify that we can pass those arguments to a type-check function
42-
// that is _similar to_, but not exactly the probe function signature. In particular, we try to
43-
// support passing things by value or reference, and take some form of reference to that thing.
44-
// The mapping is generally:
45-
//
46-
// T or &T -> Borrow<T>
47-
// Strings -> AsRef<str>
48-
// [T; N] or &[T] -> AsRef<[T]>
49-
let type_check_args = types
39+
let type_check_params = types
5040
.iter()
5141
.map(|typ| match typ {
5242
DataType::Serializable(ty) => {
@@ -84,28 +74,22 @@ pub fn generate_type_check(
8474
})
8575
.collect::<Vec<_>>();
8676

87-
// Unpack the tuple from the closure to `args.0, args.1, ...`.
88-
let expanded_lambda_args = (0..types.len())
77+
// Create a list of arguments `arg.0`, `arg.1`, ... to pass to the check
78+
// function.
79+
let type_check_args = (0..types.len())
8980
.map(|i| {
9081
let index = syn::Index::from(i);
9182
quote! { args.#index }
9283
})
9384
.collect::<Vec<_>>();
9485

95-
let preamble = unpack_argument_lambda(types, /* clone = */ true);
96-
97-
let type_check_function =
98-
format_ident!("__usdt_private_{}_{}_type_check", provider_name, probe_name);
86+
let type_check_fn = format_ident!("__usdt_private_{}_{}_type_check", provider_name, probe_name);
9987
quote! {
100-
let __usdt_private_args_lambda = $args_lambda;
10188
#[allow(unused_imports)]
10289
#(#use_statements)*
10390
#[allow(non_snake_case)]
104-
fn #type_check_function (#(#type_check_args),*) { }
105-
let _ = || {
106-
#preamble
107-
#type_check_function(#(#expanded_lambda_args),*);
108-
};
91+
fn #type_check_fn(#(#type_check_params),*) {}
92+
let _ = || { #type_check_fn(#(#type_check_args),*); };
10993
}
11094
}
11195

@@ -156,28 +140,24 @@ pub fn construct_probe_args(types: &[DataType]) -> (TokenStream, TokenStream) {
156140
(destructured_arg, register_arg)
157141
})
158142
.unzip();
159-
let preamble = unpack_argument_lambda(types, /* clone = */ false);
143+
let arg_lambda = call_argument_closure(types);
160144
let unpacked_args = quote! {
161-
#preamble
145+
#arg_lambda
162146
#(#unpacked_args)*
163147
};
164148
let in_regs = quote! { #(#in_regs,)* };
165149
(unpacked_args, in_regs)
166150
}
167151

168-
fn unpack_argument_lambda(types: &[DataType], clone: bool) -> TokenStream {
169-
let maybe_clone = if clone {
170-
quote! { .clone() }
171-
} else {
172-
quote! {}
173-
};
152+
/// Call the argument closure, assigning its output to `args`.
153+
pub fn call_argument_closure(types: &[DataType]) -> TokenStream {
174154
match types.len() {
175-
// Don't bother with arguments if there are none.
176-
0 => quote! { __usdt_private_args_lambda #maybe_clone (); },
155+
// Don't bother with any closure if there are no arguments.
156+
0 => quote! {},
177157
// Wrap a single argument in a tuple.
178-
1 => quote! { let args = (__usdt_private_args_lambda #maybe_clone (),); },
158+
1 => quote! { let args = (($args_lambda)(),); },
179159
// General case.
180-
_ => quote! { let args = __usdt_private_args_lambda #maybe_clone (); },
160+
_ => quote! { let args = ($args_lambda)(); },
181161
}
182162
}
183163

@@ -218,17 +198,18 @@ fn asm_type_convert(typ: &DataType, input: TokenStream) -> (TokenStream, TokenSt
218198
}
219199
}
220200

201+
/// Create the top-level probe macro.
202+
///
203+
/// This takes the implementation block constructed elsewhere, and builds out
204+
/// the actual macro users call in their code to fire the probe.
221205
pub(crate) fn build_probe_macro(
222206
config: &crate::CompileProvidersConfig,
223-
provider: &Provider,
224207
probe_name: &str,
225208
types: &[DataType],
226209
impl_block: TokenStream,
227210
) -> TokenStream {
228211
let module = config.module_ident();
229212
let macro_name = config.probe_ident(probe_name);
230-
let type_check_block =
231-
generate_type_check(&provider.name, &provider.use_statements, probe_name, types);
232213
let no_args_match = if types.is_empty() {
233214
quote! { () => { crate::#module::#macro_name!(|| ()) }; }
234215
} else {
@@ -243,7 +224,6 @@ pub(crate) fn build_probe_macro(
243224
};
244225
($args_lambda:expr) => {
245226
{
246-
#type_check_block
247227
#impl_block
248228
}
249229
};
@@ -263,20 +243,16 @@ mod tests {
263243
use dtrace_parser::Sign;
264244

265245
#[test]
266-
fn test_generate_type_check_empty() {
267-
let types = &[];
246+
fn test_construct_type_check_empty() {
268247
let expected = quote! {
269-
let __usdt_private_args_lambda = $args_lambda;
270-
let _ = || {
271-
let _: () = __usdt_private_args_lambda();
272-
};
248+
let _ : () = ($args_lambda)();
273249
};
274-
let block = generate_type_check("", &[], "", types);
250+
let block = construct_type_check("", "", &[], &[]);
275251
assert_eq!(block.to_string(), expected.to_string());
276252
}
277253

278254
#[test]
279-
fn test_generate_type_check_native() {
255+
fn test_construct_type_check_native() {
280256
let provider = "provider";
281257
let probe = "probe";
282258
let types = &[
@@ -290,80 +266,72 @@ mod tests {
290266
})),
291267
];
292268
let expected = quote! {
293-
let __usdt_private_args_lambda = $args_lambda;
294269
#[allow(unused_imports)]
295270
#[allow(non_snake_case)]
296271
fn __usdt_private_provider_probe_type_check(
297272
_: impl ::std::borrow::Borrow<u8>,
298273
_: impl ::std::borrow::Borrow<i64>
299274
) { }
300275
let _ = || {
301-
let args = __usdt_private_args_lambda.clone()();
302276
__usdt_private_provider_probe_type_check(args.0, args.1);
303277
};
304278
};
305-
let block = generate_type_check(provider, &[], probe, types);
279+
let block = construct_type_check(provider, probe, &[], types);
306280
assert_eq!(block.to_string(), expected.to_string());
307281
}
308282

309283
#[test]
310-
fn test_generate_type_check_with_string() {
284+
fn test_construct_type_check_with_string() {
311285
let provider = "provider";
312286
let probe = "probe";
313287
let types = &[DataType::Native(dtrace_parser::DataType::String)];
314288
let use_statements = vec![];
315289
let expected = quote! {
316-
let __usdt_private_args_lambda = $args_lambda;
317290
#[allow(unused_imports)]
318291
#[allow(non_snake_case)]
319292
fn __usdt_private_provider_probe_type_check(_: impl AsRef<str>) { }
320293
let _ = || {
321-
let args = (__usdt_private_args_lambda.clone()(),);
322294
__usdt_private_provider_probe_type_check(args.0);
323295
};
324296
};
325-
let block = generate_type_check(provider, &use_statements, probe, types);
297+
let block = construct_type_check(provider, probe, &use_statements, types);
326298
assert_eq!(block.to_string(), expected.to_string());
327299
}
328300

329301
#[test]
330-
fn test_generate_type_check_with_shared_slice() {
302+
fn test_construct_type_check_with_shared_slice() {
331303
let provider = "provider";
332304
let probe = "probe";
333305
let types = &[DataType::Serializable(syn::parse_str("&[u8]").unwrap())];
334306
let use_statements = vec![];
335307
let expected = quote! {
336-
let __usdt_private_args_lambda = $args_lambda;
337308
#[allow(unused_imports)]
338309
#[allow(non_snake_case)]
339310
fn __usdt_private_provider_probe_type_check(_: impl AsRef<[u8]>) { }
340311
let _ = || {
341-
let args = (__usdt_private_args_lambda.clone()(),);
342312
__usdt_private_provider_probe_type_check(args.0);
343313
};
344314
};
345-
let block = generate_type_check(provider, &use_statements, probe, types);
315+
let block = construct_type_check(provider, probe, &use_statements, types);
346316
assert_eq!(block.to_string(), expected.to_string());
347317
}
348318

349319
#[test]
350-
fn test_generate_type_check_with_custom_type() {
320+
fn test_construct_type_check_with_custom_type() {
351321
let provider = "provider";
352322
let probe = "probe";
353323
let types = &[DataType::Serializable(syn::parse_str("MyType").unwrap())];
354324
let use_statements = vec![syn::parse2(quote! { use my_module::MyType; }).unwrap()];
355325
let expected = quote! {
356-
let __usdt_private_args_lambda = $args_lambda;
357326
#[allow(unused_imports)]
358327
use my_module::MyType;
359328
#[allow(non_snake_case)]
360329
fn __usdt_private_provider_probe_type_check(_: impl ::std::borrow::Borrow<MyType>) { }
361330
let _ = || {
362-
let args = (__usdt_private_args_lambda.clone()(),);
363331
__usdt_private_provider_probe_type_check(args.0);
364332
};
365333
};
366-
let block = generate_type_check(provider, &use_statements, probe, types);
334+
let block = construct_type_check(provider, probe, &use_statements, types);
367335
assert_eq!(block.to_string(), expected.to_string());
368336
}
369337

@@ -382,7 +350,7 @@ mod tests {
382350
let registers = ["x0", "x1"];
383351
let (args, regs) = construct_probe_args(types);
384352
let expected = quote! {
385-
let args = __usdt_private_args_lambda();
353+
let args = ($args_lambda)();
386354
let arg_0 = (*<_ as ::std::borrow::Borrow<*const u8>>::borrow(&args.0) as usize);
387355
let arg_1 = [(args.1.as_ref() as &str).as_bytes(), &[0_u8]].concat();
388356
};

usdt-impl/src/empty.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! Used on platforms without DTrace.
44
5-
// Copyright 2021 Oxide Computer Company
5+
// Copyright 2024 Oxide Computer Company
66
//
77
// Licensed under the Apache License, Version 2.0 (the "License");
88
// you may not use this file except in compliance with the License.
@@ -76,8 +76,20 @@ fn compile_probe(
7676
probe: &Probe,
7777
config: &crate::CompileProvidersConfig,
7878
) -> TokenStream {
79-
let impl_block = quote! { let _ = || (__usdt_private_args_lambda()) ; };
80-
common::build_probe_macro(config, provider, &probe.name, &probe.types, impl_block)
79+
// We don't need to add any actual probe emission code, but we do still want
80+
// to perform type-checking of its arguments here.
81+
let args = common::call_argument_closure(&probe.types);
82+
let type_check_fn = common::construct_type_check(
83+
&provider.name,
84+
&probe.name,
85+
&provider.use_statements,
86+
&probe.types,
87+
);
88+
let impl_block = quote! {
89+
#args
90+
#type_check_fn
91+
};
92+
common::build_probe_macro(config, &probe.name, &probe.types, impl_block)
8193
}
8294

8395
pub fn register_probes() -> Result<(), crate::Error> {

0 commit comments

Comments
 (0)