blob: 2b95612a16a175b015d78ada8a624e5adc74d1eb [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
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +00005use anyhow::{anyhow, Result};
Marcel Hlopko884ae7f2021-08-18 13:58:22 +00006use ffi_types::*;
Marcel Hlopko42abfc82021-08-09 07:03:17 +00007use ir::*;
8use itertools::Itertools;
Marcel Hlopkob4b28742021-09-15 12:45:20 +00009use proc_macro2::TokenStream;
Marcel Hlopko42abfc82021-08-09 07:03:17 +000010use quote::format_ident;
11use quote::quote;
Marcel Hlopko42abfc82021-08-09 07:03:17 +000012use std::iter::Iterator;
13use std::panic::catch_unwind;
14use std::process;
Marcel Hlopko42abfc82021-08-09 07:03:17 +000015use syn::*;
16
Marcel Hlopko45fba972021-08-23 19:52:20 +000017/// FFI equivalent of `Bindings`.
18#[repr(C)]
19pub struct FfiBindings {
20 rs_api: FfiU8SliceBox,
21 rs_api_impl: FfiU8SliceBox,
22}
23
24/// Deserializes IR from `json` and generates bindings source code.
Marcel Hlopko42abfc82021-08-09 07:03:17 +000025///
26/// This function panics on error.
27///
28/// Ownership:
29/// * function doesn't take ownership of (in other words it borrows) the param `json`
30/// * function passes ownership of the returned value to the caller
31///
32/// Safety:
33/// * function expects that param `json` is a FfiU8Slice for a valid array of bytes with the
34/// given size.
35/// * function expects that param `json` doesn't change during the call.
36#[no_mangle]
Marcel Hlopko45fba972021-08-23 19:52:20 +000037pub unsafe extern "C" fn GenerateBindingsImpl(json: FfiU8Slice) -> FfiBindings {
Marcel Hlopko42abfc82021-08-09 07:03:17 +000038 catch_unwind(|| {
Marcel Hlopko45fba972021-08-23 19:52:20 +000039 // It is ok to abort here.
40 let Bindings { rs_api, rs_api_impl } = generate_bindings(json.as_slice()).unwrap();
41
42 FfiBindings {
43 rs_api: FfiU8SliceBox::from_boxed_slice(rs_api.into_bytes().into_boxed_slice()),
44 rs_api_impl: FfiU8SliceBox::from_boxed_slice(
45 rs_api_impl.into_bytes().into_boxed_slice(),
46 ),
47 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +000048 })
49 .unwrap_or_else(|_| process::abort())
50}
51
Marcel Hlopko45fba972021-08-23 19:52:20 +000052/// Source code for generated bindings.
53struct Bindings {
54 // Rust source code.
55 rs_api: String,
56 // C++ source code.
57 rs_api_impl: String,
Marcel Hlopko42abfc82021-08-09 07:03:17 +000058}
59
Marcel Hlopko45fba972021-08-23 19:52:20 +000060fn generate_bindings(json: &[u8]) -> Result<Bindings> {
61 let ir = deserialize_ir(json)?;
62 let rs_api = generate_rs_api(&ir)?;
63 let rs_api_impl = generate_rs_api_impl(&ir)?;
64 Ok(Bindings { rs_api, rs_api_impl })
65}
66
Marcel Hlopko3164eee2021-08-24 20:09:22 +000067/// If we know the original C++ function is codegenned and already compatible with `extern "C"`
68/// calling convention we skip creating/calling the C++ thunk since we can call the original C++
69/// directly.
70fn can_skip_cc_thunk(func: &Func) -> bool {
71 // Inline functions may not be codegenned in the C++ library since Clang doesn't know if Rust
72 // calls the function or not. Therefore in order to make inline functions callable from Rust we
73 // need to generate a C++ file that defines a thunk that delegates to the original inline
74 // function. When compiled, Clang will emit code for this thunk and Rust code will call the
75 // thunk when the user wants to call the original inline function.
76 //
77 // This is not great runtime-performance-wise in regular builds (inline function will not be
78 // inlined, there will always be a function call), but it is correct. ThinLTO builds will be
79 // able to see through the thunk and inline code across the language boundary. For non-ThinLTO
80 // builds we plan to implement <internal link> which removes the runtime performance overhead.
81 !func.is_inline
82}
83
Marcel Hlopkob4b28742021-09-15 12:45:20 +000084/// Generate Rust source code for a given Record.
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +000085fn generate_record(record: &Record) -> Result<TokenStream> {
Marcel Hlopkob4b28742021-09-15 12:45:20 +000086 let ident = make_ident(&record.identifier.identifier);
87 let field_idents =
88 record.fields.iter().map(|f| make_ident(&f.identifier.identifier)).collect_vec();
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +000089 let field_types =
90 record.fields.iter().map(|f| format_rs_type(&f.type_)).collect::<Result<Vec<_>>>()?;
91 Ok(quote! {
Marcel Hlopkob4b28742021-09-15 12:45:20 +000092 #[repr(C)]
93 pub struct #ident {
94 #( pub #field_idents: #field_types, )*
95 }
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +000096 })
Marcel Hlopkob4b28742021-09-15 12:45:20 +000097}
98
Marcel Hlopko45fba972021-08-23 19:52:20 +000099fn generate_rs_api(ir: &IR) -> Result<String> {
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000100 let mut thunks = vec![];
101 let mut api_funcs = vec![];
Marcel Hlopko45fba972021-08-23 19:52:20 +0000102 for func in &ir.functions {
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000103 let mangled_name = &func.mangled_name;
104 let ident = make_ident(&func.identifier.identifier);
105 let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
Marcel Hlopko7d739792021-08-12 07:52:47 +0000106 // TODO(hlopko): do not emit `-> ()` when return type is void, it's implicit.
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000107 let return_type_name = format_rs_type(&func.return_type)?;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000108
109 let param_idents =
110 func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
111
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000112 let param_types =
113 func.params.iter().map(|p| format_rs_type(&p.type_)).collect::<Result<Vec<_>>>()?;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000114
115 api_funcs.push(quote! {
116 #[inline(always)]
117 pub fn #ident( #( #param_idents: #param_types ),* ) -> #return_type_name {
118 unsafe { crate::detail::#thunk_ident( #( #param_idents ),* ) }
119 }
120 });
121
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000122 let thunk_attr = if can_skip_cc_thunk(&func) {
123 quote! {#[link_name = #mangled_name]}
124 } else {
125 quote! {}
126 };
127
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000128 thunks.push(quote! {
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000129 #thunk_attr
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000130 pub(crate) fn #thunk_ident( #( #param_idents: #param_types ),* ) -> #return_type_name ;
131 });
132 }
133
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000134 let records = ir.records.iter().map(generate_record).collect::<Result<Vec<_>>>()?;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000135
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000136 let mod_detail = if thunks.is_empty() {
137 quote! {}
138 } else {
139 quote! {
140 mod detail {
141 extern "C" {
142 #( #thunks )*
143 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000144 }
145 }
146 };
147
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000148 let result = quote! {
149 #( #api_funcs )*
150 #( #records )*
151
152 #mod_detail
153 };
154
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000155 Ok(result.to_string())
156}
157
158fn make_ident(ident: &str) -> Ident {
159 format_ident!("{}", ident)
160}
161
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000162fn format_rs_type(ty: &ir::IRType) -> Result<TokenStream> {
163 match ty.rs_name.as_str() {
164 "*mut" => {
165 if ty.type_params.len() != 1 {
166 return Err(anyhow!(
167 "Invalid pointer type (need exactly 1 type parameter): {:?}",
168 ty
169 ));
170 }
171 let nested_type = format_rs_type(&ty.type_params[0])?;
172 Ok(quote! {*mut #nested_type})
173 }
174 ident => {
175 if ty.type_params.len() > 0 {
176 return Err(anyhow!("Type not yet supported: {:?}", ty));
177 }
178 let ident = make_ident(ident);
179 Ok(quote! {#ident})
180 }
181 }
182}
183
184fn format_cc_type(ty: &ir::IRType) -> Result<TokenStream> {
185 match ty.cc_name.as_str() {
186 "*" => {
187 if ty.type_params.len() != 1 {
188 return Err(anyhow!(
189 "Invalid pointer type (need exactly 1 type parameter): {:?}",
190 ty
191 ));
192 }
193 assert_eq!(ty.type_params.len(), 1);
194 let nested_type = format_cc_type(&ty.type_params[0])?;
195 Ok(quote! {#nested_type *})
196 }
197 ident => {
198 if ty.type_params.len() > 0 {
199 return Err(anyhow!("Type not yet supported: {:?}", ty));
200 }
201 let ident = make_ident(ident);
202 Ok(quote! {#ident})
203 }
204 }
205}
206
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000207fn generate_rs_api_impl(ir: &IR) -> Result<String> {
208 // This function uses quote! to generate C++ source code out of convenience. This is a bold idea
209 // so we have to continously evaluate if it still makes sense or the cost of working around
210 // differences in Rust and C++ tokens is greather than the value added.
211 //
212 // See rs_bindings_from_cc/token_stream_printer.rs for a list
213 // of supported placeholders.
214 let mut thunks = vec![];
215 for func in &ir.functions {
216 if can_skip_cc_thunk(&func) {
217 continue;
218 }
219
220 let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
221 let ident = make_ident(&func.identifier.identifier);
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000222 let return_type_name = format_cc_type(&func.return_type)?;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000223
224 let param_idents =
225 func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
226
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000227 let param_types =
228 func.params.iter().map(|p| format_cc_type(&p.type_)).collect::<Result<Vec<_>>>()?;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000229
230 thunks.push(quote! {
231 extern "C" #return_type_name #thunk_ident( #( #param_types #param_idents ),* ) {
232 return #ident( #( #param_idents ),* );
233 }
234 });
235 }
236
237 // In order to generate C++ thunk in all the cases Clang needs to be able to access declarations
238 // from public headers of the C++ library.
239 let includes = ir.used_headers.iter().map(|i| &i.name);
240
241 let result = quote! {
242 #( __HASH_TOKEN__ include #includes __NEWLINE__)*
243
244 #( #thunks )*
245 };
246
247 token_stream_printer::cc_tokens_to_string(result)
248}
249
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000250#[cfg(test)]
251mod tests {
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000252 use super::Result;
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000253 use super::{generate_rs_api, generate_rs_api_impl};
254 use ir::*;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000255 use quote::quote;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000256 use token_stream_printer::cc_tokens_to_string;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000257
258 #[test]
Marcel Hlopko45fba972021-08-23 19:52:20 +0000259 fn test_simple_function() -> Result<()> {
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000260 let ir = IR {
Marcel Hlopkof1123c82021-08-19 11:38:52 +0000261 used_headers: vec![],
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000262 records: vec![],
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000263 functions: vec![Func {
264 identifier: Identifier { identifier: "add".to_string() },
265 mangled_name: "_Z3Addii".to_string(),
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000266 return_type: IRType {
267 rs_name: "i32".to_string(),
268 cc_name: "int".to_string(),
269 type_params: vec![],
270 },
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000271 params: vec![
272 FuncParam {
273 identifier: Identifier { identifier: "a".to_string() },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000274 type_: IRType {
275 rs_name: "i32".to_string(),
276 cc_name: "int".to_string(),
277 type_params: vec![],
278 },
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000279 },
280 FuncParam {
281 identifier: Identifier { identifier: "b".to_string() },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000282 type_: IRType {
283 rs_name: "i32".to_string(),
284 cc_name: "int".to_string(),
285 type_params: vec![],
286 },
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000287 },
288 ],
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000289 is_inline: false,
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000290 }],
291 };
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000292 assert_eq!(
Marcel Hlopko45fba972021-08-23 19:52:20 +0000293 generate_rs_api(&ir)?,
Marcel Hlopkof1123c82021-08-19 11:38:52 +0000294 quote! {
295 #[inline(always)]
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000296 pub fn add(a: i32, b: i32) -> i32 {
297 unsafe { crate::detail::__rust_thunk__add(a, b) }
298 }
299
300 mod detail {
301 extern "C" {
302 #[link_name = "_Z3Addii"]
303 pub(crate) fn __rust_thunk__add(a: i32, b: i32) -> i32;
304 } // extern
305 } // mod detail
306 }
307 .to_string()
308 );
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000309 assert_eq!(generate_rs_api_impl(&ir)?, "");
310 Ok(())
311 }
312
313 #[test]
314 fn test_inline_function() -> Result<()> {
315 let ir = IR {
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000316 records: vec![],
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000317 used_headers: vec![
318 HeaderName { name: "foo/bar.h".to_string() },
319 HeaderName { name: "foo/baz.h".to_string() },
320 ],
321 functions: vec![Func {
322 identifier: Identifier { identifier: "add".to_string() },
323 mangled_name: "_Z3Addii".to_string(),
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000324 return_type: IRType {
325 rs_name: "i32".to_string(),
326 cc_name: "int".to_string(),
327 type_params: vec![],
328 },
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000329 params: vec![
330 FuncParam {
331 identifier: Identifier { identifier: "a".to_string() },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000332 type_: IRType {
333 rs_name: "i32".to_string(),
334 cc_name: "int".to_string(),
335 type_params: vec![],
336 },
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000337 },
338 FuncParam {
339 identifier: Identifier { identifier: "b".to_string() },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000340 type_: IRType {
341 rs_name: "i32".to_string(),
342 cc_name: "int".to_string(),
343 type_params: vec![],
344 },
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000345 },
346 ],
347 is_inline: true,
348 }],
349 };
350
351 assert_eq!(
352 generate_rs_api(&ir)?,
353 quote! {#[inline(always)]
354 pub fn add(a: i32, b: i32) -> i32 {
355 unsafe { crate::detail::__rust_thunk__add(a, b) }
356 }
357
358 mod detail {
359 extern "C" {
360 pub(crate) fn __rust_thunk__add(a: i32, b: i32) -> i32;
361 } // extern
362 } // mod detail
363 }
364 .to_string()
365 );
366
367 assert_eq!(
368 generate_rs_api_impl(&ir)?,
369 cc_tokens_to_string(quote! {
370 __HASH_TOKEN__ include "foo/bar.h" __NEWLINE__
371 __HASH_TOKEN__ include "foo/baz.h" __NEWLINE__
372
373 extern "C" int __rust_thunk__add(int a, int b) {
374 return add(a, b);
375 }
376 })?
377 );
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000378 Ok(())
379 }
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000380
381 #[test]
382 fn test_simple_struct() -> Result<()> {
383 let ir = IR {
384 used_headers: vec![],
385 records: vec![Record {
386 identifier: Identifier { identifier: "SomeStruct".to_string() },
387 fields: vec![
388 Field {
389 identifier: Identifier { identifier: "first_field".to_string() },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000390 type_: IRType {
391 rs_name: "i32".to_string(),
392 cc_name: "int".to_string(),
393 type_params: vec![],
394 },
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000395 },
396 Field {
397 identifier: Identifier { identifier: "second_field".to_string() },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000398 type_: IRType {
399 rs_name: "i32".to_string(),
400 cc_name: "int".to_string(),
401 type_params: vec![],
402 },
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000403 },
404 ],
405 }],
406 functions: vec![],
407 };
408 assert_eq!(
409 generate_rs_api(&ir)?,
410 quote! {
411 #[repr(C)]
412 pub struct SomeStruct {
413 pub first_field: i32,
414 pub second_field: i32,
415 }
416 }
417 .to_string()
418 );
419 assert_eq!(generate_rs_api_impl(&ir)?, "");
420 Ok(())
421 }
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000422
423 #[test]
424 fn test_ptr_func() -> Result<()> {
425 let ir = IR {
426 used_headers: vec![],
427 records: vec![],
428 functions: vec![Func {
429 identifier: Identifier { identifier: "Deref".to_string() },
430 mangled_name: "_Z5DerefPPi".to_string(),
431 return_type: IRType {
432 rs_name: "*mut".to_string(),
433 cc_name: "*".to_string(),
434 type_params: vec![IRType {
435 rs_name: "i32".to_string(),
436 cc_name: "int".to_string(),
437 type_params: vec![],
438 }],
439 },
440 params: vec![FuncParam {
441 identifier: Identifier { identifier: "p".to_string() },
442 type_: IRType {
443 rs_name: "*mut".to_string(),
444 cc_name: "*".to_string(),
445 type_params: vec![IRType {
446 rs_name: "*mut".to_string(),
447 cc_name: "*".to_string(),
448 type_params: vec![IRType {
449 rs_name: "i32".to_string(),
450 cc_name: "int".to_string(),
451 type_params: vec![],
452 }],
453 }],
454 },
455 }],
456 is_inline: false,
457 }],
458 };
459 assert_eq!(
460 generate_rs_api(&ir)?,
461 quote! {
462 #[inline(always)]
463 pub fn Deref(p: *mut *mut i32) -> *mut i32 {
464 unsafe { crate::detail::__rust_thunk__Deref(p) }
465 }
466
467 mod detail {
468 extern "C" {
469 #[link_name = "_Z5DerefPPi"]
470 pub(crate) fn __rust_thunk__Deref(p: *mut *mut i32) -> *mut i32;
471 } // extern
472 } // mod detail
473 }
474 .to_string()
475 );
476 assert_eq!(generate_rs_api_impl(&ir)?, "");
477 Ok(())
478 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000479}