blob: 71d22f366ca56e615a8e526e21234520efcbebaa [file] [log] [blame]
Marcel Hlopko42abfc82021-08-09 07:03:17 +00001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
Michael Forstercc5941a2021-10-07 07:12:24 +00005use anyhow::{anyhow, bail, Result};
Marcel Hlopko884ae7f2021-08-18 13:58:22 +00006use ffi_types::*;
Marcel Hlopko42abfc82021-08-09 07:03:17 +00007use ir::*;
8use itertools::Itertools;
Googler5ea88642021-09-29 08:05:59 +00009use proc_macro2::{Ident, Literal, TokenStream};
Marcel Hlopko42abfc82021-08-09 07:03:17 +000010use quote::format_ident;
11use quote::quote;
Devin Jeanpierre273eeae2021-10-06 13:29:35 +000012use std::collections::BTreeSet;
Michael Forstere9c881c2021-10-01 09:38:19 +000013use std::io::Write;
Marcel Hlopko42abfc82021-08-09 07:03:17 +000014use std::iter::Iterator;
15use std::panic::catch_unwind;
16use std::process;
Michael Forstere9c881c2021-10-01 09:38:19 +000017use std::process::{Command, Stdio};
Marcel Hlopko42abfc82021-08-09 07:03:17 +000018
Marcel Hlopko45fba972021-08-23 19:52:20 +000019/// FFI equivalent of `Bindings`.
20#[repr(C)]
21pub struct FfiBindings {
22 rs_api: FfiU8SliceBox,
23 rs_api_impl: FfiU8SliceBox,
24}
25
26/// Deserializes IR from `json` and generates bindings source code.
Marcel Hlopko42abfc82021-08-09 07:03:17 +000027///
28/// This function panics on error.
29///
30/// Ownership:
31/// * function doesn't take ownership of (in other words it borrows) the param `json`
32/// * function passes ownership of the returned value to the caller
33///
34/// Safety:
35/// * function expects that param `json` is a FfiU8Slice for a valid array of bytes with the
36/// given size.
37/// * function expects that param `json` doesn't change during the call.
38#[no_mangle]
Marcel Hlopko45fba972021-08-23 19:52:20 +000039pub unsafe extern "C" fn GenerateBindingsImpl(json: FfiU8Slice) -> FfiBindings {
Marcel Hlopko42abfc82021-08-09 07:03:17 +000040 catch_unwind(|| {
Marcel Hlopko45fba972021-08-23 19:52:20 +000041 // It is ok to abort here.
42 let Bindings { rs_api, rs_api_impl } = generate_bindings(json.as_slice()).unwrap();
43
44 FfiBindings {
45 rs_api: FfiU8SliceBox::from_boxed_slice(rs_api.into_bytes().into_boxed_slice()),
46 rs_api_impl: FfiU8SliceBox::from_boxed_slice(
47 rs_api_impl.into_bytes().into_boxed_slice(),
48 ),
49 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +000050 })
51 .unwrap_or_else(|_| process::abort())
52}
53
Marcel Hlopko45fba972021-08-23 19:52:20 +000054/// Source code for generated bindings.
55struct Bindings {
56 // Rust source code.
57 rs_api: String,
58 // C++ source code.
59 rs_api_impl: String,
Marcel Hlopko42abfc82021-08-09 07:03:17 +000060}
61
Marcel Hlopko45fba972021-08-23 19:52:20 +000062fn generate_bindings(json: &[u8]) -> Result<Bindings> {
63 let ir = deserialize_ir(json)?;
64 let rs_api = generate_rs_api(&ir)?;
65 let rs_api_impl = generate_rs_api_impl(&ir)?;
66 Ok(Bindings { rs_api, rs_api_impl })
67}
68
Devin Jeanpierre273eeae2021-10-06 13:29:35 +000069/// Source code with attached information about how to modify the parent crate.
70///
71/// For example, the snippet `vec![].into_raw_parts()` is not valid unless the `vec_into_raw_parts`
72/// feature is enabled. So such a snippet should be represented as:
73///
74/// ```
75/// RsSnippet {
76/// features: btree_set!["vec_into_raw_parts"],
77/// tokens: quote!{vec![].into_raw_parts()},
78/// }
79/// ```
80struct RsSnippet {
81 /// Rust feature flags used by this snippet.
82 features: BTreeSet<Ident>,
83 /// The snippet itself, as a token stream.
84 tokens: TokenStream,
85}
86
87impl From<TokenStream> for RsSnippet {
88 fn from(tokens: TokenStream) -> Self {
89 RsSnippet { features: BTreeSet::new(), tokens }
90 }
91}
92
Marcel Hlopko3164eee2021-08-24 20:09:22 +000093/// If we know the original C++ function is codegenned and already compatible with `extern "C"`
94/// calling convention we skip creating/calling the C++ thunk since we can call the original C++
95/// directly.
96fn can_skip_cc_thunk(func: &Func) -> bool {
97 // Inline functions may not be codegenned in the C++ library since Clang doesn't know if Rust
98 // calls the function or not. Therefore in order to make inline functions callable from Rust we
99 // need to generate a C++ file that defines a thunk that delegates to the original inline
100 // function. When compiled, Clang will emit code for this thunk and Rust code will call the
101 // thunk when the user wants to call the original inline function.
102 //
103 // This is not great runtime-performance-wise in regular builds (inline function will not be
104 // inlined, there will always be a function call), but it is correct. ThinLTO builds will be
105 // able to see through the thunk and inline code across the language boundary. For non-ThinLTO
106 // builds we plan to implement <internal link> which removes the runtime performance overhead.
107 !func.is_inline
108}
109
Michael Forstered642022021-10-04 09:48:25 +0000110/// Generates Rust source code for a given `Func`.
111///
112/// Returns the generated function and the thunk as a tuple.
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000113fn generate_func(func: &Func) -> Result<(RsSnippet, RsSnippet)> {
Michael Forstered642022021-10-04 09:48:25 +0000114 let mangled_name = &func.mangled_name;
115 let ident = make_ident(&func.identifier.identifier);
116 let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
Michael Forster409d9412021-10-07 08:35:29 +0000117 let doc_comment = generate_doc_comment(&func.doc_comment);
Michael Forstered642022021-10-04 09:48:25 +0000118 // TODO(hlopko): do not emit `-> ()` when return type is void, it's implicit.
119 let return_type_name = format_rs_type(&func.return_type.rs_type)?;
120
121 let param_idents =
122 func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
123
124 let param_types =
125 func.params.iter().map(|p| format_rs_type(&p.type_.rs_type)).collect::<Result<Vec<_>>>()?;
126
127 let api_func = quote! {
Michael Forster409d9412021-10-07 08:35:29 +0000128 #doc_comment
Michael Forstered642022021-10-04 09:48:25 +0000129 #[inline(always)]
130 pub fn #ident( #( #param_idents: #param_types ),* ) -> #return_type_name {
131 unsafe { crate::detail::#thunk_ident( #( #param_idents ),* ) }
132 }
133 };
134
135 let thunk_attr = if can_skip_cc_thunk(func) {
136 quote! {#[link_name = #mangled_name]}
137 } else {
138 quote! {}
139 };
140
141 let thunk = quote! {
142 #thunk_attr
143 pub(crate) fn #thunk_ident( #( #param_idents: #param_types ),* ) -> #return_type_name ;
144 };
145
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000146 Ok((api_func.into(), thunk.into()))
Michael Forstered642022021-10-04 09:48:25 +0000147}
148
Michael Forstercc5941a2021-10-07 07:12:24 +0000149fn generate_doc_comment(comment: &Option<String>) -> TokenStream {
150 match comment {
Michael Forster028800b2021-10-05 12:39:59 +0000151 Some(text) => {
152 let doc = format!(" {}", text.replace("\n", "\n "));
153 quote! {#[doc=#doc]}
154 }
155 None => quote! {},
Michael Forstercc5941a2021-10-07 07:12:24 +0000156 }
157}
Michael Forsterdb8101a2021-10-08 06:56:03 +0000158/// Generates Rust source code for a given `Record` and associated assertions as a tuple.
159fn generate_record(record: &Record) -> Result<(RsSnippet, RsSnippet)> {
Michael Forstercc5941a2021-10-07 07:12:24 +0000160 let ident = make_ident(&record.identifier.identifier);
161 let doc_comment = generate_doc_comment(&record.doc_comment);
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000162 let field_idents =
163 record.fields.iter().map(|f| make_ident(&f.identifier.identifier)).collect_vec();
Michael Forstercc5941a2021-10-07 07:12:24 +0000164 let field_doc_coments =
165 record.fields.iter().map(|f| generate_doc_comment(&f.doc_comment)).collect_vec();
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000166 let field_types = record
167 .fields
168 .iter()
169 .map(|f| format_rs_type(&f.type_.rs_type))
170 .collect::<Result<Vec<_>>>()?;
Googlerec589eb2021-09-17 07:45:39 +0000171 let field_accesses = record
172 .fields
173 .iter()
174 .map(|f| {
175 if f.access == AccessSpecifier::Public {
176 quote! { pub }
177 } else {
178 quote! {}
179 }
180 })
181 .collect_vec();
Googlerec648ff2021-09-23 07:19:53 +0000182 let size = record.size;
183 let alignment = record.alignment;
Googleraaa0a532021-10-01 09:11:27 +0000184 let field_assertions =
185 record.fields.iter().zip(field_idents.iter()).map(|(field, field_ident)| {
186 let offset = field.offset;
187 quote! {
188 // The IR contains the offset in bits, while offset_of!()
189 // returns the offset in bytes, so we need to convert.
190 const_assert_eq!(offset_of!(#ident, #field_ident) * 8, #offset);
191 }
192 });
Michael Forsterdb8101a2021-10-08 06:56:03 +0000193 let mut record_features = BTreeSet::new();
194 let mut assertion_features = BTreeSet::new();
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000195
196 // TODO(mboehme): For the time being, we're using unstable features to
197 // be able to use offset_of!() in static assertions. This is fine for a
198 // prototype, but longer-term we want to either get those features
199 // stabilized or find an alternative. For more details, see
200 // b/200120034#comment15
Michael Forsterdb8101a2021-10-08 06:56:03 +0000201 assertion_features.insert(make_ident("const_ptr_offset_from"));
202 assertion_features.insert(make_ident("const_maybe_uninit_as_ptr"));
203 assertion_features.insert(make_ident("const_raw_ptr_deref"));
Devin Jeanpierre2ed14ec2021-10-06 11:32:19 +0000204
205 let derives = generate_copy_derives(record);
Devin Jeanpierre9227d2c2021-10-06 12:26:05 +0000206 let derives = if derives.is_empty() {
207 quote! {}
208 } else {
209 quote! {#[derive( #(#derives),* )]}
210 };
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000211 let unpin_impl;
212 if record.is_trivial_abi {
213 unpin_impl = quote! {};
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000214 } else {
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000215 // negative_impls are necessary for universal initialization due to Rust's coherence rules:
216 // PhantomPinned isn't enough to prove to Rust that a blanket impl that requires Unpin
217 // doesn't apply. See http://<internal link>=h.f6jp8ifzgt3n
Michael Forsterdb8101a2021-10-08 06:56:03 +0000218 record_features.insert(make_ident("negative_impls"));
219 unpin_impl = quote! {
220 __NEWLINE__ __NEWLINE__
221 impl !Unpin for #ident {}
222 };
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000223 }
224
Michael Forsterdb8101a2021-10-08 06:56:03 +0000225 let record_tokens = quote! {
Michael Forster028800b2021-10-05 12:39:59 +0000226 #doc_comment
Devin Jeanpierre9227d2c2021-10-06 12:26:05 +0000227 #derives
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000228 #[repr(C)]
229 pub struct #ident {
Michael Forstercc5941a2021-10-07 07:12:24 +0000230 #( #field_doc_coments #field_accesses #field_idents: #field_types, )*
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000231 }
Googlerec648ff2021-09-23 07:19:53 +0000232
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000233 #unpin_impl
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000234 };
235
Michael Forsterdb8101a2021-10-08 06:56:03 +0000236 let assertion_tokens = quote! {
237 const_assert_eq!(std::mem::size_of::<#ident>(), #size);
238 const_assert_eq!(std::mem::align_of::<#ident>(), #alignment);
239 #( #field_assertions )*
240 };
241
242 Ok((
243 RsSnippet { features: record_features, tokens: record_tokens },
244 RsSnippet { features: assertion_features, tokens: assertion_tokens },
245 ))
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000246}
247
Devin Jeanpierre2ed14ec2021-10-06 11:32:19 +0000248fn generate_copy_derives(record: &Record) -> Vec<Ident> {
249 if record.is_trivial_abi
250 && record.copy_constructor.access == ir::AccessSpecifier::Public
251 && record.copy_constructor.definition == SpecialMemberDefinition::Trivial
252 {
253 // TODO(b/202258760): Make `Copy` inclusion configurable.
254 vec![make_ident("Clone"), make_ident("Copy")]
255 } else {
256 vec![]
257 }
258}
259
Michael Forster523dbd42021-10-12 11:05:44 +0000260/// Generates Rust source code for a given `UnsupportedItem`.
261fn generate_unsupported(item: &UnsupportedItem) -> Result<TokenStream> {
262 let message =
263 format!("Error while generating bindings for item '{}':\n{}", &item.name, &item.message);
264 Ok(quote! { __COMMENT__ #message })
265}
266
Marcel Hlopko45fba972021-08-23 19:52:20 +0000267fn generate_rs_api(ir: &IR) -> Result<String> {
Michael Forstered642022021-10-04 09:48:25 +0000268 let mut items = vec![];
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000269 let mut thunks = vec![];
Michael Forsterdb8101a2021-10-08 06:56:03 +0000270 let mut assertions = vec![];
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000271
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000272 // TODO(jeanpierreda): Delete has_record, either in favor of using RsSnippet, or not having uses.
273 // See https://chat.google.com/room/AAAAnQmj8Qs/6QbkSvWcfhA
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000274 let mut has_record = false;
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000275 let mut features = BTreeSet::new();
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000276
Michael Forstered642022021-10-04 09:48:25 +0000277 for item in &ir.items {
278 match item {
279 Item::Func(func) => {
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000280 let (snippet, thunk) = generate_func(func)?;
281 features.extend(snippet.features);
282 features.extend(thunk.features);
283 items.push(snippet.tokens);
284 thunks.push(thunk.tokens);
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000285 }
Michael Forstered642022021-10-04 09:48:25 +0000286 Item::Record(record) => {
Michael Forsterdb8101a2021-10-08 06:56:03 +0000287 let (snippet, assertions_snippet) = generate_record(record)?;
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000288 features.extend(snippet.features);
Michael Forsterdb8101a2021-10-08 06:56:03 +0000289 features.extend(assertions_snippet.features);
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000290 items.push(snippet.tokens);
Michael Forsterdb8101a2021-10-08 06:56:03 +0000291 assertions.push(assertions_snippet.tokens);
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000292 has_record = true;
Michael Forstered642022021-10-04 09:48:25 +0000293 }
Michael Forster523dbd42021-10-12 11:05:44 +0000294 Item::UnsupportedItem(unsupported) => items.push(generate_unsupported(unsupported)?),
Michael Forstered642022021-10-04 09:48:25 +0000295 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000296 }
297
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000298 let mod_detail = if thunks.is_empty() {
299 quote! {}
300 } else {
301 quote! {
302 mod detail {
303 extern "C" {
304 #( #thunks )*
305 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000306 }
307 }
308 };
309
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000310 let imports = if has_record {
Googlerec648ff2021-09-23 07:19:53 +0000311 quote! {
Googleraaa0a532021-10-01 09:11:27 +0000312 use memoffset_unstable_const::offset_of;
313 use static_assertions::const_assert_eq;
Googlerec648ff2021-09-23 07:19:53 +0000314 }
Michael Forstered642022021-10-04 09:48:25 +0000315 } else {
316 quote! {}
Googlerec648ff2021-09-23 07:19:53 +0000317 };
318
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000319 let features = if features.is_empty() {
320 quote! {}
321 } else {
322 quote! {
323 #![feature( #(#features),* )]
324 }
325 };
326
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000327 let result = quote! {
Michael Forsterdb8101a2021-10-08 06:56:03 +0000328 #features __NEWLINE__ __NEWLINE__
329 #imports __NEWLINE__ __NEWLINE__
Googlerec648ff2021-09-23 07:19:53 +0000330
Michael Forsterdb8101a2021-10-08 06:56:03 +0000331 #( #items __NEWLINE__ __NEWLINE__ )*
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000332
Michael Forsterdb8101a2021-10-08 06:56:03 +0000333 #mod_detail __NEWLINE__ __NEWLINE__
334
335 #( #assertions __NEWLINE__ __NEWLINE__ )*
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000336 };
337
Michael Forsterdb8101a2021-10-08 06:56:03 +0000338 rustfmt(token_stream_printer::tokens_to_string(result)?)
Michael Forstere9c881c2021-10-01 09:38:19 +0000339}
340
341fn rustfmt(input: String) -> Result<String> {
342 // TODO(forster): This should use rustfmt as a library as soon as b/200503084 is fixed.
Michael Forstere9c881c2021-10-01 09:38:19 +0000343
344 let rustfmt = "third_party/unsupported_toolchains/rust/toolchains/nightly/bin/rustfmt";
345
346 let mut child = Command::new(rustfmt)
Michael Forster028800b2021-10-05 12:39:59 +0000347 .args(&[
Michael Forster523dbd42021-10-12 11:05:44 +0000348 // TODO(forster): Add a way to specify this as a command line parameter.
Michael Forster028800b2021-10-05 12:39:59 +0000349 "--config-path=external/rustfmt/rustfmt.toml",
Michael Forster523dbd42021-10-12 11:05:44 +0000350 // We are representing doc comments as attributes in the token stream and use rustfmt
351 // to unpack them again.
Michael Forster028800b2021-10-05 12:39:59 +0000352 "--config=normalize_doc_attributes=true",
Michael Forster523dbd42021-10-12 11:05:44 +0000353 // We don't want rustfmt to reflow C++ doc comments, so we turn off wrapping globally
354 // and reflow generated comments manually.
355 "--config=wrap_comments=false",
Michael Forster028800b2021-10-05 12:39:59 +0000356 ])
Michael Forstere9c881c2021-10-01 09:38:19 +0000357 .stdin(Stdio::piped())
358 .stdout(Stdio::piped())
359 .spawn()
360 .expect(&format!("Failed to spawn rustfmt at '{}'", rustfmt));
361
362 let mut stdin = child.stdin.take().expect("Failed to open rustfmt stdin");
363 std::thread::spawn(move || {
364 stdin.write_all(input.as_bytes()).expect("Failed to write to rustfmt stdin");
365 });
366 let output = child.wait_with_output().expect("Failed to read rustfmt stdout");
367
Michael Forster136f0aa2021-10-01 12:43:14 +0000368 if !output.status.success() {
369 // The rustfmt error message has already been printed to stderr.
370 bail!("Unable to format output with rustfmt");
371 }
372
Michael Forstere9c881c2021-10-01 09:38:19 +0000373 Ok(String::from_utf8_lossy(&output.stdout).to_string())
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000374}
375
376fn make_ident(ident: &str) -> Ident {
377 format_ident!("{}", ident)
378}
379
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000380fn format_rs_type(ty: &ir::RsType) -> Result<TokenStream> {
381 let ptr_fragment = match ty.name.as_str() {
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000382 "*mut" => Some(quote! {*mut}),
383 "*const" => Some(quote! {*const}),
384 _ => None,
385 };
386 match ptr_fragment {
387 Some(ptr_fragment) => {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000388 if ty.type_params.len() != 1 {
389 return Err(anyhow!(
390 "Invalid pointer type (need exactly 1 type parameter): {:?}",
391 ty
392 ));
393 }
394 let nested_type = format_rs_type(&ty.type_params[0])?;
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000395 Ok(quote! {#ptr_fragment #nested_type})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000396 }
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000397 None => {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000398 if ty.type_params.len() > 0 {
399 return Err(anyhow!("Type not yet supported: {:?}", ty));
400 }
Devin Jeanpierre35639a72021-10-08 11:39:59 +0000401 match ty.name.as_str() {
402 "()" => Ok(quote! {()}),
403 name => {
404 let ident = make_ident(name);
405 Ok(quote! {#ident})
406 }
407 }
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000408 }
409 }
410}
411
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000412fn format_cc_type(ty: &ir::CcType) -> Result<TokenStream> {
413 let const_fragment = if ty.is_const {
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000414 quote! {const}
415 } else {
416 quote! {}
417 };
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000418 match ty.name.as_str() {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000419 "*" => {
420 if ty.type_params.len() != 1 {
421 return Err(anyhow!(
422 "Invalid pointer type (need exactly 1 type parameter): {:?}",
423 ty
424 ));
425 }
426 assert_eq!(ty.type_params.len(), 1);
427 let nested_type = format_cc_type(&ty.type_params[0])?;
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000428 Ok(quote! {#nested_type * #const_fragment})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000429 }
430 ident => {
431 if ty.type_params.len() > 0 {
432 return Err(anyhow!("Type not yet supported: {:?}", ty));
433 }
434 let ident = make_ident(ident);
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000435 Ok(quote! {#ident #const_fragment})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000436 }
437 }
438}
439
Googler5ea88642021-09-29 08:05:59 +0000440fn cc_struct_layout_assertion(record: &Record) -> TokenStream {
441 let record_ident = make_ident(&record.identifier.identifier);
442 let size = Literal::usize_unsuffixed(record.size);
443 let alignment = Literal::usize_unsuffixed(record.alignment);
444 let field_assertions = record.fields.iter().map(|field| {
445 let field_ident = make_ident(&field.identifier.identifier);
446 let offset = Literal::usize_unsuffixed(field.offset);
447 // The IR contains the offset in bits, while C++'s offsetof()
448 // returns the offset in bytes, so we need to convert.
449 quote! {
450 static_assert(offsetof(#record_ident, #field_ident) * 8 == #offset);
451 }
452 });
453 quote! {
454 static_assert(sizeof(#record_ident) == #size);
455 static_assert(alignof(#record_ident) == #alignment);
456 #( #field_assertions )*
457 }
458}
459
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000460fn generate_rs_api_impl(ir: &IR) -> Result<String> {
461 // This function uses quote! to generate C++ source code out of convenience. This is a bold idea
462 // so we have to continously evaluate if it still makes sense or the cost of working around
463 // differences in Rust and C++ tokens is greather than the value added.
464 //
465 // See rs_bindings_from_cc/token_stream_printer.rs for a list
466 // of supported placeholders.
467 let mut thunks = vec![];
Michael Forster7ef80732021-10-01 18:12:19 +0000468 for func in ir.functions() {
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000469 if can_skip_cc_thunk(&func) {
470 continue;
471 }
472
473 let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
474 let ident = make_ident(&func.identifier.identifier);
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000475 let return_type_name = format_cc_type(&func.return_type.cc_type)?;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000476
477 let param_idents =
478 func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
479
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000480 let param_types = func
481 .params
482 .iter()
483 .map(|p| format_cc_type(&p.type_.cc_type))
484 .collect::<Result<Vec<_>>>()?;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000485
486 thunks.push(quote! {
487 extern "C" #return_type_name #thunk_ident( #( #param_types #param_idents ),* ) {
488 return #ident( #( #param_idents ),* );
489 }
490 });
491 }
492
Michael Forster7ef80732021-10-01 18:12:19 +0000493 let layout_assertions = ir.records().map(cc_struct_layout_assertion);
Googler5ea88642021-09-29 08:05:59 +0000494
Michael Forster7ef80732021-10-01 18:12:19 +0000495 let standard_headers =
496 if ir.records().next().is_none() { vec![] } else { vec![make_ident("cstddef")] };
Googler5ea88642021-09-29 08:05:59 +0000497
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000498 // In order to generate C++ thunk in all the cases Clang needs to be able to access declarations
499 // from public headers of the C++ library.
500 let includes = ir.used_headers.iter().map(|i| &i.name);
501
502 let result = quote! {
Googler5ea88642021-09-29 08:05:59 +0000503 #( __HASH_TOKEN__ include <#standard_headers> __NEWLINE__)*
Michael Forsterdb8101a2021-10-08 06:56:03 +0000504 #( __HASH_TOKEN__ include #includes __NEWLINE__)* __NEWLINE__
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000505
Michael Forsterdb8101a2021-10-08 06:56:03 +0000506 #( #thunks )* __NEWLINE__ __NEWLINE__
Googler5ea88642021-09-29 08:05:59 +0000507
Michael Forsterdb8101a2021-10-08 06:56:03 +0000508 #( #layout_assertions __NEWLINE__ __NEWLINE__ )*
Marcel Hlopkoc6b726c2021-10-07 06:53:09 +0000509
510 // To satisfy http://cs/symbol:devtools.metadata.Presubmit.CheckTerminatingNewline check.
511 __NEWLINE__
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000512 };
513
Michael Forsterdb8101a2021-10-08 06:56:03 +0000514 token_stream_printer::tokens_to_string(result)
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000515}
516
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000517#[cfg(test)]
518mod tests {
Michael Forstere9c881c2021-10-01 09:38:19 +0000519 use crate::rustfmt;
520
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000521 use super::Result;
Devin Jeanpierre2ed14ec2021-10-06 11:32:19 +0000522 use super::{generate_copy_derives, generate_rs_api, generate_rs_api_impl};
Michael Forstered642022021-10-04 09:48:25 +0000523 use anyhow::anyhow;
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000524 use ir::*;
Michael Forster028800b2021-10-05 12:39:59 +0000525 use ir_testing::{
526 ir_func, ir_id, ir_int, ir_int_param, ir_items, ir_public_trivial_special, ir_record,
527 };
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000528 use quote::quote;
Michael Forsterdb8101a2021-10-08 06:56:03 +0000529 use token_stream_printer::tokens_to_string;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000530
531 #[test]
Marcel Hlopko45fba972021-08-23 19:52:20 +0000532 fn test_simple_function() -> Result<()> {
Michael Forster3a4615d2021-10-05 10:40:36 +0000533 let ir = ir_items(vec![Item::Func(Func {
534 identifier: ir_id("add"),
535 mangled_name: "_Z3Addii".to_string(),
Michael Forster409d9412021-10-07 08:35:29 +0000536 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000537 return_type: ir_int(),
538 params: vec![ir_int_param("a"), ir_int_param("b")],
539 is_inline: false,
540 })]);
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000541 assert_eq!(
Marcel Hlopko45fba972021-08-23 19:52:20 +0000542 generate_rs_api(&ir)?,
Michael Forsterdb8101a2021-10-08 06:56:03 +0000543 rustfmt(tokens_to_string(quote! {
544 #[inline(always)]
545 pub fn add(a: i32, b: i32) -> i32 {
546 unsafe { crate::detail::__rust_thunk__add(a, b) }
547 } __NEWLINE__ __NEWLINE__
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000548
Michael Forsterdb8101a2021-10-08 06:56:03 +0000549 mod detail {
550 extern "C" {
551 #[link_name = "_Z3Addii"]
552 pub(crate) fn __rust_thunk__add(a: i32, b: i32) -> i32;
553 } // extern
554 } // mod detail
555 })?)?
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000556 );
Michael Forsterdb8101a2021-10-08 06:56:03 +0000557
558 let rs_api = generate_rs_api_impl(&ir)?;
559 assert_eq!(rs_api.trim(), "");
560 assert!(rs_api.ends_with("\n"));
561
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000562 Ok(())
563 }
564
565 #[test]
566 fn test_inline_function() -> Result<()> {
567 let ir = IR {
568 used_headers: vec![
569 HeaderName { name: "foo/bar.h".to_string() },
570 HeaderName { name: "foo/baz.h".to_string() },
571 ],
Michael Forster7ef80732021-10-01 18:12:19 +0000572 items: vec![Item::Func(Func {
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000573 identifier: Identifier { identifier: "add".to_string() },
574 mangled_name: "_Z3Addii".to_string(),
Michael Forster409d9412021-10-07 08:35:29 +0000575 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000576 return_type: ir_int(),
577 params: vec![ir_int_param("a"), ir_int_param("b")],
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000578 is_inline: true,
Michael Forster7ef80732021-10-01 18:12:19 +0000579 })],
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000580 };
581
582 assert_eq!(
583 generate_rs_api(&ir)?,
Michael Forsterdb8101a2021-10-08 06:56:03 +0000584 rustfmt(tokens_to_string(quote! {
585 #[inline(always)]
586 pub fn add(a: i32, b: i32) -> i32 {
587 unsafe { crate::detail::__rust_thunk__add(a, b) }
588 } __NEWLINE__ __NEWLINE__
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000589
Michael Forsterdb8101a2021-10-08 06:56:03 +0000590 mod detail {
591 extern "C" {
592 pub(crate) fn __rust_thunk__add(a: i32, b: i32) -> i32;
593 } // extern
594 } // mod detail
595 })?)?
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000596 );
597
598 assert_eq!(
Marcel Hlopkoc6b726c2021-10-07 06:53:09 +0000599 generate_rs_api_impl(&ir)?.trim(),
Michael Forsterdb8101a2021-10-08 06:56:03 +0000600 tokens_to_string(quote! {
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000601 __HASH_TOKEN__ include "foo/bar.h" __NEWLINE__
Michael Forsterdb8101a2021-10-08 06:56:03 +0000602 __HASH_TOKEN__ include "foo/baz.h" __NEWLINE__ __NEWLINE__
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000603
604 extern "C" int __rust_thunk__add(int a, int b) {
605 return add(a, b);
606 }
607 })?
608 );
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000609 Ok(())
610 }
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000611
612 #[test]
613 fn test_simple_struct() -> Result<()> {
Michael Forster3a4615d2021-10-05 10:40:36 +0000614 let ir = ir_items(vec![Item::Record(Record {
615 identifier: ir_id("SomeStruct"),
Michael Forster028800b2021-10-05 12:39:59 +0000616 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000617 fields: vec![
618 Field {
619 identifier: ir_id("public_int"),
Michael Forstercc5941a2021-10-07 07:12:24 +0000620 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000621 type_: ir_int(),
622 access: AccessSpecifier::Public,
623 offset: 0,
624 },
625 Field {
626 identifier: ir_id("protected_int"),
Michael Forstercc5941a2021-10-07 07:12:24 +0000627 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000628 type_: ir_int(),
629 access: AccessSpecifier::Protected,
630 offset: 32,
631 },
632 Field {
633 identifier: ir_id("private_int"),
Michael Forstercc5941a2021-10-07 07:12:24 +0000634 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000635 type_: ir_int(),
636 access: AccessSpecifier::Private,
637 offset: 64,
638 },
639 ],
640 size: 12,
641 alignment: 4,
Michael Forster028800b2021-10-05 12:39:59 +0000642 copy_constructor: ir_public_trivial_special(),
643 move_constructor: ir_public_trivial_special(),
644 destructor: ir_public_trivial_special(),
Michael Forster3a4615d2021-10-05 10:40:36 +0000645 is_trivial_abi: true,
646 })]);
Michael Forster028800b2021-10-05 12:39:59 +0000647
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000648 assert_eq!(
649 generate_rs_api(&ir)?,
Michael Forsterdb8101a2021-10-08 06:56:03 +0000650 rustfmt(tokens_to_string(quote! {
651 #![feature(const_maybe_uninit_as_ptr, const_ptr_offset_from, const_raw_ptr_deref)] __NEWLINE__ __NEWLINE__
Googlerec648ff2021-09-23 07:19:53 +0000652
Michael Forsterdb8101a2021-10-08 06:56:03 +0000653 use memoffset_unstable_const::offset_of;
654 use static_assertions::const_assert_eq; __NEWLINE__ __NEWLINE__
Michael Forstere9c881c2021-10-01 09:38:19 +0000655
Michael Forsterdb8101a2021-10-08 06:56:03 +0000656 #[derive(Clone, Copy)]
657 #[repr(C)]
658 pub struct SomeStruct {
659 pub public_int: i32,
660 protected_int: i32,
661 private_int: i32,
662 } __NEWLINE__ __NEWLINE__
663
664 const_assert_eq!(std::mem::size_of::<SomeStruct>(), 12usize);
665 const_assert_eq!(std::mem::align_of::<SomeStruct>(), 4usize);
666 const_assert_eq!(offset_of!(SomeStruct, public_int) * 8, 0usize);
667 const_assert_eq!(offset_of!(SomeStruct, protected_int) * 8, 32usize);
668 const_assert_eq!(offset_of!(SomeStruct, private_int) * 8, 64usize);
669 })?)?
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000670 );
Googler5ea88642021-09-29 08:05:59 +0000671 assert_eq!(
Marcel Hlopkoc6b726c2021-10-07 06:53:09 +0000672 generate_rs_api_impl(&ir)?.trim(),
Michael Forsterdb8101a2021-10-08 06:56:03 +0000673 tokens_to_string(quote! {
674 __HASH_TOKEN__ include <cstddef> __NEWLINE__ __NEWLINE__ __NEWLINE__ __NEWLINE__
Googler5ea88642021-09-29 08:05:59 +0000675 static_assert(sizeof(SomeStruct) == 12);
676 static_assert(alignof(SomeStruct) == 4);
677 static_assert(offsetof(SomeStruct, public_int) * 8 == 0);
678 static_assert(offsetof(SomeStruct, protected_int) * 8 == 32);
679 static_assert(offsetof(SomeStruct, private_int) * 8 == 64);
680 })?
681 );
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000682 Ok(())
683 }
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000684
Devin Jeanpierre2ed14ec2021-10-06 11:32:19 +0000685 fn unwrapped_ir_record(name: &str) -> Record {
686 match ir_record(name) {
687 Item::Record(r) => r,
688 _ => unreachable!(),
689 }
690 }
691
692 #[test]
693 fn test_copy_derives() {
694 let record = unwrapped_ir_record("S");
695 assert_eq!(generate_copy_derives(&record), &["Clone", "Copy"]);
696 }
697
698 #[test]
699 fn test_copy_derives_not_is_trivial_abi() {
700 let mut record = unwrapped_ir_record("S");
701 record.is_trivial_abi = false;
702 assert_eq!(generate_copy_derives(&record), &[""; 0]);
703 }
704
705 #[test]
706 fn test_copy_derives_ctor_nonpublic() {
707 let mut record = unwrapped_ir_record("S");
708 for access in [ir::AccessSpecifier::Protected, ir::AccessSpecifier::Private] {
709 record.copy_constructor.access = access;
710 assert_eq!(generate_copy_derives(&record), &[""; 0]);
711 }
712 }
713
714 #[test]
715 fn test_copy_derives_ctor_deleted() {
716 let mut record = unwrapped_ir_record("S");
717 record.copy_constructor.definition = ir::SpecialMemberDefinition::Deleted;
718 assert_eq!(generate_copy_derives(&record), &[""; 0]);
719 }
720
721 #[test]
722 fn test_copy_derives_ctor_nontrivial() {
723 let mut record = unwrapped_ir_record("S");
724 record.copy_constructor.definition = ir::SpecialMemberDefinition::Nontrivial;
725 assert_eq!(generate_copy_derives(&record), &[""; 0]);
726 }
727
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000728 #[test]
729 fn test_ptr_func() -> Result<()> {
Michael Forster3a4615d2021-10-05 10:40:36 +0000730 let ir = ir_items(vec![Item::Func(Func {
731 identifier: Identifier { identifier: "Deref".to_string() },
732 mangled_name: "_Z5DerefPKPi".to_string(),
Michael Forster409d9412021-10-07 08:35:29 +0000733 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000734 return_type: MappedType {
735 rs_type: RsType {
736 name: "*mut".to_string(),
737 type_params: vec![RsType { name: "i32".to_string(), type_params: vec![] }],
738 },
739 cc_type: CcType {
740 name: "*".to_string(),
741 is_const: false,
742 type_params: vec![CcType {
743 name: "int".to_string(),
744 is_const: false,
745 type_params: vec![],
746 }],
747 },
748 },
749 params: vec![FuncParam {
750 identifier: Identifier { identifier: "p".to_string() },
751 type_: MappedType {
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000752 rs_type: RsType {
Michael Forster3a4615d2021-10-05 10:40:36 +0000753 name: "*const".to_string(),
754 type_params: vec![RsType {
755 name: "*mut".to_string(),
756 type_params: vec![RsType {
757 name: "i32".to_string(),
758 type_params: vec![],
759 }],
760 }],
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000761 },
762 cc_type: CcType {
763 name: "*".to_string(),
764 is_const: false,
765 type_params: vec![CcType {
Michael Forster3a4615d2021-10-05 10:40:36 +0000766 name: "*".to_string(),
767 is_const: true,
768 type_params: vec![CcType {
769 name: "int".to_string(),
770 is_const: false,
771 type_params: vec![],
772 }],
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000773 }],
774 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000775 },
Michael Forster3a4615d2021-10-05 10:40:36 +0000776 }],
777 is_inline: true,
778 })]);
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000779 assert_eq!(
780 generate_rs_api(&ir)?,
Michael Forsterdb8101a2021-10-08 06:56:03 +0000781 rustfmt(tokens_to_string(quote! {
782 #[inline(always)]
783 pub fn Deref(p: *const *mut i32) -> *mut i32 {
784 unsafe { crate::detail::__rust_thunk__Deref(p) }
785 } __NEWLINE__ __NEWLINE__
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000786
Michael Forsterdb8101a2021-10-08 06:56:03 +0000787 mod detail {
788 extern "C" {
789 pub(crate) fn __rust_thunk__Deref(p: *const *mut i32) -> *mut i32;
790 } // extern
791 } // mod detail
792 })?)?
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000793 );
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000794
795 assert_eq!(
Marcel Hlopkoc6b726c2021-10-07 06:53:09 +0000796 generate_rs_api_impl(&ir)?.trim(),
Michael Forsterdb8101a2021-10-08 06:56:03 +0000797 tokens_to_string(quote! {
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000798 extern "C" int* __rust_thunk__Deref(int* const * p) {
799 return Deref(p);
800 }
801 })?
802 );
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000803 Ok(())
804 }
Michael Forstered642022021-10-04 09:48:25 +0000805
806 #[test]
807 fn test_item_order() -> Result<()> {
Michael Forster3a4615d2021-10-05 10:40:36 +0000808 let ir = ir_items(vec![
809 ir_func("first_func"),
810 ir_record("FirstStruct"),
811 ir_func("second_func"),
812 ir_record("SecondStruct"),
813 ]);
Michael Forstered642022021-10-04 09:48:25 +0000814
815 let rs_api = generate_rs_api(&ir)?;
816
817 let idx = |s: &str| rs_api.find(s).ok_or(anyhow!("'{}' missing", s));
818
819 let f1 = idx("fn first_func")?;
820 let f2 = idx("fn second_func")?;
821 let s1 = idx("struct FirstStruct")?;
822 let s2 = idx("struct SecondStruct")?;
823 let t1 = idx("fn __rust_thunk__first_func")?;
824 let t2 = idx("fn __rust_thunk__second_func")?;
825
826 assert!(f1 < s1);
827 assert!(s1 < f2);
828 assert!(f2 < s2);
829 assert!(s2 < t1);
830 assert!(t1 < t2);
831
832 Ok(())
833 }
Michael Forster028800b2021-10-05 12:39:59 +0000834
835 #[test]
Michael Forster409d9412021-10-07 08:35:29 +0000836 fn test_doc_comment_func() -> Result<()> {
837 let ir = IR {
838 used_headers: vec![],
839 items: vec![Item::Func(Func {
840 identifier: ir_id("func"),
841 mangled_name: "foo".to_string(),
842 doc_comment: Some("Doc Comment".to_string()),
843 return_type: ir_int(),
844 is_inline: true,
845 params: vec![],
846 })],
847 };
848
849 assert!(
850 generate_rs_api(&ir)?.contains("/// Doc Comment\n#[inline(always)]\npub fn func"),
851 "func doc comment missing"
852 );
853
854 Ok(())
855 }
856
857 #[test]
858 fn test_doc_comment_record() -> Result<()> {
Michael Forster028800b2021-10-05 12:39:59 +0000859 let ir = IR {
860 used_headers: vec![],
861 items: vec![Item::Record(Record {
862 identifier: ir_id("SomeStruct"),
863 doc_comment: Some("Doc Comment\n\n * with bullet".to_string()),
864 alignment: 0,
865 size: 0,
Michael Forstercc5941a2021-10-07 07:12:24 +0000866 fields: vec![Field {
867 identifier: ir_id("field"),
868 doc_comment: Some("Field doc".to_string()),
869 type_: ir_int(),
870 access: AccessSpecifier::Public,
871 offset: 0,
872 }],
Michael Forster028800b2021-10-05 12:39:59 +0000873 copy_constructor: ir_public_trivial_special(),
874 move_constructor: ir_public_trivial_special(),
875 destructor: ir_public_trivial_special(),
876 is_trivial_abi: true,
877 })],
878 };
879
Michael Forstercc5941a2021-10-07 07:12:24 +0000880 let rs_api = generate_rs_api(&ir)?;
881 assert!(
882 rs_api.contains("/// Doc Comment\n///\n/// * with bullet\n"),
883 "struct doc comment missing"
884 );
885 assert!(rs_api.contains("/// Field doc\n"), "field doc comment missing");
Michael Forster028800b2021-10-05 12:39:59 +0000886
887 Ok(())
888 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000889}