blob: 2d086e1e70e6ddff40de3705753486b5033f5fa6 [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;
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;
Michael Forstere9c881c2021-10-01 09:38:19 +000012use std::io::Write;
Marcel Hlopko42abfc82021-08-09 07:03:17 +000013use std::iter::Iterator;
14use std::panic::catch_unwind;
15use std::process;
Michael Forstere9c881c2021-10-01 09:38:19 +000016use std::process::{Command, Stdio};
Marcel Hlopko42abfc82021-08-09 07:03:17 +000017
Marcel Hlopko45fba972021-08-23 19:52:20 +000018/// FFI equivalent of `Bindings`.
19#[repr(C)]
20pub struct FfiBindings {
21 rs_api: FfiU8SliceBox,
22 rs_api_impl: FfiU8SliceBox,
23}
24
25/// Deserializes IR from `json` and generates bindings source code.
Marcel Hlopko42abfc82021-08-09 07:03:17 +000026///
27/// This function panics on error.
28///
29/// Ownership:
30/// * function doesn't take ownership of (in other words it borrows) the param `json`
31/// * function passes ownership of the returned value to the caller
32///
33/// Safety:
34/// * function expects that param `json` is a FfiU8Slice for a valid array of bytes with the
35/// given size.
36/// * function expects that param `json` doesn't change during the call.
37#[no_mangle]
Marcel Hlopko45fba972021-08-23 19:52:20 +000038pub unsafe extern "C" fn GenerateBindingsImpl(json: FfiU8Slice) -> FfiBindings {
Marcel Hlopko42abfc82021-08-09 07:03:17 +000039 catch_unwind(|| {
Marcel Hlopko45fba972021-08-23 19:52:20 +000040 // It is ok to abort here.
41 let Bindings { rs_api, rs_api_impl } = generate_bindings(json.as_slice()).unwrap();
42
43 FfiBindings {
44 rs_api: FfiU8SliceBox::from_boxed_slice(rs_api.into_bytes().into_boxed_slice()),
45 rs_api_impl: FfiU8SliceBox::from_boxed_slice(
46 rs_api_impl.into_bytes().into_boxed_slice(),
47 ),
48 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +000049 })
50 .unwrap_or_else(|_| process::abort())
51}
52
Marcel Hlopko45fba972021-08-23 19:52:20 +000053/// Source code for generated bindings.
54struct Bindings {
55 // Rust source code.
56 rs_api: String,
57 // C++ source code.
58 rs_api_impl: String,
Marcel Hlopko42abfc82021-08-09 07:03:17 +000059}
60
Marcel Hlopko45fba972021-08-23 19:52:20 +000061fn generate_bindings(json: &[u8]) -> Result<Bindings> {
62 let ir = deserialize_ir(json)?;
63 let rs_api = generate_rs_api(&ir)?;
64 let rs_api_impl = generate_rs_api_impl(&ir)?;
65 Ok(Bindings { rs_api, rs_api_impl })
66}
67
Marcel Hlopko3164eee2021-08-24 20:09:22 +000068/// If we know the original C++ function is codegenned and already compatible with `extern "C"`
69/// calling convention we skip creating/calling the C++ thunk since we can call the original C++
70/// directly.
71fn can_skip_cc_thunk(func: &Func) -> bool {
72 // Inline functions may not be codegenned in the C++ library since Clang doesn't know if Rust
73 // calls the function or not. Therefore in order to make inline functions callable from Rust we
74 // need to generate a C++ file that defines a thunk that delegates to the original inline
75 // function. When compiled, Clang will emit code for this thunk and Rust code will call the
76 // thunk when the user wants to call the original inline function.
77 //
78 // This is not great runtime-performance-wise in regular builds (inline function will not be
79 // inlined, there will always be a function call), but it is correct. ThinLTO builds will be
80 // able to see through the thunk and inline code across the language boundary. For non-ThinLTO
81 // builds we plan to implement <internal link> which removes the runtime performance overhead.
82 !func.is_inline
83}
84
Marcel Hlopkob4b28742021-09-15 12:45:20 +000085/// Generate Rust source code for a given Record.
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +000086fn generate_record(record: &Record) -> Result<TokenStream> {
Marcel Hlopkob4b28742021-09-15 12:45:20 +000087 let ident = make_ident(&record.identifier.identifier);
88 let field_idents =
89 record.fields.iter().map(|f| make_ident(&f.identifier.identifier)).collect_vec();
Devin Jeanpierre09c6f452021-09-29 07:34:24 +000090 let field_types = record
91 .fields
92 .iter()
93 .map(|f| format_rs_type(&f.type_.rs_type))
94 .collect::<Result<Vec<_>>>()?;
Googlerec589eb2021-09-17 07:45:39 +000095 let field_accesses = record
96 .fields
97 .iter()
98 .map(|f| {
99 if f.access == AccessSpecifier::Public {
100 quote! { pub }
101 } else {
102 quote! {}
103 }
104 })
105 .collect_vec();
Googlerec648ff2021-09-23 07:19:53 +0000106 let size = record.size;
107 let alignment = record.alignment;
Googleraaa0a532021-10-01 09:11:27 +0000108 let field_assertions =
109 record.fields.iter().zip(field_idents.iter()).map(|(field, field_ident)| {
110 let offset = field.offset;
111 quote! {
112 // The IR contains the offset in bits, while offset_of!()
113 // returns the offset in bytes, so we need to convert.
114 const_assert_eq!(offset_of!(#ident, #field_ident) * 8, #offset);
115 }
116 });
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000117 Ok(quote! {
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000118 #[repr(C)]
119 pub struct #ident {
Googlerec589eb2021-09-17 07:45:39 +0000120 #( #field_accesses #field_idents: #field_types, )*
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000121 }
Googlerec648ff2021-09-23 07:19:53 +0000122
123 const_assert_eq!(std::mem::size_of::<#ident>(), #size);
124 const_assert_eq!(std::mem::align_of::<#ident>(), #alignment);
Googleraaa0a532021-10-01 09:11:27 +0000125 #( #field_assertions )*
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000126 })
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000127}
128
Marcel Hlopko45fba972021-08-23 19:52:20 +0000129fn generate_rs_api(ir: &IR) -> Result<String> {
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000130 let mut thunks = vec![];
131 let mut api_funcs = vec![];
Marcel Hlopko45fba972021-08-23 19:52:20 +0000132 for func in &ir.functions {
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000133 let mangled_name = &func.mangled_name;
134 let ident = make_ident(&func.identifier.identifier);
135 let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
Marcel Hlopko7d739792021-08-12 07:52:47 +0000136 // TODO(hlopko): do not emit `-> ()` when return type is void, it's implicit.
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000137 let return_type_name = format_rs_type(&func.return_type.rs_type)?;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000138
139 let param_idents =
140 func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
141
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000142 let param_types = func
143 .params
144 .iter()
145 .map(|p| format_rs_type(&p.type_.rs_type))
146 .collect::<Result<Vec<_>>>()?;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000147
148 api_funcs.push(quote! {
149 #[inline(always)]
150 pub fn #ident( #( #param_idents: #param_types ),* ) -> #return_type_name {
151 unsafe { crate::detail::#thunk_ident( #( #param_idents ),* ) }
152 }
153 });
154
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000155 let thunk_attr = if can_skip_cc_thunk(&func) {
156 quote! {#[link_name = #mangled_name]}
157 } else {
158 quote! {}
159 };
160
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000161 thunks.push(quote! {
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000162 #thunk_attr
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000163 pub(crate) fn #thunk_ident( #( #param_idents: #param_types ),* ) -> #return_type_name ;
164 });
165 }
166
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000167 let records = ir.records.iter().map(generate_record).collect::<Result<Vec<_>>>()?;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000168
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000169 let mod_detail = if thunks.is_empty() {
170 quote! {}
171 } else {
172 quote! {
173 mod detail {
174 extern "C" {
175 #( #thunks )*
176 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000177 }
178 }
179 };
180
Googlerec648ff2021-09-23 07:19:53 +0000181 let imports = if records.is_empty() {
182 quote! {}
183 } else {
Googleraaa0a532021-10-01 09:11:27 +0000184 // TODO(mboehme): For the time being, we're using unstable features to
185 // be able to use offset_of!() in static assertions. This is fine for a
186 // prototype, but longer-term we want to either get those features
187 // stabilized or find an alternative. For more details, see
188 // b/200120034#comment15
Googlerec648ff2021-09-23 07:19:53 +0000189 quote! {
Googleraaa0a532021-10-01 09:11:27 +0000190 #![feature(const_ptr_offset_from, const_maybe_uninit_as_ptr, const_raw_ptr_deref)]
191 use memoffset_unstable_const::offset_of;
192 use static_assertions::const_assert_eq;
Googlerec648ff2021-09-23 07:19:53 +0000193 }
194 };
195
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000196 let result = quote! {
Googlerec648ff2021-09-23 07:19:53 +0000197 #imports
198
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000199 #( #api_funcs )*
200 #( #records )*
201
202 #mod_detail
203 };
204
Michael Forstere9c881c2021-10-01 09:38:19 +0000205 Ok(rustfmt(result.to_string())?)
206}
207
208fn rustfmt(input: String) -> Result<String> {
209 // TODO(forster): This should use rustfmt as a library as soon as b/200503084 is fixed.
210 // TODO(forster): Add way to specify a configuration file.
211
212 let rustfmt = "third_party/unsupported_toolchains/rust/toolchains/nightly/bin/rustfmt";
213
214 let mut child = Command::new(rustfmt)
215 .stdin(Stdio::piped())
216 .stdout(Stdio::piped())
217 .spawn()
218 .expect(&format!("Failed to spawn rustfmt at '{}'", rustfmt));
219
220 let mut stdin = child.stdin.take().expect("Failed to open rustfmt stdin");
221 std::thread::spawn(move || {
222 stdin.write_all(input.as_bytes()).expect("Failed to write to rustfmt stdin");
223 });
224 let output = child.wait_with_output().expect("Failed to read rustfmt stdout");
225
226 Ok(String::from_utf8_lossy(&output.stdout).to_string())
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000227}
228
229fn make_ident(ident: &str) -> Ident {
230 format_ident!("{}", ident)
231}
232
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000233fn format_rs_type(ty: &ir::RsType) -> Result<TokenStream> {
234 let ptr_fragment = match ty.name.as_str() {
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000235 "*mut" => Some(quote! {*mut}),
236 "*const" => Some(quote! {*const}),
237 _ => None,
238 };
239 match ptr_fragment {
240 Some(ptr_fragment) => {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000241 if ty.type_params.len() != 1 {
242 return Err(anyhow!(
243 "Invalid pointer type (need exactly 1 type parameter): {:?}",
244 ty
245 ));
246 }
247 let nested_type = format_rs_type(&ty.type_params[0])?;
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000248 Ok(quote! {#ptr_fragment #nested_type})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000249 }
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000250 None => {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000251 if ty.type_params.len() > 0 {
252 return Err(anyhow!("Type not yet supported: {:?}", ty));
253 }
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000254 let ident = make_ident(&ty.name);
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000255 Ok(quote! {#ident})
256 }
257 }
258}
259
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000260fn format_cc_type(ty: &ir::CcType) -> Result<TokenStream> {
261 let const_fragment = if ty.is_const {
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000262 quote! {const}
263 } else {
264 quote! {}
265 };
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000266 match ty.name.as_str() {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000267 "*" => {
268 if ty.type_params.len() != 1 {
269 return Err(anyhow!(
270 "Invalid pointer type (need exactly 1 type parameter): {:?}",
271 ty
272 ));
273 }
274 assert_eq!(ty.type_params.len(), 1);
275 let nested_type = format_cc_type(&ty.type_params[0])?;
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000276 Ok(quote! {#nested_type * #const_fragment})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000277 }
278 ident => {
279 if ty.type_params.len() > 0 {
280 return Err(anyhow!("Type not yet supported: {:?}", ty));
281 }
282 let ident = make_ident(ident);
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000283 Ok(quote! {#ident #const_fragment})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000284 }
285 }
286}
287
Googler5ea88642021-09-29 08:05:59 +0000288fn cc_struct_layout_assertion(record: &Record) -> TokenStream {
289 let record_ident = make_ident(&record.identifier.identifier);
290 let size = Literal::usize_unsuffixed(record.size);
291 let alignment = Literal::usize_unsuffixed(record.alignment);
292 let field_assertions = record.fields.iter().map(|field| {
293 let field_ident = make_ident(&field.identifier.identifier);
294 let offset = Literal::usize_unsuffixed(field.offset);
295 // The IR contains the offset in bits, while C++'s offsetof()
296 // returns the offset in bytes, so we need to convert.
297 quote! {
298 static_assert(offsetof(#record_ident, #field_ident) * 8 == #offset);
299 }
300 });
301 quote! {
302 static_assert(sizeof(#record_ident) == #size);
303 static_assert(alignof(#record_ident) == #alignment);
304 #( #field_assertions )*
305 }
306}
307
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000308fn generate_rs_api_impl(ir: &IR) -> Result<String> {
309 // This function uses quote! to generate C++ source code out of convenience. This is a bold idea
310 // so we have to continously evaluate if it still makes sense or the cost of working around
311 // differences in Rust and C++ tokens is greather than the value added.
312 //
313 // See rs_bindings_from_cc/token_stream_printer.rs for a list
314 // of supported placeholders.
315 let mut thunks = vec![];
316 for func in &ir.functions {
317 if can_skip_cc_thunk(&func) {
318 continue;
319 }
320
321 let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
322 let ident = make_ident(&func.identifier.identifier);
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000323 let return_type_name = format_cc_type(&func.return_type.cc_type)?;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000324
325 let param_idents =
326 func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
327
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000328 let param_types = func
329 .params
330 .iter()
331 .map(|p| format_cc_type(&p.type_.cc_type))
332 .collect::<Result<Vec<_>>>()?;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000333
334 thunks.push(quote! {
335 extern "C" #return_type_name #thunk_ident( #( #param_types #param_idents ),* ) {
336 return #ident( #( #param_idents ),* );
337 }
338 });
339 }
340
Googler5ea88642021-09-29 08:05:59 +0000341 let layout_assertions = ir.records.iter().map(cc_struct_layout_assertion);
342
343 let standard_headers = if ir.records.is_empty() { vec![] } else { vec![make_ident("cstddef")] };
344
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000345 // In order to generate C++ thunk in all the cases Clang needs to be able to access declarations
346 // from public headers of the C++ library.
347 let includes = ir.used_headers.iter().map(|i| &i.name);
348
349 let result = quote! {
Googler5ea88642021-09-29 08:05:59 +0000350 #( __HASH_TOKEN__ include <#standard_headers> __NEWLINE__)*
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000351 #( __HASH_TOKEN__ include #includes __NEWLINE__)*
352
353 #( #thunks )*
Googler5ea88642021-09-29 08:05:59 +0000354
355 #( #layout_assertions )*
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000356 };
357
358 token_stream_printer::cc_tokens_to_string(result)
359}
360
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000361#[cfg(test)]
362mod tests {
Michael Forstere9c881c2021-10-01 09:38:19 +0000363 use crate::rustfmt;
364
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000365 use super::Result;
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000366 use super::{generate_rs_api, generate_rs_api_impl};
367 use ir::*;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000368 use quote::quote;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000369 use token_stream_printer::cc_tokens_to_string;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000370
371 #[test]
Marcel Hlopko45fba972021-08-23 19:52:20 +0000372 fn test_simple_function() -> Result<()> {
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000373 let ir = IR {
Marcel Hlopkof1123c82021-08-19 11:38:52 +0000374 used_headers: vec![],
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000375 records: vec![],
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000376 functions: vec![Func {
377 identifier: Identifier { identifier: "add".to_string() },
378 mangled_name: "_Z3Addii".to_string(),
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000379 return_type: MappedType {
380 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
381 cc_type: CcType {
382 name: "int".to_string(),
383 is_const: false,
384 type_params: vec![],
385 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000386 },
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000387 params: vec![
388 FuncParam {
389 identifier: Identifier { identifier: "a".to_string() },
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000390 type_: MappedType {
391 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
392 cc_type: CcType {
393 name: "int".to_string(),
394 is_const: false,
395 type_params: vec![],
396 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000397 },
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000398 },
399 FuncParam {
400 identifier: Identifier { identifier: "b".to_string() },
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000401 type_: MappedType {
402 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
403 cc_type: CcType {
404 name: "int".to_string(),
405 is_const: false,
406 type_params: vec![],
407 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000408 },
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000409 },
410 ],
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000411 is_inline: false,
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000412 }],
413 };
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000414 assert_eq!(
Marcel Hlopko45fba972021-08-23 19:52:20 +0000415 generate_rs_api(&ir)?,
Michael Forstere9c881c2021-10-01 09:38:19 +0000416 rustfmt(
417 quote! {
418 #[inline(always)]
419 pub fn add(a: i32, b: i32) -> i32 {
420 unsafe { crate::detail::__rust_thunk__add(a, b) }
421 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000422
Michael Forstere9c881c2021-10-01 09:38:19 +0000423 mod detail {
424 extern "C" {
425 #[link_name = "_Z3Addii"]
426 pub(crate) fn __rust_thunk__add(a: i32, b: i32) -> i32;
427 } // extern
428 } // mod detail
429 }
430 .to_string()
431 )?
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000432 );
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000433 assert_eq!(generate_rs_api_impl(&ir)?, "");
434 Ok(())
435 }
436
437 #[test]
438 fn test_inline_function() -> Result<()> {
439 let ir = IR {
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000440 records: vec![],
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000441 used_headers: vec![
442 HeaderName { name: "foo/bar.h".to_string() },
443 HeaderName { name: "foo/baz.h".to_string() },
444 ],
445 functions: vec![Func {
446 identifier: Identifier { identifier: "add".to_string() },
447 mangled_name: "_Z3Addii".to_string(),
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000448 return_type: MappedType {
449 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
450 cc_type: CcType {
451 name: "int".to_string(),
452 is_const: false,
453 type_params: vec![],
454 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000455 },
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000456 params: vec![
457 FuncParam {
458 identifier: Identifier { identifier: "a".to_string() },
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000459 type_: MappedType {
460 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
461 cc_type: CcType {
462 name: "int".to_string(),
463 is_const: false,
464 type_params: vec![],
465 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000466 },
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000467 },
468 FuncParam {
469 identifier: Identifier { identifier: "b".to_string() },
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000470 type_: MappedType {
471 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
472 cc_type: CcType {
473 name: "int".to_string(),
474 is_const: false,
475 type_params: vec![],
476 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000477 },
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000478 },
479 ],
480 is_inline: true,
481 }],
482 };
483
484 assert_eq!(
485 generate_rs_api(&ir)?,
Michael Forstere9c881c2021-10-01 09:38:19 +0000486 rustfmt(
487 quote! {#[inline(always)]
488 pub fn add(a: i32, b: i32) -> i32 {
489 unsafe { crate::detail::__rust_thunk__add(a, b) }
490 }
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000491
Michael Forstere9c881c2021-10-01 09:38:19 +0000492 mod detail {
493 extern "C" {
494 pub(crate) fn __rust_thunk__add(a: i32, b: i32) -> i32;
495 } // extern
496 } // mod detail
497 }
498 .to_string()
499 )?
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000500 );
501
502 assert_eq!(
503 generate_rs_api_impl(&ir)?,
504 cc_tokens_to_string(quote! {
505 __HASH_TOKEN__ include "foo/bar.h" __NEWLINE__
506 __HASH_TOKEN__ include "foo/baz.h" __NEWLINE__
507
508 extern "C" int __rust_thunk__add(int a, int b) {
509 return add(a, b);
510 }
511 })?
512 );
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000513 Ok(())
514 }
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000515
516 #[test]
517 fn test_simple_struct() -> Result<()> {
518 let ir = IR {
519 used_headers: vec![],
520 records: vec![Record {
521 identifier: Identifier { identifier: "SomeStruct".to_string() },
522 fields: vec![
523 Field {
Googlerec589eb2021-09-17 07:45:39 +0000524 identifier: Identifier { identifier: "public_int".to_string() },
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000525 type_: MappedType {
526 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
527 cc_type: CcType {
528 name: "int".to_string(),
529 is_const: false,
530 type_params: vec![],
531 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000532 },
Googler2294c702021-09-17 07:32:07 +0000533 access: AccessSpecifier::Public,
Googlere6e5f302021-09-17 13:56:40 +0000534 offset: 0,
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000535 },
536 Field {
Googlerec589eb2021-09-17 07:45:39 +0000537 identifier: Identifier { identifier: "protected_int".to_string() },
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000538 type_: MappedType {
539 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
540 cc_type: CcType {
541 name: "int".to_string(),
542 is_const: false,
543 type_params: vec![],
544 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000545 },
Googlerec589eb2021-09-17 07:45:39 +0000546 access: AccessSpecifier::Protected,
Googlere6e5f302021-09-17 13:56:40 +0000547 offset: 32,
Googlerec589eb2021-09-17 07:45:39 +0000548 },
549 Field {
550 identifier: Identifier { identifier: "private_int".to_string() },
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000551 type_: MappedType {
552 rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
553 cc_type: CcType {
554 name: "int".to_string(),
555 is_const: false,
556 type_params: vec![],
557 },
Googlerec589eb2021-09-17 07:45:39 +0000558 },
559 access: AccessSpecifier::Private,
Googlere6e5f302021-09-17 13:56:40 +0000560 offset: 64,
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000561 },
562 ],
Googlere6e5f302021-09-17 13:56:40 +0000563 size: 12,
564 alignment: 4,
Devin Jeanpierreb2cd0212021-10-01 07:16:23 +0000565 is_trivial_abi: true,
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000566 }],
567 functions: vec![],
568 };
569 assert_eq!(
570 generate_rs_api(&ir)?,
Michael Forstere9c881c2021-10-01 09:38:19 +0000571 rustfmt(
572 quote! {
573 #![feature(const_ptr_offset_from, const_maybe_uninit_as_ptr, const_raw_ptr_deref)]
574 use memoffset_unstable_const::offset_of;
575 use static_assertions::const_assert_eq;
Googlerec648ff2021-09-23 07:19:53 +0000576
Michael Forstere9c881c2021-10-01 09:38:19 +0000577 #[repr(C)]
578 pub struct SomeStruct {
579 pub public_int: i32,
580 protected_int: i32,
581 private_int: i32,
582 }
583
584 const_assert_eq!(std::mem::size_of::<SomeStruct>(), 12usize);
585 const_assert_eq!(std::mem::align_of::<SomeStruct>(), 4usize);
586 const_assert_eq!(offset_of!(SomeStruct, public_int) * 8, 0usize);
587 const_assert_eq!(offset_of!(SomeStruct, protected_int) * 8, 32usize);
588 const_assert_eq!(offset_of!(SomeStruct, private_int) * 8, 64usize);
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000589 }
Michael Forstere9c881c2021-10-01 09:38:19 +0000590 .to_string()
591 )?
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000592 );
Googler5ea88642021-09-29 08:05:59 +0000593 assert_eq!(
594 generate_rs_api_impl(&ir)?,
595 cc_tokens_to_string(quote! {
596 __HASH_TOKEN__ include <cstddef> __NEWLINE__
597 static_assert(sizeof(SomeStruct) == 12);
598 static_assert(alignof(SomeStruct) == 4);
599 static_assert(offsetof(SomeStruct, public_int) * 8 == 0);
600 static_assert(offsetof(SomeStruct, protected_int) * 8 == 32);
601 static_assert(offsetof(SomeStruct, private_int) * 8 == 64);
602 })?
603 );
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000604 Ok(())
605 }
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000606
607 #[test]
608 fn test_ptr_func() -> Result<()> {
609 let ir = IR {
610 used_headers: vec![],
611 records: vec![],
612 functions: vec![Func {
613 identifier: Identifier { identifier: "Deref".to_string() },
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000614 mangled_name: "_Z5DerefPKPi".to_string(),
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000615 return_type: MappedType {
616 rs_type: RsType {
617 name: "*mut".to_string(),
618 type_params: vec![RsType { name: "i32".to_string(), type_params: vec![] }],
619 },
620 cc_type: CcType {
621 name: "*".to_string(),
622 is_const: false,
623 type_params: vec![CcType {
624 name: "int".to_string(),
625 is_const: false,
626 type_params: vec![],
627 }],
628 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000629 },
630 params: vec![FuncParam {
631 identifier: Identifier { identifier: "p".to_string() },
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000632 type_: MappedType {
633 rs_type: RsType {
634 name: "*const".to_string(),
635 type_params: vec![RsType {
636 name: "*mut".to_string(),
637 type_params: vec![RsType {
638 name: "i32".to_string(),
639 type_params: vec![],
640 }],
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000641 }],
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000642 },
643 cc_type: CcType {
644 name: "*".to_string(),
645 is_const: false,
646 type_params: vec![CcType {
647 name: "*".to_string(),
648 is_const: true,
649 type_params: vec![CcType {
650 name: "int".to_string(),
651 is_const: false,
652 type_params: vec![],
653 }],
654 }],
655 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000656 },
657 }],
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000658 is_inline: true,
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000659 }],
660 };
661 assert_eq!(
662 generate_rs_api(&ir)?,
Michael Forstere9c881c2021-10-01 09:38:19 +0000663 rustfmt(
664 quote! {
665 #[inline(always)]
666 pub fn Deref(p: *const *mut i32) -> *mut i32 {
667 unsafe { crate::detail::__rust_thunk__Deref(p) }
668 }
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000669
Michael Forstere9c881c2021-10-01 09:38:19 +0000670 mod detail {
671 extern "C" {
672 pub(crate) fn __rust_thunk__Deref(p: *const *mut i32) -> *mut i32;
673 } // extern
674 } // mod detail
675 }
676 .to_string()
677 )?
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000678 );
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000679
680 assert_eq!(
681 generate_rs_api_impl(&ir)?,
682 cc_tokens_to_string(quote! {
683 extern "C" int* __rust_thunk__Deref(int* const * p) {
684 return Deref(p);
685 }
686 })?
687 );
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000688 Ok(())
689 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000690}