Skip to content

Commit 77a9483

Browse files
committed
Add library support for generic lambdas
1 parent 84ac34f commit 77a9483

3 files changed

Lines changed: 231 additions & 31 deletions

File tree

‎libcc2rs-macros/src/lambda.rs‎

Lines changed: 132 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,38 @@ use syn::parse::{Parse, ParseStream};
99
use syn::{Block, Error, ExprClosure, Pat, Result, Stmt, Token, parse_macro_input};
1010

1111
struct Lambda {
12+
is_generic: bool,
1213
captures: Block,
13-
closure: ExprClosure,
14+
closures: Vec<ExprClosure>,
1415
}
1516

1617
impl Parse for Lambda {
1718
fn parse(input: ParseStream) -> Result<Self> {
19+
let is_generic = input.peek(syn::Ident);
20+
if is_generic {
21+
let marker: Ident = input.parse()?;
22+
if marker != "Generic" {
23+
return Err(Error::new(marker.span(), "expected `Generic`"));
24+
}
25+
input.parse::<Token![,]>()?;
26+
}
1827
let captures = input.parse()?;
1928
input.parse::<Token![,]>()?;
20-
let closure = input.parse()?;
21-
input.parse::<Option<Token![,]>>()?;
22-
Ok(Lambda { captures, closure })
29+
let mut closures = vec![input.parse()?];
30+
while input.parse::<Option<Token![,]>>()?.is_some() && !input.is_empty() {
31+
closures.push(input.parse()?);
32+
}
33+
if !is_generic && closures.len() != 1 {
34+
return Err(Error::new_spanned(
35+
&closures[1],
36+
"a non-generic lambda has one closure",
37+
));
38+
}
39+
Ok(Lambda {
40+
is_generic,
41+
captures,
42+
closures,
43+
})
2344
}
2445
}
2546

@@ -137,28 +158,49 @@ fn expand_lambda(lambda: Lambda, is_unsafe: bool) -> Result<proc_macro2::TokenSt
137158
inits.push(&init.expr);
138159
}
139160

140-
let mut params = Vec::new();
141-
let mut param_types = Vec::new();
142-
for input in &lambda.closure.inputs {
143-
let Pat::Type(pat) = input else {
144-
return Err(Error::new_spanned(input, "expected a typed parameter"));
161+
let captured: HashSet<String> = names.iter().map(|name| name.to_string()).collect();
162+
let mut calls = Vec::new();
163+
let mut sigs = Vec::new();
164+
let mut call_names = Vec::new();
165+
for (i, closure) in lambda.closures.iter().enumerate() {
166+
let mut params = Vec::new();
167+
let mut param_types = Vec::new();
168+
for input in &closure.inputs {
169+
let Pat::Type(pat) = input else {
170+
return Err(Error::new_spanned(input, "expected a typed parameter"));
171+
};
172+
params.push(pat);
173+
param_types.push(&pat.ty);
174+
}
175+
let output = &closure.output;
176+
let body = &closure.body;
177+
let body = rewrite_captures(quote! { #body }, &captured, false)?;
178+
let (receiver, body) = if is_unsafe {
179+
(quote! { &mut self }, quote! { unsafe { #body } })
180+
} else {
181+
(quote! { &self }, body)
145182
};
146-
params.push(pat);
147-
param_types.push(&pat.ty);
183+
let call_name = if lambda.is_generic {
184+
Ident::new(&format!("call_{i}"), proc_macro2::Span::call_site())
185+
} else {
186+
Ident::new("call", proc_macro2::Span::call_site())
187+
};
188+
calls.push(quote! {
189+
#[allow(unused_unsafe, clippy::too_many_arguments)]
190+
fn #call_name(#receiver #(, #params)*) #output {
191+
#body
192+
}
193+
});
194+
sigs.push(quote! { fn(#(#param_types),*) #output });
195+
call_names.push((call_name, closure.inputs.len()));
148196
}
149-
150-
let output = &lambda.closure.output;
151-
let body = &lambda.closure.body;
152-
let captured = names.iter().map(|name| name.to_string()).collect();
153-
let body = rewrite_captures(quote! { #body }, &captured, false)?;
154197
// The copy and move constructors of the lambda copy and move each
155198
// capture, and its destructor destroys them in reverse order, as chosen
156199
// from their types by the traits in libcc2rs::__capture.
157200
let reversed: Vec<_> = names.iter().rev().collect();
158-
let (receiver, body, destroy, constructor, copy_from, move_from) = if is_unsafe {
201+
let (receiver, destroy, constructor, copy_from, move_from) = if is_unsafe {
159202
(
160203
quote! { &mut self },
161-
quote! { unsafe { #body } },
162204
quote! {
163205
use ::libcc2rs::__capture::{DestroyNoneUnsafe as _, DestroyWithDtorUnsafe as _};
164206
unsafe {
@@ -192,7 +234,6 @@ fn expand_lambda(lambda: Lambda, is_unsafe: bool) -> Result<proc_macro2::TokenSt
192234
} else {
193235
(
194236
quote! { &self },
195-
body,
196237
quote! {
197238
use ::libcc2rs::__capture::{
198239
DestroyElems as _, DestroyNone as _, DestroyWithDtor as _,
@@ -217,15 +258,12 @@ fn expand_lambda(lambda: Lambda, is_unsafe: bool) -> Result<proc_macro2::TokenSt
217258
)
218259
};
219260

220-
Ok(quote! {{
261+
let lambda_struct = quote! {
221262
struct __Lambda {
222263
#(#names: #types,)*
223264
}
224265
impl __Lambda {
225-
#[allow(unused_unsafe, clippy::too_many_arguments)]
226-
fn call(#receiver #(, #params)*) #output {
227-
#body
228-
}
266+
#(#calls)*
229267
#[allow(unused_imports, unused_unsafe)]
230268
fn copy_from(#receiver) -> Self {
231269
#copy_from
@@ -239,12 +277,76 @@ fn expand_lambda(lambda: Lambda, is_unsafe: bool) -> Result<proc_macro2::TokenSt
239277
#destroy
240278
}
241279
}
242-
::libcc2rs::FnPtr::<fn(#(#param_types),*) #output>::#constructor(
243-
__Lambda { #(#names: #inits,)* },
244-
__Lambda::call,
245-
__Lambda::copy_from,
246-
__Lambda::move_from,
247-
__Lambda::destroy,
280+
};
281+
let lambda_init = quote! { __Lambda { #(#names: #inits,)* } };
282+
283+
if !lambda.is_generic {
284+
let sig = &sigs[0];
285+
return Ok(quote! {{
286+
#lambda_struct
287+
::libcc2rs::FnPtr::<#sig>::#constructor(
288+
#lambda_init,
289+
__Lambda::call,
290+
__Lambda::copy_from,
291+
__Lambda::move_from,
292+
__Lambda::destroy,
293+
)
294+
}});
295+
}
296+
297+
let mut specs = Vec::new();
298+
for (sig, (call_name, arity)) in sigs.iter().zip(&call_names) {
299+
let args: Vec<Ident> = (0..*arity)
300+
.map(|i| Ident::new(&format!("__a{i}"), proc_macro2::Span::call_site()))
301+
.collect();
302+
let call = if is_unsafe {
303+
quote! {
304+
|__l: &mut ::std::rc::Rc<::std::cell::UnsafeCell<__Lambda>> #(, #args)*| {
305+
unsafe { &mut *__l.get() }.#call_name(#(#args),*)
306+
}
307+
}
308+
} else {
309+
quote! {
310+
|__l: &::std::rc::Rc<__Lambda> #(, #args)*| __l.#call_name(#(#args),*)
311+
}
312+
};
313+
specs.push(quote! {
314+
::std::rc::Rc::new(::libcc2rs::FnPtr::<#sig>::#constructor(
315+
::std::rc::Rc::clone(__l),
316+
#call,
317+
|__l| ::std::rc::Rc::clone(__l),
318+
|__l| ::std::rc::Rc::clone(__l),
319+
|_| {},
320+
)) as ::std::rc::Rc<dyn ::std::any::Any>
321+
});
322+
}
323+
let (shared, copy_from, move_from, destroy) = if is_unsafe {
324+
(
325+
quote! { ::std::rc::Rc::new(::std::cell::UnsafeCell::new(#lambda_init)) },
326+
quote! { |__l| ::std::rc::Rc::new(::std::cell::UnsafeCell::new(
327+
unsafe { &mut *__l.get() }.copy_from()
328+
)) },
329+
quote! { |__l| ::std::rc::Rc::new(::std::cell::UnsafeCell::new(
330+
unsafe { &mut *__l.get() }.move_from()
331+
)) },
332+
quote! { |__l| unsafe { &mut *__l.get() }.destroy() },
333+
)
334+
} else {
335+
(
336+
quote! { ::std::rc::Rc::new(#lambda_init) },
337+
quote! { |__l| ::std::rc::Rc::new(__l.copy_from()) },
338+
quote! { |__l| ::std::rc::Rc::new(__l.move_from()) },
339+
quote! { |__l| __l.destroy() },
340+
)
341+
};
342+
Ok(quote! {{
343+
#lambda_struct
344+
::libcc2rs::FnPtr::<::libcc2rs::Generic>::from_generic_lambda(
345+
#shared,
346+
|__l| vec![#(#specs),*],
347+
#copy_from,
348+
#move_from,
349+
#destroy,
248350
)
249351
}})
250352
}

‎libcc2rs/src/fn_ptr.rs‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ trait Adapted: Any {
6060
}
6161

6262
fn destroy(&self) {}
63+
64+
fn specializations(&self) -> &[Rc<dyn Any>] {
65+
panic!("ub: not a generic lambda");
66+
}
6367
}
6468

6569
impl<T: FnSig> Adapted for T {
@@ -373,6 +377,100 @@ macro_rules! impl_fn_ptr_call {
373377
}
374378
impl_fn_ptr_call!();
375379

380+
#[derive(Clone, Copy)]
381+
pub struct Generic;
382+
383+
impl FnSig for Generic {
384+
type Args = ();
385+
type Ret = ();
386+
fn fn_addr(&self) -> usize {
387+
panic!("ub: a generic lambda has no single function address");
388+
}
389+
fn call_direct(self, _args: ()) {
390+
panic!("ub: calling a generic lambda without a signature");
391+
}
392+
}
393+
394+
struct GenericLambda<Captures> {
395+
captures: Captures,
396+
specializations: Vec<Rc<dyn Any>>,
397+
make_specializations: fn(&Captures) -> Vec<Rc<dyn Any>>,
398+
copy_from: fn(&Captures) -> Captures,
399+
move_from: fn(&Captures) -> Captures,
400+
destroy: fn(&Captures),
401+
}
402+
403+
impl<Captures: 'static> GenericLambda<Captures> {
404+
fn with_captures(&self, captures: Captures) -> Rc<dyn Adapted> {
405+
Rc::new(GenericLambda {
406+
specializations: (self.make_specializations)(&captures),
407+
captures,
408+
make_specializations: self.make_specializations,
409+
copy_from: self.copy_from,
410+
move_from: self.move_from,
411+
destroy: self.destroy,
412+
})
413+
}
414+
}
415+
416+
impl<Captures: 'static> Adapted for GenericLambda<Captures> {
417+
fn call_adapted(&self, _args: ArgList) -> ArgRepr {
418+
panic!("ub: calling a generic lambda without a signature");
419+
}
420+
421+
fn copy_from(&self) -> Option<Rc<dyn Adapted>> {
422+
Some(self.with_captures((self.copy_from)(&self.captures)))
423+
}
424+
425+
fn move_from(&self) -> Option<Rc<dyn Adapted>> {
426+
Some(self.with_captures((self.move_from)(&self.captures)))
427+
}
428+
429+
fn destroy(&self) {
430+
(self.destroy)(&self.captures)
431+
}
432+
433+
fn specializations(&self) -> &[Rc<dyn Any>] {
434+
&self.specializations
435+
}
436+
}
437+
438+
impl FnPtr<Generic> {
439+
pub fn from_generic_lambda<Captures: 'static>(
440+
captures: Captures,
441+
make_specializations: fn(&Captures) -> Vec<Rc<dyn Any>>,
442+
copy_from: fn(&Captures) -> Captures,
443+
move_from: fn(&Captures) -> Captures,
444+
destroy: fn(&Captures),
445+
) -> Self {
446+
let lambda: Rc<dyn Adapted> = Rc::new(GenericLambda {
447+
specializations: make_specializations(&captures),
448+
captures,
449+
make_specializations,
450+
copy_from,
451+
move_from,
452+
destroy,
453+
});
454+
FnPtr {
455+
addr: Rc::as_ptr(&lambda) as *const () as usize,
456+
current: None,
457+
original: Some(lambda),
458+
}
459+
}
460+
461+
pub fn spec<U: FnSig>(&self) -> FnPtr<U> {
462+
assert!(!self.is_null(), "ub: null fn pointer call");
463+
self.original
464+
.as_ref()
465+
.unwrap()
466+
.specializations()
467+
.iter()
468+
.find_map(|spec| spec.downcast_ref::<FnPtr<U>>())
469+
.expect("ub: calling through incompatible fn pointer type")
470+
.clone()
471+
}
472+
}
473+
376474
impl<T: FnSig> Clone for FnPtr<T> {
377475
fn clone(&self) -> Self {
378476
FnPtr {

‎libcc2rs/src/lib.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ mod fn_ptr_arg;
3535
pub use fn_ptr_arg::{ArgRepr, FnPtrArg, record_from_repr};
3636

3737
mod fn_ptr;
38-
pub use fn_ptr::FnPtr;
38+
pub use fn_ptr::{FnPtr, Generic};
3939

4040
mod capture;
4141
pub use capture::{Destructor, DestructorUnsafe, MoveCtor, MoveCtorUnsafe};

0 commit comments

Comments
 (0)