From 0736ef42b720b4ce0dd8af260bd2c8ef339093a2 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Fri, 4 Nov 2022 12:55:27 -0500 Subject: [PATCH] Generate extern wrappers for inlined functions If bindgen finds an inlined function and the `--generate-extern-functions` options is enabled, then: - It will generate two new source and header files with external functions that wrap the inlined functions. - Rerun `Bindings::generate` using the new header file to include these wrappers in the generated bindings. The following additional options were added: - `--extern-function-suffix=`: Adds to the name of each external wrapper function (`__extern` is used by default). - `--extern-functions-file-name=`: Uses as the file name for the header and source files (`extern` is used by default). - `--extern-function-directory=`: Creates the source and header files inside (`/tmp/bindgen` is used by default). The C code serialization is experimental and only supports a very limited set of C functions. Fixes #1090. --- bindgen-cli/options.rs | 31 +++ .../tests/generate-extern-functions.rs | 15 ++ .../tests/headers/generate-extern-functions.h | 11 + bindgen/codegen/mod.rs | 21 +- bindgen/ir/context.rs | 5 + bindgen/ir/function.rs | 16 +- bindgen/ir/mod.rs | 1 + bindgen/ir/serialize.rs | 234 ++++++++++++++++++ bindgen/lib.rs | 162 +++++++++++- 9 files changed, 487 insertions(+), 9 deletions(-) create mode 100644 bindgen-tests/tests/expectations/tests/generate-extern-functions.rs create mode 100644 bindgen-tests/tests/headers/generate-extern-functions.h create mode 100644 bindgen/ir/serialize.rs diff --git a/bindgen-cli/options.rs b/bindgen-cli/options.rs index f7fccc4ff4..317b098b10 100644 --- a/bindgen-cli/options.rs +++ b/bindgen-cli/options.rs @@ -565,6 +565,21 @@ where Arg::new("wrap-unsafe-ops") .long("wrap-unsafe-ops") .help("Wrap unsafe operations in unsafe blocks."), + Arg::new("generate-extern-functions") + .long("generate-extern-functions") + .help("Generate extern wrappers for inlined functions"), + Arg::new("extern-functions-file-name") + .long("extern-functions-file-name") + .help("Sets the name of the header and source code files that would be created if any extern wrapper functions must be generated due to the presence of inlined functions.") + .takes_value(true), + Arg::new("extern-functions-directory") + .long("extern-functions-directory") + .help("Sets the directory path where any extra files must be created due to the presence of inlined functions.") + .takes_value(true), + Arg::new("extern-function-suffix") + .long("extern-function-suffix") + .help("Sets the suffix added to the extern wrapper functions generated for inlined functions.") + .takes_value(true), Arg::new("V") .long("version") .help("Prints the version, and exits"), @@ -1092,5 +1107,21 @@ where builder = builder.wrap_unsafe_ops(true); } + if matches.is_present("generate-extern-functions") { + builder = builder.generate_extern_functions(true); + } + + if let Some(file_name) = matches.value_of("extern-functions-file-name") { + builder = builder.extern_functions_file_name(file_name); + } + + if let Some(directory) = matches.value_of("extern-functions-directory") { + builder = builder.extern_functions_directory(directory); + } + + if let Some(suffix) = matches.value_of("extern-function-suffix") { + builder = builder.extern_function_suffix(suffix); + } + Ok((builder, output, verbose)) } diff --git a/bindgen-tests/tests/expectations/tests/generate-extern-functions.rs b/bindgen-tests/tests/expectations/tests/generate-extern-functions.rs new file mode 100644 index 0000000000..c4b81f1a0b --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/generate-extern-functions.rs @@ -0,0 +1,15 @@ +#![allow( + dead_code, + non_snake_case, + non_camel_case_types, + non_upper_case_globals +)] + +extern "C" { + #[link_name = "\u{1}foo__extern"] + pub fn foo() -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}bar__extern"] + pub fn bar() -> ::std::os::raw::c_int; +} diff --git a/bindgen-tests/tests/headers/generate-extern-functions.h b/bindgen-tests/tests/headers/generate-extern-functions.h new file mode 100644 index 0000000000..dacd69fd2c --- /dev/null +++ b/bindgen-tests/tests/headers/generate-extern-functions.h @@ -0,0 +1,11 @@ +// bindgen-flags: --generate-extern-functions + +static inline int foo() { + return 0; +} +static int bar() { + return 1; +} +inline int baz() { + return 2; +} diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index b405063737..d3955f202b 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -3995,9 +3995,8 @@ impl CodeGenerator for Function { // We can't currently do anything with Internal functions so just // avoid generating anything for them. - match self.linkage() { - Linkage::Internal => return None, - Linkage::External => {} + if matches!(self.linkage(), Linkage::Internal) { + return None; } // Pure virtual methods have no actual symbol, so we can't generate @@ -4107,6 +4106,7 @@ impl CodeGenerator for Function { write!(&mut canonical_name, "{}", times_seen).unwrap(); } + let mut has_link_name_attr = false; let link_name = mangled_name.unwrap_or(name); if !is_dynamic_function && !utils::names_will_be_identical_after_mangling( @@ -4116,6 +4116,7 @@ impl CodeGenerator for Function { ) { attributes.push(attributes::link_name(link_name)); + has_link_name_attr = true; } // Unfortunately this can't piggyback on the `attributes` list because @@ -4126,6 +4127,20 @@ impl CodeGenerator for Function { quote! { #[link(wasm_import_module = #name)] } }); + if ctx.options().generate_extern_functions.is_second_run() { + if let Some(name) = canonical_name.strip_suffix( + ctx.options() + .extern_function_suffix + .as_deref() + .unwrap_or(crate::DEFAULT_EXTERN_FUNCTION_SUFFIX), + ) { + if !has_link_name_attr { + attributes.push(attributes::link_name(&canonical_name)); + } + canonical_name = name.to_owned(); + } + } + let ident = ctx.rust_ident(canonical_name); let tokens = quote! { #wasm_link_attribute diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs index 3cc30f1c0b..1bb7a8a300 100644 --- a/bindgen/ir/context.rs +++ b/bindgen/ir/context.rs @@ -16,6 +16,7 @@ use super::int::IntKind; use super::item::{IsOpaque, Item, ItemAncestors, ItemSet}; use super::item_kind::ItemKind; use super::module::{Module, ModuleKind}; +use super::serialize::CItem; use super::template::{TemplateInstantiation, TemplateParameters}; use super::traversal::{self, Edge, ItemTraversal}; use super::ty::{FloatKind, Type, TypeKind}; @@ -461,6 +462,9 @@ pub struct BindgenContext { /// The set of warnings raised during binding generation. warnings: Vec, + + /// C items that need to be serialized to an extra header file. + pub(crate) c_items: Vec, } /// A traversal of allowlisted items. @@ -576,6 +580,7 @@ If you encounter an error missing from this list, please file an issue or a PR!" has_type_param_in_array: None, has_float: None, warnings: Vec::new(), + c_items: Vec::new(), } } diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs index ecb3202ff1..024d8ca880 100644 --- a/bindgen/ir/function.rs +++ b/bindgen/ir/function.rs @@ -7,6 +7,7 @@ use super::item::Item; use super::traversal::{EdgeKind, Trace, Tracer}; use super::ty::TypeKind; use crate::clang::{self, Attribute}; +use crate::ir::serialize::CItem; use crate::parse::{ClangSubItemParser, ParseError, ParseResult}; use clang_sys::{self, CXCallingConv}; use proc_macro2; @@ -678,9 +679,12 @@ impl ClangSubItemParser for Function { .definition() .map_or(false, |x| x.is_inlined_function()) { - if !context.options().generate_inline_functions { + if !(context.options().generate_inline_functions || + context.options().generate_extern_functions.is_true()) + { return Err(ParseError::Continue); } + if cursor.is_deleted_function() { return Err(ParseError::Continue); } @@ -725,6 +729,16 @@ impl ClangSubItemParser for Function { let function = Self::new(name, mangled_name, sig, comment, kind, linkage); + + if matches!(linkage, Linkage::Internal) && + context.options().generate_extern_functions.is_true() + { + match CItem::from_function(&function, context) { + Ok(c_item) => context.c_items.push(c_item), + Err(err) => warn!("Serialization failed: {:?}", err), + } + } + Ok(ParseResult::New(function, Some(cursor))) } } diff --git a/bindgen/ir/mod.rs b/bindgen/ir/mod.rs index 8f6a2dac88..bb76e2ab4b 100644 --- a/bindgen/ir/mod.rs +++ b/bindgen/ir/mod.rs @@ -18,6 +18,7 @@ pub mod item_kind; pub mod layout; pub mod module; pub mod objc; +pub(crate) mod serialize; pub mod template; pub mod traversal; pub mod ty; diff --git a/bindgen/ir/serialize.rs b/bindgen/ir/serialize.rs new file mode 100644 index 0000000000..bc480acaa5 --- /dev/null +++ b/bindgen/ir/serialize.rs @@ -0,0 +1,234 @@ +use std::fmt::{self, Write}; + +use crate::callbacks::IntKind; +use crate::DEFAULT_EXTERN_FUNCTION_SUFFIX; + +use super::context::{BindgenContext, TypeId}; +use super::function::{Function, FunctionKind}; +use super::ty::{FloatKind, TypeKind}; + +#[derive(Debug)] +pub(crate) enum Error { + Serialize(String), + Fmt(fmt::Error), +} + +impl From for Error { + fn from(err: fmt::Error) -> Self { + Self::Fmt(err) + } +} + +impl From for Error { + fn from(err: String) -> Self { + Self::Serialize(err) + } +} + +#[derive(Debug)] +pub(crate) struct CItem { + header: String, + code: String, +} + +impl CItem { + pub(crate) fn from_function( + function: &Function, + ctx: &BindgenContext, + ) -> Result { + match function.kind() { + FunctionKind::Function => { + let signature_type = ctx.resolve_type(function.signature()); + match signature_type.kind() { + TypeKind::Function(signature) => { + let mut buf = String::new(); + + let mut count = 0; + + let name = function.name(); + let args = signature + .argument_types() + .iter() + .cloned() + .map(|(opt_name, type_id)| { + ( + opt_name.unwrap_or_else(|| { + let name = format!("arg_{}", count); + count += 1; + name + }), + type_id, + ) + }) + .collect::>(); + + serialize_type(signature.return_type(), ctx, &mut buf)?; + write!( + buf, + " {}{}(", + name, + ctx.options() + .extern_function_suffix + .as_deref() + .unwrap_or(DEFAULT_EXTERN_FUNCTION_SUFFIX) + )?; + serialize_sep( + ", ", + args.iter(), + ctx, + &mut buf, + |(name, type_id), ctx, buf| { + serialize_type(*type_id, ctx, buf)?; + write!(buf, " {}", name).map_err(Error::from) + }, + )?; + write!(buf, ")")?; + + let header = format!("{};", buf); + + write!(buf, " {{ return {}(", name)?; + serialize_sep( + ", ", + args.iter(), + ctx, + &mut buf, + |(name, _), _, buf| { + write!(buf, "{}", name).map_err(Error::from) + }, + )?; + write!(buf, "); }}")?; + + Ok(Self { header, code: buf }) + } + _ => unreachable!(), + } + } + function_kind => Err(Error::Serialize(format!( + "Cannot serialize function kind {:?}", + function_kind + ))), + } + } + + pub(crate) fn header(&self) -> &str { + self.header.as_ref() + } + + pub(crate) fn code(&self) -> &str { + self.code.as_ref() + } +} + +fn serialize_sep< + W: fmt::Write, + F: Fn(I::Item, &BindgenContext, &mut W) -> Result<(), Error>, + I: Iterator, +>( + sep: &'static str, + mut iter: I, + ctx: &BindgenContext, + buf: &mut W, + f: F, +) -> Result<(), Error> { + if let Some(item) = iter.next() { + f(item, ctx, buf)?; + + for item in iter { + write!(buf, "{}", sep)?; + f(item, ctx, buf)?; + } + } + + Ok(()) +} + +fn serialize_type( + type_id: TypeId, + ctx: &BindgenContext, + buf: &mut W, +) -> Result<(), Error> { + match ctx.resolve_type(type_id).kind() { + TypeKind::Void => write!(buf, "void")?, + TypeKind::NullPtr => write!(buf, "nullptr_t")?, + TypeKind::Int(int_kind) => match int_kind { + IntKind::Bool => write!(buf, "bool")?, + IntKind::SChar => write!(buf, "signed char")?, + IntKind::UChar => write!(buf, "unsigned char")?, + IntKind::WChar => write!(buf, "wchar_t")?, + IntKind::Short => write!(buf, "short")?, + IntKind::UShort => write!(buf, "unsigned short")?, + IntKind::Int => write!(buf, "int")?, + IntKind::UInt => write!(buf, "unsigned int")?, + IntKind::Long => write!(buf, "long")?, + IntKind::ULong => write!(buf, "unsigned long")?, + IntKind::LongLong => write!(buf, "long long")?, + IntKind::ULongLong => write!(buf, "unsigned long long")?, + int_kind => { + return Err(Error::Serialize(format!( + "Cannot serialize integer kind {:?}", + int_kind + ))) + } + }, + TypeKind::Float(float_kind) => match float_kind { + FloatKind::Float => write!(buf, "float")?, + FloatKind::Double => write!(buf, "double")?, + FloatKind::LongDouble => write!(buf, "long double")?, + FloatKind::Float128 => write!(buf, "__float128")?, + }, + TypeKind::Complex(float_kind) => match float_kind { + FloatKind::Float => write!(buf, "float complex")?, + FloatKind::Double => write!(buf, "double complex")?, + FloatKind::LongDouble => write!(buf, "long double complex")?, + FloatKind::Float128 => write!(buf, "__complex128")?, + }, + TypeKind::Alias(type_id) => serialize_type(*type_id, ctx, buf)?, + TypeKind::TemplateAlias(type_id, params) => { + serialize_type(*type_id, ctx, buf)?; + write!(buf, "<")?; + serialize_sep( + ", ", + params.iter().copied(), + ctx, + buf, + serialize_type, + )?; + write!(buf, ">")? + } + TypeKind::Array(type_id, length) => { + serialize_type(*type_id, ctx, buf)?; + write!(buf, " [{}]", length)? + } + TypeKind::Function(signature) => { + serialize_type(signature.return_type(), ctx, buf)?; + write!(buf, " (")?; + serialize_sep( + ", ", + signature.argument_types().iter(), + ctx, + buf, + |(name, type_id), ctx, buf| { + serialize_type(*type_id, ctx, buf)?; + + if let Some(name) = name { + write!(buf, "{}", name)?; + } + + Ok(()) + }, + )?; + write!(buf, ")")? + } + TypeKind::ResolvedTypeRef(type_id) => { + serialize_type(*type_id, ctx, buf)? + } + ty => { + return Err(Error::Serialize(format!( + "Cannot serialize type kind {:?}", + ty + ))) + } + }; + + Ok(()) +} diff --git a/bindgen/lib.rs b/bindgen/lib.rs index 3068d14ec8..9bf57cc32c 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -108,6 +108,7 @@ pub(crate) use std::collections::hash_map::Entry; /// Default prefix for the anon fields. pub const DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_"; +const DEFAULT_EXTERN_FUNCTION_SUFFIX: &str = "__extern"; fn file_is_cpp(name_file: &str) -> bool { name_file.ends_with(".hpp") || @@ -649,6 +650,25 @@ impl Builder { output_vector.push("--wrap-unsafe-ops".into()); } + if self.options.generate_extern_functions.is_true() { + output_vector.push("--generate-extern-functions".into()) + } + + if let Some(ref file_name) = self.options.extern_functions_file_name { + output_vector.push("--extern-functions-file-name".into()); + output_vector.push(file_name.clone()); + } + + if let Some(ref directory) = self.options.extern_functions_directory { + output_vector.push("--extern-functions-directory".into()); + output_vector.push(directory.display().to_string()); + } + + if let Some(ref suffix) = self.options.extern_function_suffix { + output_vector.push("--extern-function-suffix".into()); + output_vector.push(suffix.clone()); + } + // Add clang arguments output_vector.push("--".into()); @@ -1565,10 +1585,9 @@ impl Builder { match Bindings::generate(options, input_unsaved_files) { GenerateResult::Ok(bindings) => Ok(bindings), - GenerateResult::ShouldRestart { header } => self - .header(header) - .generate_inline_functions(false) - .generate(), + GenerateResult::ShouldRestart { header } => { + self.second_run(header).generate() + } GenerateResult::Err(err) => Err(err), } } @@ -1787,6 +1806,73 @@ impl Builder { self.options.wrap_unsafe_ops = doit; self } + + /// Whether to generate extern wrappers for inline functions. Defaults to false. + pub fn generate_extern_functions(mut self, doit: bool) -> Self { + self.options.generate_extern_functions = if doit { + GenerateExternFunctions::True + } else { + GenerateExternFunctions::False + }; + self + } + + /// Set the name of the header and source code files that would be created if any extern + /// wrapper functions must be generated due to the presence of inlined functions. + pub fn extern_functions_file_name>( + mut self, + file_name: T, + ) -> Self { + self.options.extern_functions_file_name = + Some(file_name.as_ref().to_owned()); + self + } + + /// Set the directory path where any extra files must be created due to the presence of inlined + /// functions. + pub fn extern_functions_directory>( + mut self, + directory: T, + ) -> Self { + self.options.extern_functions_directory = + Some(directory.as_ref().to_owned().into()); + self + } + + /// Set the suffix added to the extern wrapper functions generated for inlined functions. + pub fn extern_function_suffix>(mut self, suffix: T) -> Self { + self.options.extern_function_suffix = Some(suffix.as_ref().to_owned()); + self + } + + fn second_run(mut self, extra_header: String) -> Self { + self.options.generate_extern_functions = + GenerateExternFunctions::SecondRun; + self.header(extra_header) + } +} + +#[derive(Clone, Copy, Debug)] +enum GenerateExternFunctions { + True, + False, + SecondRun, +} + +impl GenerateExternFunctions { + fn is_true(self) -> bool { + matches!(self, Self::True) + } + + fn is_second_run(self) -> bool { + matches!(self, Self::SecondRun) + } +} + +impl Default for GenerateExternFunctions { + fn default() -> Self { + Self::False + } } /// Configuration options for generated bindings. @@ -2127,6 +2213,14 @@ struct BindgenOptions { /// Whether to wrap unsafe operations in unsafe blocks or not. wrap_unsafe_ops: bool, + + generate_extern_functions: GenerateExternFunctions, + + extern_function_suffix: Option, + + extern_functions_directory: Option, + + extern_functions_file_name: Option, } impl BindgenOptions { @@ -2319,6 +2413,10 @@ impl Default for BindgenOptions { merge_extern_blocks, abi_overrides, wrap_unsafe_ops, + generate_extern_functions, + extern_function_suffix, + extern_functions_directory, + extern_functions_file_name, } } } @@ -2354,7 +2452,6 @@ enum GenerateResult { Ok(Bindings), /// Error variant raised when bindgen requires to run again with a newly generated header /// input. - #[allow(dead_code)] ShouldRestart { header: String, }, @@ -2622,6 +2719,61 @@ impl Bindings { } } + fn serialize_c_items( + context: &BindgenContext, + ) -> Result { + let dir = context + .options() + .extern_functions_directory + .clone() + .unwrap_or_else(|| std::env::temp_dir().join("bindgen")); + + if !dir.exists() { + std::fs::create_dir_all(&dir)?; + } + + let path = std::fs::canonicalize(dir)?.join( + context + .options() + .extern_functions_file_name + .as_deref() + .unwrap_or("extern"), + ); + + let is_cpp = args_are_cpp(&context.options().clang_args) || + context + .options() + .input_headers + .iter() + .any(|h| file_is_cpp(h)); + + let headers_path = + path.with_extension(if is_cpp { "hpp" } else { "h" }); + let source_path = + path.with_extension(if is_cpp { "cpp" } else { "c" }); + + let mut headers_file = File::create(&headers_path)?; + let mut source_file = File::create(source_path)?; + + for item in &context.c_items { + writeln!(headers_file, "{}", item.header())?; + writeln!(source_file, "{}", item.code())?; + } + + Ok(headers_path) + } + + if !context.c_items.is_empty() { + match serialize_c_items(&context) { + Ok(headers) => { + return GenerateResult::ShouldRestart { + header: headers.display().to_string(), + } + } + Err(err) => warn!("Could not serialize C items: {}", err), + } + } + let (module, options, warnings) = codegen::codegen(context); GenerateResult::Ok(Bindings {