Skip to content

Commit f41a0b3

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 f41a0b3

File tree

7 files changed

+71
-108
lines changed

7 files changed

+71
-108
lines changed

.github/workflows/rust.yml

-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ jobs:
8181
--exclude does-it-work
8282
--exclude test-json
8383
--exclude test-unique-id
84-
--exclude compile-errors
8584
8685
stable-test-no-op:
8786
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

+42-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,23 @@ 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 = unpack_argument_lambda(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+
fn unpack_argument_lambda(types: &[DataType]) -> TokenStream {
174153
match types.len() {
175-
// Don't bother with arguments if there are none.
176-
0 => quote! { __usdt_private_args_lambda #maybe_clone (); },
154+
// Don't bother with any closure if there are no arguments.
155+
0 => quote! {},
177156
// Wrap a single argument in a tuple.
178-
1 => quote! { let args = (__usdt_private_args_lambda #maybe_clone (),); },
157+
1 => quote! { let args = (($args_lambda)(),); },
179158
// General case.
180-
_ => quote! { let args = __usdt_private_args_lambda #maybe_clone (); },
159+
_ => quote! { let args = ($args_lambda)(); },
181160
}
182161
}
183162

@@ -218,17 +197,18 @@ fn asm_type_convert(typ: &DataType, input: TokenStream) -> (TokenStream, TokenSt
218197
}
219198
}
220199

200+
/// Create the top-level probe macro.
201+
///
202+
/// This takes the implementation block constructed elsewhere, and builds out
203+
/// the actual macro users call in their code to fire the probe.
221204
pub(crate) fn build_probe_macro(
222205
config: &crate::CompileProvidersConfig,
223-
provider: &Provider,
224206
probe_name: &str,
225207
types: &[DataType],
226208
impl_block: TokenStream,
227209
) -> TokenStream {
228210
let module = config.module_ident();
229211
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);
232212
let no_args_match = if types.is_empty() {
233213
quote! { () => { crate::#module::#macro_name!(|| ()) }; }
234214
} else {
@@ -243,7 +223,6 @@ pub(crate) fn build_probe_macro(
243223
};
244224
($args_lambda:expr) => {
245225
{
246-
#type_check_block
247226
#impl_block
248227
}
249228
};
@@ -263,20 +242,16 @@ mod tests {
263242
use dtrace_parser::Sign;
264243

265244
#[test]
266-
fn test_generate_type_check_empty() {
267-
let types = &[];
245+
fn test_construct_type_check_empty() {
268246
let expected = quote! {
269-
let __usdt_private_args_lambda = $args_lambda;
270-
let _ = || {
271-
let _: () = __usdt_private_args_lambda();
272-
};
247+
let _ : () = ($args_lambda)();
273248
};
274-
let block = generate_type_check("", &[], "", types);
249+
let block = construct_type_check("", "", &[], &[]);
275250
assert_eq!(block.to_string(), expected.to_string());
276251
}
277252

278253
#[test]
279-
fn test_generate_type_check_native() {
254+
fn test_construct_type_check_native() {
280255
let provider = "provider";
281256
let probe = "probe";
282257
let types = &[
@@ -290,80 +265,72 @@ mod tests {
290265
})),
291266
];
292267
let expected = quote! {
293-
let __usdt_private_args_lambda = $args_lambda;
294268
#[allow(unused_imports)]
295269
#[allow(non_snake_case)]
296270
fn __usdt_private_provider_probe_type_check(
297271
_: impl ::std::borrow::Borrow<u8>,
298272
_: impl ::std::borrow::Borrow<i64>
299273
) { }
300274
let _ = || {
301-
let args = __usdt_private_args_lambda.clone()();
302275
__usdt_private_provider_probe_type_check(args.0, args.1);
303276
};
304277
};
305-
let block = generate_type_check(provider, &[], probe, types);
278+
let block = construct_type_check(provider, probe, &[], types);
306279
assert_eq!(block.to_string(), expected.to_string());
307280
}
308281

309282
#[test]
310-
fn test_generate_type_check_with_string() {
283+
fn test_construct_type_check_with_string() {
311284
let provider = "provider";
312285
let probe = "probe";
313286
let types = &[DataType::Native(dtrace_parser::DataType::String)];
314287
let use_statements = vec![];
315288
let expected = quote! {
316-
let __usdt_private_args_lambda = $args_lambda;
317289
#[allow(unused_imports)]
318290
#[allow(non_snake_case)]
319291
fn __usdt_private_provider_probe_type_check(_: impl AsRef<str>) { }
320292
let _ = || {
321-
let args = (__usdt_private_args_lambda.clone()(),);
322293
__usdt_private_provider_probe_type_check(args.0);
323294
};
324295
};
325-
let block = generate_type_check(provider, &use_statements, probe, types);
296+
let block = construct_type_check(provider, probe, &use_statements, types);
326297
assert_eq!(block.to_string(), expected.to_string());
327298
}
328299

329300
#[test]
330-
fn test_generate_type_check_with_shared_slice() {
301+
fn test_construct_type_check_with_shared_slice() {
331302
let provider = "provider";
332303
let probe = "probe";
333304
let types = &[DataType::Serializable(syn::parse_str("&[u8]").unwrap())];
334305
let use_statements = vec![];
335306
let expected = quote! {
336-
let __usdt_private_args_lambda = $args_lambda;
337307
#[allow(unused_imports)]
338308
#[allow(non_snake_case)]
339309
fn __usdt_private_provider_probe_type_check(_: impl AsRef<[u8]>) { }
340310
let _ = || {
341-
let args = (__usdt_private_args_lambda.clone()(),);
342311
__usdt_private_provider_probe_type_check(args.0);
343312
};
344313
};
345-
let block = generate_type_check(provider, &use_statements, probe, types);
314+
let block = construct_type_check(provider, probe, &use_statements, types);
346315
assert_eq!(block.to_string(), expected.to_string());
347316
}
348317

349318
#[test]
350-
fn test_generate_type_check_with_custom_type() {
319+
fn test_construct_type_check_with_custom_type() {
351320
let provider = "provider";
352321
let probe = "probe";
353322
let types = &[DataType::Serializable(syn::parse_str("MyType").unwrap())];
354323
let use_statements = vec![syn::parse2(quote! { use my_module::MyType; }).unwrap()];
355324
let expected = quote! {
356-
let __usdt_private_args_lambda = $args_lambda;
357325
#[allow(unused_imports)]
358326
use my_module::MyType;
359327
#[allow(non_snake_case)]
360328
fn __usdt_private_provider_probe_type_check(_: impl ::std::borrow::Borrow<MyType>) { }
361329
let _ = || {
362-
let args = (__usdt_private_args_lambda.clone()(),);
363330
__usdt_private_provider_probe_type_check(args.0);
364331
};
365332
};
366-
let block = generate_type_check(provider, &use_statements, probe, types);
333+
let block = construct_type_check(provider, probe, &use_statements, types);
367334
assert_eq!(block.to_string(), expected.to_string());
368335
}
369336

@@ -382,7 +349,7 @@ mod tests {
382349
let registers = ["x0", "x1"];
383350
let (args, regs) = construct_probe_args(types);
384351
let expected = quote! {
385-
let args = __usdt_private_args_lambda();
352+
let args = ($args_lambda)();
386353
let arg_0 = (*<_ as ::std::borrow::Borrow<*const u8>>::borrow(&args.0) as usize);
387354
let arg_1 = [(args.1.as_ref() as &str).as_bytes(), &[0_u8]].concat();
388355
};

usdt-impl/src/empty.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,17 @@ 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+
let type_check_fn = common::construct_type_check(
80+
&provider.name,
81+
&probe.name,
82+
&provider.use_statements,
83+
&probe.types,
84+
);
85+
let impl_block = quote! {
86+
let args = || { ($args_lambda)() };
87+
#type_check_fn
88+
};
89+
common::build_probe_macro(config, &probe.name, &probe.types, impl_block)
8190
}
8291

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

usdt-impl/src/linker.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ fn compile_probe(
170170
syn::parse2::<syn::FnArg>(quote! { _: #ty }).unwrap()
171171
});
172172
let (unpacked_args, in_regs) = common::construct_probe_args(types);
173+
let type_check_fn =
174+
common::construct_type_check(&provider.name, probe_name, &provider.use_statements, types);
173175

174176
#[cfg(target_arch = "x86_64")]
175177
let call_instruction = quote! { "call {extern_probe_fn}" };
@@ -199,6 +201,7 @@ fn compile_probe(
199201
unsafe {
200202
if #is_enabled_fn() != 0 {
201203
#unpacked_args
204+
#type_check_fn
202205
::std::arch::asm!(
203206
".reference {typedefs}",
204207
#call_instruction,
@@ -213,7 +216,7 @@ fn compile_probe(
213216
}
214217
};
215218

216-
common::build_probe_macro(config, provider, probe_name, types, impl_block)
219+
common::build_probe_macro(config, probe_name, types, impl_block)
217220
}
218221

219222
#[derive(Debug, Default, Clone)]

0 commit comments

Comments
 (0)