blob: 3d6eee0a8310b74bd7cd11fc316a1e00bc71113c [file] [log] [blame]
David Tolnay7db73692019-10-20 14:51:12 -04001use crate::namespace::Namespace;
2use crate::syntax::atom::Atom;
David Tolnay39d575f2020-03-03 00:10:56 -08003use crate::syntax::{self, check, Api, ExternFn, ExternType, Struct, Type, Types};
David Tolnay7db73692019-10-20 14:51:12 -04004use proc_macro2::{Ident, Span, TokenStream};
5use quote::{format_ident, quote, quote_spanned};
6use syn::{Error, ItemMod, Result, Token};
7
8pub fn bridge(namespace: &Namespace, ffi: ItemMod) -> Result<TokenStream> {
9 let ident = &ffi.ident;
10 let content = ffi.content.ok_or(Error::new(
11 Span::call_site(),
12 "#[cxx::bridge] module must have inline contents",
13 ))?;
14 let apis = syntax::parse_items(content.1)?;
15 let ref types = Types::collect(&apis)?;
16 check::typecheck(&apis, types)?;
17
18 let mut expanded = TokenStream::new();
19 let mut hidden = TokenStream::new();
20 let mut has_rust_type = false;
21
22 for api in &apis {
23 if let Api::RustType(ety) = api {
24 expanded.extend(expand_rust_type(ety));
25 if !has_rust_type {
David Tolnay199d7352020-01-20 18:40:10 -080026 hidden.extend(quote!(
27 const fn __assert_sized<T>() {}
28 ));
David Tolnay7db73692019-10-20 14:51:12 -040029 has_rust_type = true;
30 }
31 let ident = &ety.ident;
32 hidden.extend(quote!(__assert_sized::<#ident>();));
33 }
34 }
35
36 for api in &apis {
37 match api {
38 Api::Include(_) | Api::RustType(_) => {}
39 Api::Struct(strct) => expanded.extend(expand_struct(strct)),
40 Api::CxxType(ety) => expanded.extend(expand_cxx_type(ety)),
41 Api::CxxFunction(efn) => {
42 expanded.extend(expand_cxx_function_shim(namespace, efn, types));
43 }
44 Api::RustFunction(efn) => {
45 hidden.extend(expand_rust_function_shim(namespace, efn, types))
46 }
47 }
48 }
49
50 for ty in types {
51 if let Type::RustBox(ty) = ty {
52 if let Type::Ident(ident) = &ty.inner {
53 if Atom::from(ident).is_none() {
54 hidden.extend(expand_rust_box(namespace, ident));
55 }
56 }
57 } else if let Type::UniquePtr(ptr) = ty {
58 if let Type::Ident(ident) = &ptr.inner {
59 if Atom::from(ident).is_none() {
60 expanded.extend(expand_unique_ptr(namespace, ident));
61 }
62 }
63 }
64 }
65
66 // Work around https://github.com/rust-lang/rust/issues/67851.
67 if !hidden.is_empty() {
68 expanded.extend(quote! {
69 #[doc(hidden)]
70 const _: () = {
71 #hidden
72 };
73 });
74 }
75
76 let attrs = ffi
77 .attrs
78 .into_iter()
79 .filter(|attr| attr.path.is_ident("doc"));
80 let vis = &ffi.vis;
81
82 Ok(quote! {
83 #(#attrs)*
84 #[deny(improper_ctypes)]
85 #[allow(non_snake_case)]
86 #vis mod #ident {
87 #expanded
88 }
89 })
90}
91
92fn expand_struct(strct: &Struct) -> TokenStream {
93 let ident = &strct.ident;
94 let doc = &strct.doc;
95 let derives = &strct.derives;
96 let fields = strct.fields.iter().map(|field| {
97 // This span on the pub makes "private type in public interface" errors
98 // appear in the right place.
99 let vis = Token![pub](field.ident.span());
100 quote!(#vis #field)
101 });
102 quote! {
103 #doc
104 #[derive(#(#derives),*)]
105 #[repr(C)]
106 pub struct #ident {
107 #(#fields,)*
108 }
109 }
110}
111
112fn expand_cxx_type(ety: &ExternType) -> TokenStream {
113 let ident = &ety.ident;
114 let doc = &ety.doc;
115 quote! {
116 #doc
117 #[repr(C)]
118 pub struct #ident {
119 _private: ::cxx::private::Opaque,
120 }
121 }
122}
123
124fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
125 let ident = &efn.ident;
David Tolnay39d575f2020-03-03 00:10:56 -0800126 let args = efn.args.iter().map(|arg| {
127 let ident = &arg.ident;
128 let ty = expand_extern_type(&arg.ty);
129 if types.needs_indirect_abi(&arg.ty) {
130 quote!(#ident: *mut #ty)
131 } else {
132 quote!(#ident: #ty)
133 }
134 });
David Tolnay7db73692019-10-20 14:51:12 -0400135 let ret = expand_extern_return_type(&efn.ret, types);
136 let mut outparam = None;
137 if indirect_return(&efn.ret, types) {
138 let ret = expand_extern_type(efn.ret.as_ref().unwrap());
139 outparam = Some(quote!(__return: *mut #ret));
140 }
David Tolnaye43b7372020-01-08 08:46:20 -0800141 let link_name = format!("{}cxxbridge01${}", namespace, ident);
David Tolnay7db73692019-10-20 14:51:12 -0400142 let local_name = format_ident!("__{}", ident);
143 quote! {
144 #[link_name = #link_name]
145 fn #local_name(#(#args,)* #outparam) #ret;
146 }
147}
148
149fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
150 let ident = &efn.ident;
151 let doc = &efn.doc;
152 let decl = expand_cxx_function_decl(namespace, efn, types);
153 let args = &efn.args;
154 let ret = expand_return_type(&efn.ret);
155 let indirect_return = indirect_return(&efn.ret, types);
156 let vars = efn.args.iter().map(|arg| {
157 let var = &arg.ident;
158 match &arg.ty {
159 Type::Ident(ident) if ident == "String" => {
160 quote!(#var.as_mut_ptr() as *mut ::cxx::private::RustString)
161 }
162 Type::RustBox(_) => quote!(::std::boxed::Box::into_raw(#var)),
163 Type::UniquePtr(_) => quote!(::cxx::UniquePtr::into_raw(#var)),
164 Type::Ref(ty) => match &ty.inner {
165 Type::Ident(ident) if ident == "String" => {
166 quote!(::cxx::private::RustString::from_ref(#var))
167 }
168 _ => quote!(#var),
169 },
170 Type::Str(_) => quote!(::cxx::private::RustStr::from(#var)),
171 ty if types.needs_indirect_abi(ty) => quote!(#var.as_mut_ptr()),
172 _ => quote!(#var),
173 }
174 });
175 let mut setup = efn
176 .args
177 .iter()
178 .filter(|arg| types.needs_indirect_abi(&arg.ty))
179 .map(|arg| {
180 let var = &arg.ident;
181 // These are arguments for which C++ has taken ownership of the data
182 // behind the mut reference it received.
183 quote! {
184 let mut #var = std::mem::MaybeUninit::new(#var);
185 }
186 })
187 .collect::<TokenStream>();
188 let local_name = format_ident!("__{}", ident);
189 let call = if indirect_return {
190 let ret = expand_extern_type(efn.ret.as_ref().unwrap());
191 setup.extend(quote! {
192 let mut __return = ::std::mem::MaybeUninit::<#ret>::uninit();
193 #local_name(#(#vars,)* __return.as_mut_ptr());
194 });
195 quote! {
196 __return.assume_init()
197 }
198 } else {
199 quote! {
200 #local_name(#(#vars),*)
201 }
202 };
203 let expr = efn
204 .ret
205 .as_ref()
206 .and_then(|ret| match ret {
207 Type::Ident(ident) if ident == "String" => Some(quote!(#call.into_string())),
208 Type::RustBox(_) => Some(quote!(::std::boxed::Box::from_raw(#call))),
209 Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::from_raw(#call))),
210 Type::Ref(ty) => match &ty.inner {
211 Type::Ident(ident) if ident == "String" => Some(quote!(#call.as_string())),
212 _ => None,
213 },
214 Type::Str(_) => Some(quote!(#call.as_str())),
215 _ => None,
216 })
217 .unwrap_or(call);
218 quote! {
219 #doc
220 pub fn #ident(#(#args),*) #ret {
221 extern "C" {
222 #decl
223 }
224 unsafe {
225 #setup
226 #expr
227 }
228 }
229 }
230}
231
232fn expand_rust_type(ety: &ExternType) -> TokenStream {
233 let ident = &ety.ident;
234 quote! {
235 use super::#ident;
236 }
237}
238
239fn expand_rust_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
240 let ident = &efn.ident;
David Tolnay39d575f2020-03-03 00:10:56 -0800241 let args = efn.args.iter().map(|arg| {
242 let ident = &arg.ident;
243 let ty = expand_extern_type(&arg.ty);
244 if types.needs_indirect_abi(&arg.ty) {
245 quote!(#ident: *mut #ty)
246 } else {
247 quote!(#ident: #ty)
248 }
249 });
David Tolnay7db73692019-10-20 14:51:12 -0400250 let vars = efn.args.iter().map(|arg| {
251 let ident = &arg.ident;
David Tolnay17955e22020-01-20 17:58:24 -0800252 match &arg.ty {
David Tolnay40226ab2020-03-03 00:05:35 -0800253 Type::Ident(i) if i == "String" => quote!(::std::mem::take((*#ident).as_mut_string())),
254 Type::RustBox(_) => quote!(::std::boxed::Box::from_raw(#ident)),
255 Type::UniquePtr(_) => quote!(::cxx::UniquePtr::from_raw(#ident)),
David Tolnay17955e22020-01-20 17:58:24 -0800256 Type::Ref(ty) => match &ty.inner {
David Tolnay40226ab2020-03-03 00:05:35 -0800257 Type::Ident(i) if i == "String" => quote!(#ident.as_string()),
258 _ => quote!(#ident),
David Tolnay17955e22020-01-20 17:58:24 -0800259 },
David Tolnay40226ab2020-03-03 00:05:35 -0800260 Type::Str(_) => quote!(#ident.as_str()),
261 ty if types.needs_indirect_abi(ty) => quote!(::std::ptr::read(#ident)),
262 _ => quote!(#ident),
David Tolnay7db73692019-10-20 14:51:12 -0400263 }
264 });
265 let mut outparam = None;
266 let call = quote! {
267 ::cxx::private::catch_unwind(__fn, move || super::#ident(#(#vars),*))
268 };
269 let mut expr = efn
270 .ret
271 .as_ref()
272 .and_then(|ret| match ret {
273 Type::Ident(ident) if ident == "String" => {
274 Some(quote!(::cxx::private::RustString::from(#call)))
275 }
276 Type::RustBox(_) => Some(quote!(::std::boxed::Box::into_raw(#call))),
277 Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::into_raw(#call))),
278 Type::Ref(ty) => match &ty.inner {
279 Type::Ident(ident) if ident == "String" => {
280 Some(quote!(::cxx::private::RustString::from_ref(#call)))
281 }
282 _ => None,
283 },
284 Type::Str(_) => Some(quote!(::cxx::private::RustStr::from(#call))),
285 _ => None,
286 })
287 .unwrap_or(call);
288 if indirect_return(&efn.ret, types) {
289 let ret = expand_extern_type(efn.ret.as_ref().unwrap());
290 outparam = Some(quote!(__return: *mut #ret));
291 expr = quote!(::std::ptr::write(__return, #expr));
292 }
293 let ret = expand_extern_return_type(&efn.ret, types);
David Tolnaye43b7372020-01-08 08:46:20 -0800294 let link_name = format!("{}cxxbridge01${}", namespace, ident);
David Tolnay7db73692019-10-20 14:51:12 -0400295 let local_name = format_ident!("__{}", ident);
296 let catch_unwind_label = format!("::{}", ident);
297 quote! {
298 #[doc(hidden)]
299 #[export_name = #link_name]
300 unsafe extern "C" fn #local_name(#(#args,)* #outparam) #ret {
301 let __fn = concat!(module_path!(), #catch_unwind_label);
302 #expr
303 }
304 }
305}
306
307fn expand_rust_box(namespace: &Namespace, ident: &Ident) -> TokenStream {
David Tolnay9081beb2020-03-01 19:51:46 -0800308 let link_prefix = format!("cxxbridge01$box${}{}$", namespace, ident);
David Tolnay7db73692019-10-20 14:51:12 -0400309 let link_uninit = format!("{}uninit", link_prefix);
310 let link_set_raw = format!("{}set_raw", link_prefix);
311 let link_drop = format!("{}drop", link_prefix);
312 let link_deref = format!("{}deref", link_prefix);
313 let link_deref_mut = format!("{}deref_mut", link_prefix);
314
315 let local_prefix = format_ident!("{}__box_", ident);
316 let local_uninit = format_ident!("{}uninit", local_prefix);
317 let local_set_raw = format_ident!("{}set_raw", local_prefix);
318 let local_drop = format_ident!("{}drop", local_prefix);
319 let local_deref = format_ident!("{}deref", local_prefix);
320 let local_deref_mut = format_ident!("{}deref_mut", local_prefix);
321
322 let span = ident.span();
323 quote_spanned! {span=>
324 #[doc(hidden)]
325 #[export_name = #link_uninit]
326 unsafe extern "C" fn #local_uninit(
327 this: *mut ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>,
328 ) {
329 ::std::ptr::write(
330 this,
331 ::std::boxed::Box::new(::std::mem::MaybeUninit::uninit()),
332 );
333 }
334 #[doc(hidden)]
335 #[export_name = #link_set_raw]
336 unsafe extern "C" fn #local_set_raw(
337 this: *mut ::std::boxed::Box<#ident>,
338 raw: *mut #ident,
339 ) {
340 ::std::ptr::write(this, ::std::boxed::Box::from_raw(raw));
341 }
342 #[doc(hidden)]
343 #[export_name = #link_drop]
344 unsafe extern "C" fn #local_drop(this: *mut ::std::boxed::Box<#ident>) {
345 ::std::ptr::drop_in_place(this);
346 }
347 #[doc(hidden)]
348 #[export_name = #link_deref]
349 unsafe extern "C" fn #local_deref(
350 this: *const ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>,
351 ) -> *const ::std::mem::MaybeUninit<#ident> {
352 &**this
353 }
354 #[doc(hidden)]
355 #[export_name = #link_deref_mut]
356 unsafe extern "C" fn #local_deref_mut(
357 this: *mut ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>,
358 ) -> *mut ::std::mem::MaybeUninit<#ident> {
359 &mut **this
360 }
361 }
362}
363
364fn expand_unique_ptr(namespace: &Namespace, ident: &Ident) -> TokenStream {
David Tolnaye43b7372020-01-08 08:46:20 -0800365 let prefix = format!("cxxbridge01$unique_ptr${}{}$", namespace, ident);
David Tolnay7db73692019-10-20 14:51:12 -0400366 let link_null = format!("{}null", prefix);
367 let link_new = format!("{}new", prefix);
368 let link_raw = format!("{}raw", prefix);
369 let link_get = format!("{}get", prefix);
370 let link_release = format!("{}release", prefix);
371 let link_drop = format!("{}drop", prefix);
372
373 quote! {
374 unsafe impl ::cxx::private::UniquePtrTarget for #ident {
375 fn __null() -> *mut ::std::ffi::c_void {
376 extern "C" {
377 #[link_name = #link_null]
378 fn __null(this: *mut *mut ::std::ffi::c_void);
379 }
380 let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
381 unsafe { __null(&mut repr) }
382 repr
383 }
384 fn __new(mut value: Self) -> *mut ::std::ffi::c_void {
385 extern "C" {
386 #[link_name = #link_new]
387 fn __new(this: *mut *mut ::std::ffi::c_void, value: *mut #ident);
388 }
389 let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
390 unsafe { __new(&mut repr, &mut value) }
391 repr
392 }
393 unsafe fn __raw(raw: *mut Self) -> *mut ::std::ffi::c_void {
394 extern "C" {
395 #[link_name = #link_raw]
396 fn __raw(this: *mut *mut ::std::ffi::c_void, raw: *mut #ident);
397 }
398 let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
399 __raw(&mut repr, raw);
400 repr
401 }
402 unsafe fn __get(repr: *mut ::std::ffi::c_void) -> *const Self {
403 extern "C" {
404 #[link_name = #link_get]
405 fn __get(this: *const *mut ::std::ffi::c_void) -> *const #ident;
406 }
407 __get(&repr)
408 }
409 unsafe fn __release(mut repr: *mut ::std::ffi::c_void) -> *mut Self {
410 extern "C" {
411 #[link_name = #link_release]
412 fn __release(this: *mut *mut ::std::ffi::c_void) -> *mut #ident;
413 }
414 __release(&mut repr)
415 }
416 unsafe fn __drop(mut repr: *mut ::std::ffi::c_void) {
417 extern "C" {
418 #[link_name = #link_drop]
419 fn __drop(this: *mut *mut ::std::ffi::c_void);
420 }
421 __drop(&mut repr);
422 }
423 }
424 }
425}
426
427fn expand_return_type(ret: &Option<Type>) -> TokenStream {
428 match ret {
429 Some(ret) => quote!(-> #ret),
430 None => TokenStream::new(),
431 }
432}
433
434fn indirect_return(ret: &Option<Type>, types: &Types) -> bool {
435 ret.as_ref()
436 .map_or(false, |ret| types.needs_indirect_abi(ret))
437}
438
439fn expand_extern_type(ty: &Type) -> TokenStream {
440 match ty {
441 Type::Ident(ident) if ident == "String" => quote!(::cxx::private::RustString),
442 Type::RustBox(ty) | Type::UniquePtr(ty) => {
443 let inner = &ty.inner;
444 quote!(*mut #inner)
445 }
446 Type::Ref(ty) => match &ty.inner {
447 Type::Ident(ident) if ident == "String" => quote!(&::cxx::private::RustString),
448 _ => quote!(#ty),
449 },
450 Type::Str(_) => quote!(::cxx::private::RustStr),
451 _ => quote!(#ty),
452 }
453}
454
455fn expand_extern_return_type(ret: &Option<Type>, types: &Types) -> TokenStream {
456 let ret = match ret {
457 Some(ret) if !types.needs_indirect_abi(ret) => ret,
458 _ => return TokenStream::new(),
459 };
460 let ty = expand_extern_type(ret);
461 quote!(-> #ty)
462}