Generate extern wrappers for inlined functions (#2335)

* 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=<suffix>`: Adds <suffix> to the name of each
  external wrapper function (`__extern` is used by default).
- `--extern-functions-file-name=<name>`: Uses <name> as the file name
  for the header and source files (`extern` is used by default).
- `--extern-function-directory=<dir>`: Creates the source and header
  files inside <dir> (`/tmp/bindgen` is used by default).

The C code serialization is experimental and only supports a very
limited set of C functions.

Fixes #1090.

---------

Co-authored-by: Amanjeev Sethi <aj@amanjeev.com>
diff --git a/bindgen-cli/Cargo.toml b/bindgen-cli/Cargo.toml
index b900d94..10b4d4c 100644
--- a/bindgen-cli/Cargo.toml
+++ b/bindgen-cli/Cargo.toml
@@ -21,7 +21,7 @@
 name = "bindgen"
 
 [dependencies]
-bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli"] }
+bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli", "experimental"] }
 shlex = "1"
 clap = { version = "4", features = ["derive"] }
 env_logger = { version = "0.9.0", optional = true }
diff --git a/bindgen-cli/options.rs b/bindgen-cli/options.rs
index 2b09d85..458c7ba 100644
--- a/bindgen-cli/options.rs
+++ b/bindgen-cli/options.rs
@@ -353,6 +353,20 @@
     /// Derive custom traits on a `union`. The <CUSTOM> value must be of the shape <REGEX>=<DERIVE> where <DERIVE> is a coma-separated list of derive macros.
     #[arg(long, value_name = "CUSTOM")]
     with_derive_custom_union: Vec<String>,
+    /// Generate wrappers for `static` and `static inline` functions.
+    #[arg(long, requires = "experimental")]
+    wrap_static_fns: bool,
+    /// Sets the path for the source file that must be created due to the presence of `static` and
+    /// `static inline` functions.
+    #[arg(long, requires = "experimental", value_name = "PATH")]
+    wrap_static_fns_path: Option<PathBuf>,
+    /// Sets the suffix added to the extern wrapper functions generated for `static` and `static
+    /// inline` functions.
+    #[arg(long, requires = "experimental", value_name = "SUFFIX")]
+    wrap_static_fns_suffix: Option<String>,
+    /// Enables experimental features.
+    #[arg(long)]
+    experimental: bool,
     /// Prints the version, and exits
     #[arg(short = 'V', long)]
     version: bool,
@@ -473,6 +487,10 @@
         with_derive_custom_struct,
         with_derive_custom_enum,
         with_derive_custom_union,
+        wrap_static_fns,
+        wrap_static_fns_path,
+        wrap_static_fns_suffix,
+        experimental: _,
         version,
         clang_args,
     } = command;
@@ -978,5 +996,17 @@
         }
     }
 
+    if wrap_static_fns {
+        builder = builder.wrap_static_fns(true);
+    }
+
+    if let Some(path) = wrap_static_fns_path {
+        builder = builder.wrap_static_fns_path(path);
+    }
+
+    if let Some(suffix) = wrap_static_fns_suffix {
+        builder = builder.wrap_static_fns_suffix(suffix);
+    }
+
     Ok((builder, output, verbose))
 }
diff --git a/bindgen-integration/Cargo.toml b/bindgen-integration/Cargo.toml
index e2abb6e..60f0426 100644
--- a/bindgen-integration/Cargo.toml
+++ b/bindgen-integration/Cargo.toml
@@ -7,7 +7,7 @@
 build = "build.rs"
 
 [build-dependencies]
-bindgen = { path = "../bindgen" }
+bindgen = { path = "../bindgen", features = ["experimental"] }
 cc = "1.0"
 
 [features]
diff --git a/bindgen-integration/build.rs b/bindgen-integration/build.rs
index 0f30ad4..3cc0edb 100644
--- a/bindgen-integration/build.rs
+++ b/bindgen-integration/build.rs
@@ -4,7 +4,7 @@
 use bindgen::callbacks::{
     DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks,
 };
-use bindgen::{Builder, EnumVariation};
+use bindgen::{Builder, CargoCallbacks, EnumVariation};
 use std::collections::HashSet;
 use std::env;
 use std::path::PathBuf;
@@ -28,21 +28,14 @@
         MacroParsingBehavior::Default
     }
 
-    fn item_name(&self, original_item_name: &str) -> Option<String> {
-        if original_item_name.starts_with("my_prefixed_") {
-            Some(
-                original_item_name
-                    .trim_start_matches("my_prefixed_")
-                    .to_string(),
-            )
-        } else if original_item_name.starts_with("MY_PREFIXED_") {
-            Some(
-                original_item_name
-                    .trim_start_matches("MY_PREFIXED_")
-                    .to_string(),
-            )
-        } else {
-            None
+    fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
+        match name {
+            "TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom {
+                name: "crate::MacroInteger",
+                is_signed: true,
+            }),
+
+            _ => None,
         }
     }
 
@@ -67,17 +60,6 @@
         }
     }
 
-    fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
-        match name {
-            "TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom {
-                name: "crate::MacroInteger",
-                is_signed: true,
-            }),
-
-            _ => None,
-        }
-    }
-
     fn func_macro(&self, name: &str, value: &[&[u8]]) {
         match name {
             "TESTMACRO_NONFUNCTIONAL" => {
@@ -122,6 +104,24 @@
         }
     }
 
+    fn item_name(&self, original_item_name: &str) -> Option<String> {
+        if original_item_name.starts_with("my_prefixed_") {
+            Some(
+                original_item_name
+                    .trim_start_matches("my_prefixed_")
+                    .to_string(),
+            )
+        } else if original_item_name.starts_with("MY_PREFIXED_") {
+            Some(
+                original_item_name
+                    .trim_start_matches("MY_PREFIXED_")
+                    .to_string(),
+            )
+        } else {
+            None
+        }
+    }
+
     // Test the "custom derives" capability by adding `PartialEq` to the `Test` struct.
     fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> {
         if info.name == "Test" {
@@ -149,7 +149,7 @@
     }
 }
 
-fn main() {
+fn setup_macro_test() {
     cc::Build::new()
         .cpp(true)
         .file("cpp/Test.cc")
@@ -204,3 +204,75 @@
         "including stub via include dir must produce correct dep path",
     );
 }
+
+fn setup_wrap_static_fns_test() {
+    // GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090
+    // set output directory under /target so it is easy to clean generated files
+    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+    let out_rust_file = out_path.join("extern.rs");
+
+    let input_header_dir = PathBuf::from("../bindgen-tests/tests/headers/")
+        .canonicalize()
+        .expect("Cannot canonicalize libdir path");
+    let input_header_file_path = input_header_dir.join("wrap-static-fns.h");
+    let input_header_file_path_str = input_header_file_path
+        .to_str()
+        .expect("Path could not be converted to a str");
+
+    // generate external bindings with the external .c and .h files
+    let bindings = Builder::default()
+        .header(input_header_file_path_str)
+        .parse_callbacks(Box::new(CargoCallbacks))
+        .wrap_static_fns(true)
+        .wrap_static_fns_path(
+            out_path.join("wrap_static_fns").display().to_string(),
+        )
+        .generate()
+        .expect("Unable to generate bindings");
+
+    println!("cargo:rustc-link-lib=static=wrap_static_fns"); // tell cargo to link libextern
+    println!("bindings generated: {}", bindings);
+
+    let obj_path = out_path.join("wrap_static_fns.o");
+    let lib_path = out_path.join("libwrap_static_fns.a");
+
+    // build the external files to check if they work
+    let clang_output = std::process::Command::new("clang")
+        .arg("-c")
+        .arg("-o")
+        .arg(&obj_path)
+        .arg(out_path.join("wrap_static_fns.c"))
+        .arg("-include")
+        .arg(input_header_file_path)
+        .output()
+        .expect("`clang` command error");
+    if !clang_output.status.success() {
+        panic!(
+            "Could not compile object file:\n{}",
+            String::from_utf8_lossy(&clang_output.stderr)
+        );
+    }
+
+    let ar_output = std::process::Command::new("ar")
+        .arg("rcs")
+        .arg(lib_path)
+        .arg(obj_path)
+        .output()
+        .expect("`ar` command error");
+
+    if !ar_output.status.success() {
+        panic!(
+            "Could not emit library file:\n{}",
+            String::from_utf8_lossy(&ar_output.stderr)
+        );
+    }
+
+    bindings
+        .write_to_file(out_rust_file)
+        .expect("Cound not write bindings to the Rust file");
+}
+
+fn main() {
+    setup_macro_test();
+    setup_wrap_static_fns_test();
+}
diff --git a/bindgen-integration/src/lib.rs b/bindgen-integration/src/lib.rs
index 43f7158..e89351c 100755
--- a/bindgen-integration/src/lib.rs
+++ b/bindgen-integration/src/lib.rs
@@ -4,6 +4,10 @@
     include!(concat!(env!("OUT_DIR"), "/test.rs"));
 }
 
+mod extern_bindings {
+    include!(concat!(env!("OUT_DIR"), "/extern.rs"));
+}
+
 use std::ffi::CStr;
 use std::mem;
 use std::os::raw::c_int;
@@ -286,3 +290,35 @@
     assert!(meter < lightyear);
     assert!(meter > micron);
 }
+
+#[test]
+fn test_wrap_static_fns() {
+    // GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090
+    unsafe {
+        let f = extern_bindings::foo();
+        assert_eq!(11, f);
+
+        let b = extern_bindings::bar();
+        assert_eq!(1, b);
+
+        let t = extern_bindings::takes_ptr(&mut 1);
+        assert_eq!(2, t);
+
+        extern "C" fn function(x: i32) -> i32 {
+            x + 1
+        }
+
+        let tp = extern_bindings::takes_fn_ptr(Some(function));
+        assert_eq!(2, tp);
+
+        let tf = extern_bindings::takes_fn(Some(function));
+        assert_eq!(3, tf);
+
+        let ta = extern_bindings::takes_alias(Some(function));
+        assert_eq!(4, ta);
+
+        let tq =
+            extern_bindings::takes_qualified(&(&5 as *const _) as *const _);
+        assert_eq!(5, tq);
+    }
+}
diff --git a/bindgen-tests/Cargo.toml b/bindgen-tests/Cargo.toml
index 0678274..6df84e8 100644
--- a/bindgen-tests/Cargo.toml
+++ b/bindgen-tests/Cargo.toml
@@ -5,7 +5,7 @@
 publish = false
 
 [dev-dependencies]
-bindgen = { path = "../bindgen", features = ["cli"] }
+bindgen = { path = "../bindgen", features = ["cli", "experimental"] }
 diff = "0.1"
 shlex = "1"
 clap = { version = "4", features = ["derive"] }
diff --git a/bindgen-tests/tests/expectations/tests/generated/README.md b/bindgen-tests/tests/expectations/tests/generated/README.md
new file mode 100644
index 0000000..b4e8cab
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/generated/README.md
@@ -0,0 +1,4 @@
+# Generated C, C++, Header files
+
+This directory contains files for features where extra files are generated
+as a part of the feature. For example, `--wrap-static-fns`.
diff --git a/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c b/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c
new file mode 100644
index 0000000..22b2f67
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c
@@ -0,0 +1,14 @@
+int foo__extern(void) asm("foo__extern");
+int foo__extern() { return foo(); }
+int bar__extern(void) asm("bar__extern");
+int bar__extern() { return bar(); }
+int takes_ptr__extern(int *arg) asm("takes_ptr__extern");
+int takes_ptr__extern(int *arg) { return takes_ptr(arg); }
+int takes_fn_ptr__extern(int (*f) (int)) asm("takes_fn_ptr__extern");
+int takes_fn_ptr__extern(int (*f) (int)) { return takes_fn_ptr(f); }
+int takes_fn__extern(int (f) (int)) asm("takes_fn__extern");
+int takes_fn__extern(int (f) (int)) { return takes_fn(f); }
+int takes_alias__extern(func f) asm("takes_alias__extern");
+int takes_alias__extern(func f) { return takes_alias(f); }
+int takes_qualified__extern(const int *const *arg) asm("takes_qualified__extern");
+int takes_qualified__extern(const int *const *arg) { return takes_qualified(arg); }
diff --git a/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs b/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs
new file mode 100644
index 0000000..54ed9fd
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs
@@ -0,0 +1,52 @@
+#![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;
+}
+extern "C" {
+    #[link_name = "\u{1}takes_ptr__extern"]
+    pub fn takes_ptr(arg: *mut ::std::os::raw::c_int) -> ::std::os::raw::c_int;
+}
+extern "C" {
+    #[link_name = "\u{1}takes_fn_ptr__extern"]
+    pub fn takes_fn_ptr(
+        f: ::std::option::Option<
+            unsafe extern "C" fn(
+                arg1: ::std::os::raw::c_int,
+            ) -> ::std::os::raw::c_int,
+        >,
+    ) -> ::std::os::raw::c_int;
+}
+extern "C" {
+    #[link_name = "\u{1}takes_fn__extern"]
+    pub fn takes_fn(
+        f: ::std::option::Option<
+            unsafe extern "C" fn(
+                arg1: ::std::os::raw::c_int,
+            ) -> ::std::os::raw::c_int,
+        >,
+    ) -> ::std::os::raw::c_int;
+}
+pub type func = ::std::option::Option<
+    unsafe extern "C" fn(arg1: ::std::os::raw::c_int) -> ::std::os::raw::c_int,
+>;
+extern "C" {
+    #[link_name = "\u{1}takes_alias__extern"]
+    pub fn takes_alias(f: func) -> ::std::os::raw::c_int;
+}
+extern "C" {
+    #[link_name = "\u{1}takes_qualified__extern"]
+    pub fn takes_qualified(
+        arg: *const *const ::std::os::raw::c_int,
+    ) -> ::std::os::raw::c_int;
+}
diff --git a/bindgen-tests/tests/headers/wrap-static-fns.h b/bindgen-tests/tests/headers/wrap-static-fns.h
new file mode 100644
index 0000000..8b90c7b
--- /dev/null
+++ b/bindgen-tests/tests/headers/wrap-static-fns.h
@@ -0,0 +1,33 @@
+// bindgen-flags: --experimental --wrap-static-fns
+
+static inline int foo() {
+    return 11;
+}
+static int bar() {
+    return 1;
+}
+inline int baz() {
+    return 2;
+}
+
+static inline int takes_ptr(int* arg) {
+    return *arg + 1;
+}
+
+static inline int takes_fn_ptr(int (*f)(int)) {
+    return f(1);
+}
+
+static inline int takes_fn(int (f)(int)) {
+    return f(2);
+}
+
+typedef int (func)(int);
+
+static inline int takes_alias(func f) {
+    return f(3);
+}
+
+static inline int takes_qualified(const int *const *arg) {
+    return **arg;
+}
diff --git a/bindgen-tests/tests/tests.rs b/bindgen-tests/tests/tests.rs
index 25c073c..ed8566c 100644
--- a/bindgen-tests/tests/tests.rs
+++ b/bindgen-tests/tests/tests.rs
@@ -713,3 +713,40 @@
         .header("tests/headers/16-byte-alignment.h");
     build_flags_output_helper(&bindings);
 }
+
+#[test]
+fn test_wrap_static_fns() {
+    // This test is for testing diffs of the generated C source and header files
+    // TODO: If another such feature is added, convert this test into a more generic
+    //      test that looks at `tests/headers/generated` directory.
+    let expect_path = PathBuf::from("tests/expectations/tests/generated")
+        .join("wrap_static_fns");
+    println!("In path is ::: {}", expect_path.display());
+
+    let generated_path =
+        PathBuf::from(env::var("OUT_DIR").unwrap()).join("wrap_static_fns");
+    println!("Out path is ::: {}", generated_path.display());
+
+    let _bindings = Builder::default()
+        .header("tests/headers/wrap-static-fns.h")
+        .wrap_static_fns(true)
+        .wrap_static_fns_path(generated_path.display().to_string())
+        .generate()
+        .expect("Failed to generate bindings");
+
+    let expected_c = fs::read_to_string(expect_path.with_extension("c"))
+        .expect("Could not read generated wrap_static_fns.c");
+
+    let actual_c = fs::read_to_string(generated_path.with_extension("c"))
+        .expect("Could not read actual wrap_static_fns.c");
+
+    if expected_c != actual_c {
+        error_diff_mismatch(
+            &actual_c,
+            &expected_c,
+            None,
+            &expect_path.with_extension("c"),
+        )
+        .unwrap();
+    }
+}
diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml
index 2292a5e..6d4902d 100644
--- a/bindgen/Cargo.toml
+++ b/bindgen/Cargo.toml
@@ -48,6 +48,7 @@
 # Dynamically discover a `rustfmt` binary using the `which` crate
 which-rustfmt = ["which"]
 cli = []
+experimental = []
 
 # These features only exist for CI testing -- don't use them if you're not hacking
 # on bindgen!
diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs
index 6b24ae1..b6fb70e 100644
--- a/bindgen/codegen/mod.rs
+++ b/bindgen/codegen/mod.rs
@@ -4,6 +4,7 @@
 mod impl_debug;
 mod impl_partialeq;
 mod postprocessing;
+mod serialize;
 pub mod struct_layout;
 
 #[cfg(test)]
@@ -59,6 +60,29 @@
 use std::ops;
 use std::str::FromStr;
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum CodegenError {
+    Serialize { msg: String, loc: String },
+    Io(String),
+}
+
+impl From<std::io::Error> for CodegenError {
+    fn from(err: std::io::Error) -> Self {
+        Self::Io(err.to_string())
+    }
+}
+
+impl std::fmt::Display for CodegenError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            CodegenError::Serialize { msg, loc } => {
+                write!(f, "serialization error at {}: {}", loc, msg)
+            }
+            CodegenError::Io(err) => err.fmt(f),
+        }
+    }
+}
+
 // Name of type defined in constified enum module
 pub static CONSTIFIED_ENUM_MODULE_REPR_NAME: &str = "Type";
 
@@ -241,6 +265,8 @@
     /// function name to the number of overloads we have already codegen'd for
     /// that name. This lets us give each overload a unique suffix.
     overload_counters: HashMap<String, u32>,
+
+    items_to_serialize: Vec<ItemId>,
 }
 
 impl<'a> CodegenResult<'a> {
@@ -258,6 +284,7 @@
             functions_seen: Default::default(),
             vars_seen: Default::default(),
             overload_counters: Default::default(),
+            items_to_serialize: Default::default(),
         }
     }
 
@@ -4000,11 +4027,16 @@
         debug!("<Function as CodeGenerator>::codegen: item = {:?}", item);
         debug_assert!(item.is_enabled_for_codegen(ctx));
 
-        // 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 => {}
+        let is_internal = matches!(self.linkage(), Linkage::Internal);
+
+        if is_internal {
+            if ctx.options().wrap_static_fns {
+                result.items_to_serialize.push(item.id());
+            } else {
+                // We can't do anything with Internal functions if we are not wrapping them so just
+                // avoid generating anything for them.
+                return None;
+            }
         }
 
         // Pure virtual methods have no actual symbol, so we can't generate
@@ -4114,6 +4146,7 @@
             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(
@@ -4123,6 +4156,7 @@
             )
         {
             attributes.push(attributes::link_name(link_name));
+            has_link_name_attr = true;
         }
 
         // Unfortunately this can't piggyback on the `attributes` list because
@@ -4133,6 +4167,11 @@
                 quote! { #[link(wasm_import_module = #name)] }
             });
 
+        if is_internal && ctx.options().wrap_static_fns && !has_link_name_attr {
+            let name = canonical_name.clone() + ctx.wrap_static_fns_suffix();
+            attributes.push(attributes::link_name(&name));
+        }
+
         let ident = ctx.rust_ident(canonical_name);
         let tokens = quote! {
             #wasm_link_attribute
@@ -4437,7 +4476,8 @@
 
 pub(crate) fn codegen(
     context: BindgenContext,
-) -> (proc_macro2::TokenStream, BindgenOptions, Vec<String>) {
+) -> Result<(proc_macro2::TokenStream, BindgenOptions, Vec<String>), CodegenError>
+{
     context.gen(|context| {
         let _t = context.timer("codegen");
         let counter = Cell::new(0);
@@ -4487,21 +4527,73 @@
             result.push(dynamic_items_tokens);
         }
 
-        postprocessing::postprocessing(result.items, context.options())
+        utils::serialize_items(&result, context)?;
+
+        Ok(postprocessing::postprocessing(
+            result.items,
+            context.options(),
+        ))
     })
 }
 
 pub mod utils {
-    use super::{error, ToRustTyOrOpaque};
+    use super::serialize::CSerialize;
+    use super::{error, CodegenError, CodegenResult, ToRustTyOrOpaque};
     use crate::ir::context::BindgenContext;
     use crate::ir::function::{Abi, ClangAbi, FunctionSig};
     use crate::ir::item::{Item, ItemCanonicalPath};
     use crate::ir::ty::TypeKind;
+    use crate::{args_are_cpp, file_is_cpp};
     use proc_macro2;
     use std::borrow::Cow;
     use std::mem;
+    use std::path::PathBuf;
     use std::str::FromStr;
 
+    pub(super) fn serialize_items(
+        result: &CodegenResult,
+        context: &BindgenContext,
+    ) -> Result<(), CodegenError> {
+        if result.items_to_serialize.is_empty() {
+            return Ok(());
+        }
+
+        let path = context
+            .options()
+            .wrap_static_fns_path
+            .as_ref()
+            .map(PathBuf::from)
+            .unwrap_or_else(|| {
+                std::env::temp_dir().join("bindgen").join("extern")
+            });
+
+        let dir = path.parent().unwrap();
+
+        if !dir.exists() {
+            std::fs::create_dir_all(&dir)?;
+        }
+
+        let is_cpp = args_are_cpp(&context.options().clang_args) ||
+            context
+                .options()
+                .input_headers
+                .iter()
+                .any(|h| file_is_cpp(h));
+
+        let source_path = path.with_extension(if is_cpp { "cpp" } else { "c" });
+
+        let mut code = Vec::new();
+
+        for &id in &result.items_to_serialize {
+            let item = context.resolve_item(id);
+            item.serialize(context, (), &mut vec![], &mut code)?;
+        }
+
+        std::fs::write(source_path, code)?;
+
+        Ok(())
+    }
+
     pub fn prepend_bitfield_unit_type(
         ctx: &BindgenContext,
         result: &mut Vec<proc_macro2::TokenStream>,
diff --git a/bindgen/codegen/serialize.rs b/bindgen/codegen/serialize.rs
new file mode 100644
index 0000000..217098e
--- /dev/null
+++ b/bindgen/codegen/serialize.rs
@@ -0,0 +1,356 @@
+use std::io::Write;
+
+use crate::callbacks::IntKind;
+
+use crate::ir::comp::CompKind;
+use crate::ir::context::{BindgenContext, TypeId};
+use crate::ir::function::{Function, FunctionKind};
+use crate::ir::item::Item;
+use crate::ir::item::ItemCanonicalName;
+use crate::ir::item_kind::ItemKind;
+use crate::ir::ty::{FloatKind, Type, TypeKind};
+
+use super::CodegenError;
+
+fn get_loc(item: &Item) -> String {
+    item.location()
+        .map(|x| x.to_string())
+        .unwrap_or_else(|| "unknown".to_owned())
+}
+
+pub(crate) trait CSerialize<'a> {
+    type Extra;
+
+    fn serialize<W: Write>(
+        &self,
+        ctx: &BindgenContext,
+        extra: Self::Extra,
+        stack: &mut Vec<String>,
+        writer: &mut W,
+    ) -> Result<(), CodegenError>;
+}
+
+impl<'a> CSerialize<'a> for Item {
+    type Extra = ();
+
+    fn serialize<W: Write>(
+        &self,
+        ctx: &BindgenContext,
+        (): Self::Extra,
+        stack: &mut Vec<String>,
+        writer: &mut W,
+    ) -> Result<(), CodegenError> {
+        match self.kind() {
+            ItemKind::Function(func) => {
+                func.serialize(ctx, self, stack, writer)
+            }
+            kind => {
+                return Err(CodegenError::Serialize {
+                    msg: format!("Cannot serialize item kind {:?}", kind),
+                    loc: get_loc(self),
+                });
+            }
+        }
+    }
+}
+
+impl<'a> CSerialize<'a> for Function {
+    type Extra = &'a Item;
+
+    fn serialize<W: Write>(
+        &self,
+        ctx: &BindgenContext,
+        item: Self::Extra,
+        stack: &mut Vec<String>,
+        writer: &mut W,
+    ) -> Result<(), CodegenError> {
+        if self.kind() != FunctionKind::Function {
+            return Err(CodegenError::Serialize {
+                msg: format!(
+                    "Cannot serialize function kind {:?}",
+                    self.kind(),
+                ),
+                loc: get_loc(item),
+            });
+        }
+
+        let signature = match ctx.resolve_type(self.signature()).kind() {
+            TypeKind::Function(signature) => signature,
+            _ => unreachable!(),
+        };
+
+        let name = self.name();
+
+        // Function argoments stored as `(name, type_id)` tuples.
+        let args = {
+            let mut count = 0;
+
+            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::<Vec<_>>()
+        };
+
+        // The name used for the wrapper self.
+        let wrap_name = format!("{}{}", name, ctx.wrap_static_fns_suffix());
+        // The function's return type
+        let ret_ty = signature.return_type();
+
+        // Write `ret_ty wrap_name(args) asm("wrap_name");`
+        ret_ty.serialize(ctx, (), stack, writer)?;
+        write!(writer, " {}(", wrap_name)?;
+        if args.is_empty() {
+            write!(writer, "void")?;
+        } else {
+            serialize_sep(
+                ", ",
+                args.iter(),
+                ctx,
+                writer,
+                |(name, type_id), ctx, buf| {
+                    type_id.serialize(ctx, (), &mut vec![name.clone()], buf)
+                },
+            )?;
+        }
+        writeln!(writer, ") asm(\"{}\");", wrap_name)?;
+
+        // Write `ret_ty wrap_name(args) { return name(arg_names)' }`
+        ret_ty.serialize(ctx, (), stack, writer)?;
+        write!(writer, " {}(", wrap_name)?;
+        serialize_sep(
+            ", ",
+            args.iter(),
+            ctx,
+            writer,
+            |(name, type_id), _, buf| {
+                type_id.serialize(ctx, (), &mut vec![name.clone()], buf)
+            },
+        )?;
+        write!(writer, ") {{ return {}(", name)?;
+        serialize_sep(", ", args.iter(), ctx, writer, |(name, _), _, buf| {
+            write!(buf, "{}", name).map_err(From::from)
+        })?;
+        writeln!(writer, "); }}")?;
+
+        Ok(())
+    }
+}
+
+impl<'a> CSerialize<'a> for TypeId {
+    type Extra = ();
+
+    fn serialize<W: Write>(
+        &self,
+        ctx: &BindgenContext,
+        (): Self::Extra,
+        stack: &mut Vec<String>,
+        writer: &mut W,
+    ) -> Result<(), CodegenError> {
+        let item = ctx.resolve_item(*self);
+        item.expect_type().serialize(ctx, item, stack, writer)
+    }
+}
+
+impl<'a> CSerialize<'a> for Type {
+    type Extra = &'a Item;
+
+    fn serialize<W: Write>(
+        &self,
+        ctx: &BindgenContext,
+        item: Self::Extra,
+        stack: &mut Vec<String>,
+        writer: &mut W,
+    ) -> Result<(), CodegenError> {
+        match self.kind() {
+            TypeKind::Void => {
+                if self.is_const() {
+                    write!(writer, "const ")?;
+                }
+                write!(writer, "void")?
+            }
+            TypeKind::NullPtr => {
+                if self.is_const() {
+                    write!(writer, "const ")?;
+                }
+                write!(writer, "nullptr_t")?
+            }
+            TypeKind::Int(int_kind) => {
+                if self.is_const() {
+                    write!(writer, "const ")?;
+                }
+                match int_kind {
+                    IntKind::Bool => write!(writer, "bool")?,
+                    IntKind::SChar => write!(writer, "signed char")?,
+                    IntKind::UChar => write!(writer, "unsigned char")?,
+                    IntKind::WChar => write!(writer, "wchar_t")?,
+                    IntKind::Short => write!(writer, "short")?,
+                    IntKind::UShort => write!(writer, "unsigned short")?,
+                    IntKind::Int => write!(writer, "int")?,
+                    IntKind::UInt => write!(writer, "unsigned int")?,
+                    IntKind::Long => write!(writer, "long")?,
+                    IntKind::ULong => write!(writer, "unsigned long")?,
+                    IntKind::LongLong => write!(writer, "long long")?,
+                    IntKind::ULongLong => write!(writer, "unsigned long long")?,
+                    IntKind::Char { .. } => write!(writer, "char")?,
+                    int_kind => {
+                        return Err(CodegenError::Serialize {
+                            msg: format!(
+                                "Cannot serialize integer kind {:?}",
+                                int_kind
+                            ),
+                            loc: get_loc(item),
+                        })
+                    }
+                }
+            }
+            TypeKind::Float(float_kind) => {
+                if self.is_const() {
+                    write!(writer, "const ")?;
+                }
+                match float_kind {
+                    FloatKind::Float => write!(writer, "float")?,
+                    FloatKind::Double => write!(writer, "double")?,
+                    FloatKind::LongDouble => write!(writer, "long double")?,
+                    FloatKind::Float128 => write!(writer, "__float128")?,
+                }
+            }
+            TypeKind::Complex(float_kind) => {
+                if self.is_const() {
+                    write!(writer, "const ")?;
+                }
+                match float_kind {
+                    FloatKind::Float => write!(writer, "float complex")?,
+                    FloatKind::Double => write!(writer, "double complex")?,
+                    FloatKind::LongDouble => {
+                        write!(writer, "long double complex")?
+                    }
+                    FloatKind::Float128 => write!(writer, "__complex128")?,
+                }
+            }
+            TypeKind::Alias(type_id) => {
+                if let Some(name) = self.name() {
+                    if self.is_const() {
+                        write!(writer, "const {}", name)?;
+                    } else {
+                        write!(writer, "{}", name)?;
+                    }
+                } else {
+                    type_id.serialize(ctx, (), stack, writer)?;
+                }
+            }
+            TypeKind::Array(type_id, length) => {
+                type_id.serialize(ctx, (), stack, writer)?;
+                write!(writer, " [{}]", length)?
+            }
+            TypeKind::Function(signature) => {
+                if self.is_const() {
+                    stack.push("const ".to_string());
+                }
+
+                signature.return_type().serialize(
+                    ctx,
+                    (),
+                    &mut vec![],
+                    writer,
+                )?;
+
+                write!(writer, " (")?;
+                while let Some(item) = stack.pop() {
+                    write!(writer, "{}", item)?;
+                }
+                write!(writer, ")")?;
+
+                write!(writer, " (")?;
+                serialize_sep(
+                    ", ",
+                    signature.argument_types().iter(),
+                    ctx,
+                    writer,
+                    |(name, type_id), ctx, buf| {
+                        let mut stack = vec![];
+                        if let Some(name) = name {
+                            stack.push(name.clone());
+                        }
+                        type_id.serialize(ctx, (), &mut stack, buf)
+                    },
+                )?;
+                write!(writer, ")")?
+            }
+            TypeKind::ResolvedTypeRef(type_id) => {
+                if self.is_const() {
+                    write!(writer, "const ")?;
+                }
+                type_id.serialize(ctx, (), stack, writer)?
+            }
+            TypeKind::Pointer(type_id) => {
+                if self.is_const() {
+                    stack.push("*const ".to_owned());
+                } else {
+                    stack.push("*".to_owned());
+                }
+                type_id.serialize(ctx, (), stack, writer)?
+            }
+            TypeKind::Comp(comp_info) => {
+                if self.is_const() {
+                    write!(writer, "const ")?;
+                }
+
+                let name = item.canonical_name(ctx);
+
+                match comp_info.kind() {
+                    CompKind::Struct => write!(writer, "struct {}", name)?,
+                    CompKind::Union => write!(writer, "union {}", name)?,
+                };
+            }
+            ty => {
+                return Err(CodegenError::Serialize {
+                    msg: format!("Cannot serialize type kind {:?}", ty),
+                    loc: get_loc(item),
+                })
+            }
+        };
+
+        if !stack.is_empty() {
+            write!(writer, " ")?;
+            while let Some(item) = stack.pop() {
+                write!(writer, "{}", item)?;
+            }
+        }
+
+        Ok(())
+    }
+}
+
+fn serialize_sep<
+    W: Write,
+    F: FnMut(I::Item, &BindgenContext, &mut W) -> Result<(), CodegenError>,
+    I: Iterator,
+>(
+    sep: &str,
+    mut iter: I,
+    ctx: &BindgenContext,
+    buf: &mut W,
+    mut f: F,
+) -> Result<(), CodegenError> {
+    if let Some(item) = iter.next() {
+        f(item, ctx, buf)?;
+        let sep = sep.as_bytes();
+        for item in iter {
+            buf.write_all(sep)?;
+            f(item, ctx, buf)?;
+        }
+    }
+
+    Ok(())
+}
diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs
index c5df37d..b693a70 100644
--- a/bindgen/ir/context.rs
+++ b/bindgen/ir/context.rs
@@ -20,6 +20,7 @@
 use super::traversal::{self, Edge, ItemTraversal};
 use super::ty::{FloatKind, Type, TypeKind};
 use crate::clang::{self, Cursor};
+use crate::codegen::CodegenError;
 use crate::BindgenOptions;
 use crate::{Entry, HashMap, HashSet};
 use cexpr;
@@ -1146,9 +1147,9 @@
     pub(crate) fn gen<F, Out>(
         mut self,
         cb: F,
-    ) -> (Out, BindgenOptions, Vec<String>)
+    ) -> Result<(Out, BindgenOptions, Vec<String>), CodegenError>
     where
-        F: FnOnce(&Self) -> Out,
+        F: FnOnce(&Self) -> Result<Out, CodegenError>,
     {
         self.in_codegen = true;
 
@@ -1183,8 +1184,8 @@
         self.compute_cannot_derive_hash();
         self.compute_cannot_derive_partialord_partialeq_or_eq();
 
-        let ret = cb(&self);
-        (ret, self.options, self.warnings)
+        let ret = cb(&self)?;
+        Ok((ret, self.options, self.warnings))
     }
 
     /// When the `testing_only_extra_assertions` feature is enabled, this
@@ -2792,6 +2793,13 @@
             tokens.into_token_stream()
         }
     }
+
+    pub(crate) fn wrap_static_fns_suffix(&self) -> &str {
+        self.options()
+            .wrap_static_fns_suffix
+            .as_deref()
+            .unwrap_or(crate::DEFAULT_NON_EXTERN_FNS_SUFFIX)
+    }
 }
 
 /// A builder struct for configuring item resolution options.
diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs
index 8e83d98..baa2c36 100644
--- a/bindgen/ir/function.rs
+++ b/bindgen/ir/function.rs
@@ -664,7 +664,6 @@
         };
 
         debug!("Function::parse({:?}, {:?})", cursor, cursor.cur_type());
-
         let visibility = cursor.visibility();
         if visibility != CXVisibility_Default {
             return Err(ParseError::Continue);
@@ -674,19 +673,6 @@
             return Err(ParseError::Continue);
         }
 
-        if cursor.is_inlined_function() ||
-            cursor
-                .definition()
-                .map_or(false, |x| x.is_inlined_function())
-        {
-            if !context.options().generate_inline_functions {
-                return Err(ParseError::Continue);
-            }
-            if cursor.is_deleted_function() {
-                return Err(ParseError::Continue);
-            }
-        }
-
         let linkage = cursor.linkage();
         let linkage = match linkage {
             CXLinkage_External | CXLinkage_UniqueExternal => Linkage::External,
@@ -694,6 +680,30 @@
             _ => return Err(ParseError::Continue),
         };
 
+        if cursor.is_inlined_function() ||
+            cursor
+                .definition()
+                .map_or(false, |x| x.is_inlined_function())
+        {
+            if !context.options().generate_inline_functions &&
+                !context.options().wrap_static_fns
+            {
+                return Err(ParseError::Continue);
+            }
+
+            if cursor.is_deleted_function() {
+                return Err(ParseError::Continue);
+            }
+
+            // We cannot handle `inline` functions that are not `static`.
+            if context.options().wrap_static_fns &&
+                cursor.is_inlined_function() &&
+                matches!(linkage, Linkage::External)
+            {
+                return Err(ParseError::Continue);
+            }
+        }
+
         // Grab the signature using Item::from_ty.
         let sig = Item::from_ty(&cursor.cur_type(), cursor, None, context)?;
 
@@ -727,7 +737,8 @@
         let comment = cursor.raw_comment();
 
         let function =
-            Self::new(name, mangled_name, sig, comment, kind, linkage);
+            Self::new(name.clone(), mangled_name, sig, comment, kind, linkage);
+
         Ok(ParseResult::New(function, Some(cursor)))
     }
 }
diff --git a/bindgen/lib.rs b/bindgen/lib.rs
index 8c8ffe5..5374742 100644
--- a/bindgen/lib.rs
+++ b/bindgen/lib.rs
@@ -78,6 +78,7 @@
 doc_mod!(parse, parse_docs);
 doc_mod!(regex_set, regex_set_docs);
 
+use codegen::CodegenError;
 use ir::comment;
 
 pub use crate::codegen::{
@@ -108,6 +109,7 @@
 
 /// Default prefix for the anon fields.
 pub const DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_";
+const DEFAULT_NON_EXTERN_FNS_SUFFIX: &str = "__extern";
 
 fn file_is_cpp(name_file: &str) -> bool {
     name_file.ends_with(".hpp") ||
@@ -657,6 +659,23 @@
         for callbacks in &self.options.parse_callbacks {
             output_vector.extend(callbacks.cli_args());
         }
+        if self.options.wrap_static_fns {
+            output_vector.push("--wrap-static-fns".into())
+        }
+
+        if let Some(ref path) = self.options.wrap_static_fns_path {
+            output_vector.push("--wrap-static-fns-path".into());
+            output_vector.push(path.display().to_string());
+        }
+
+        if let Some(ref suffix) = self.options.wrap_static_fns_suffix {
+            output_vector.push("--wrap-static-fns-suffix".into());
+            output_vector.push(suffix.clone());
+        }
+
+        if cfg!(feature = "experimental") {
+            output_vector.push("--experimental".into());
+        }
 
         // Add clang arguments
 
@@ -1553,33 +1572,25 @@
     }
 
     /// Generate the Rust bindings using the options built up thus far.
-    pub fn generate(self) -> Result<Bindings, BindgenError> {
-        let mut options = self.options.clone();
+    pub fn generate(mut self) -> Result<Bindings, BindgenError> {
         // Add any extra arguments from the environment to the clang command line.
-        options.clang_args.extend(get_extra_clang_args());
+        self.options.clang_args.extend(get_extra_clang_args());
 
         // Transform input headers to arguments on the clang command line.
-        options.clang_args.extend(
-            options.input_headers
-                [..options.input_headers.len().saturating_sub(1)]
+        self.options.clang_args.extend(
+            self.options.input_headers
+                [..self.options.input_headers.len().saturating_sub(1)]
                 .iter()
                 .flat_map(|header| ["-include".into(), header.to_string()]),
         );
 
         let input_unsaved_files =
-            std::mem::take(&mut options.input_header_contents)
+            std::mem::take(&mut self.options.input_header_contents)
                 .into_iter()
                 .map(|(name, contents)| clang::UnsavedFile::new(name, contents))
                 .collect::<Vec<_>>();
 
-        match Bindings::generate(options, input_unsaved_files) {
-            GenerateResult::Ok(bindings) => Ok(*bindings),
-            GenerateResult::ShouldRestart { header } => self
-                .header(header)
-                .generate_inline_functions(false)
-                .generate(),
-            GenerateResult::Err(err) => Err(err),
-        }
+        Bindings::generate(self.options, input_unsaved_files)
     }
 
     /// Preprocess and dump the input header files to disk.
@@ -1796,6 +1807,32 @@
         self.options.wrap_unsafe_ops = doit;
         self
     }
+
+    #[cfg(feature = "experimental")]
+    /// Whether to generate extern wrappers for `static` and `static inline` functions. Defaults to
+    /// false.
+    pub fn wrap_static_fns(mut self, doit: bool) -> Self {
+        self.options.wrap_static_fns = doit;
+        self
+    }
+
+    #[cfg(feature = "experimental")]
+    /// Set the path for the source code file that would be created if any wrapper functions must
+    /// be generated due to the presence of static functions.
+    ///
+    /// Bindgen will automatically add the right extension to the header and source code files.
+    pub fn wrap_static_fns_path<T: AsRef<Path>>(mut self, path: T) -> Self {
+        self.options.wrap_static_fns_path = Some(path.as_ref().to_owned());
+        self
+    }
+
+    #[cfg(feature = "experimental")]
+    /// Set the suffix added to the extern wrapper functions generated for `static` and `static
+    /// inline` functions.
+    pub fn wrap_static_fns_suffix<T: AsRef<str>>(mut self, suffix: T) -> Self {
+        self.options.wrap_static_fns_suffix = Some(suffix.as_ref().to_owned());
+        self
+    }
 }
 
 /// Configuration options for generated bindings.
@@ -2136,6 +2173,12 @@
 
     /// Whether to wrap unsafe operations in unsafe blocks or not.
     wrap_unsafe_ops: bool,
+
+    wrap_static_fns: bool,
+
+    wrap_static_fns_suffix: Option<String>,
+
+    wrap_static_fns_path: Option<PathBuf>,
 }
 
 impl BindgenOptions {
@@ -2328,6 +2371,9 @@
             merge_extern_blocks,
             abi_overrides,
             wrap_unsafe_ops,
+            wrap_static_fns,
+            wrap_static_fns_suffix,
+            wrap_static_fns_path,
         }
     }
 }
@@ -2358,18 +2404,6 @@
 #[cfg(not(feature = "runtime"))]
 fn ensure_libclang_is_loaded() {}
 
-#[derive(Debug)]
-enum GenerateResult {
-    Ok(Box<Bindings>),
-    /// Error variant raised when bindgen requires to run again with a newly generated header
-    /// input.
-    #[allow(dead_code)]
-    ShouldRestart {
-        header: String,
-    },
-    Err(BindgenError),
-}
-
 /// Error type for rust-bindgen.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 #[non_exhaustive]
@@ -2382,6 +2416,8 @@
     NotExist(PathBuf),
     /// Clang diagnosed an error.
     ClangDiagnostic(String),
+    /// Code generation reported an error.
+    Codegen(CodegenError),
 }
 
 impl std::fmt::Display for BindgenError {
@@ -2399,6 +2435,9 @@
             BindgenError::ClangDiagnostic(message) => {
                 write!(f, "clang diagnosed error: {}", message)
             }
+            BindgenError::Codegen(err) => {
+                write!(f, "codegen error: {}", err)
+            }
         }
     }
 }
@@ -2472,7 +2511,7 @@
     pub(crate) fn generate(
         mut options: BindgenOptions,
         input_unsaved_files: Vec<clang::UnsavedFile>,
-    ) -> GenerateResult {
+    ) -> Result<Bindings, BindgenError> {
         ensure_libclang_is_loaded();
 
         #[cfg(feature = "runtime")]
@@ -2593,21 +2632,17 @@
             let path = Path::new(h);
             if let Ok(md) = std::fs::metadata(path) {
                 if md.is_dir() {
-                    return GenerateResult::Err(BindgenError::FolderAsHeader(
-                        path.into(),
-                    ));
+                    return Err(BindgenError::FolderAsHeader(path.into()));
                 }
                 if !can_read(&md.permissions()) {
-                    return GenerateResult::Err(
-                        BindgenError::InsufficientPermissions(path.into()),
-                    );
+                    return Err(BindgenError::InsufficientPermissions(
+                        path.into(),
+                    ));
                 }
                 let h = h.clone();
                 options.clang_args.push(h);
             } else {
-                return GenerateResult::Err(BindgenError::NotExist(
-                    path.into(),
-                ));
+                return Err(BindgenError::NotExist(path.into()));
             }
         }
 
@@ -2635,18 +2670,17 @@
 
         {
             let _t = time::Timer::new("parse").with_output(time_phases);
-            if let Err(err) = parse(&mut context) {
-                return GenerateResult::Err(err);
-            }
+            parse(&mut context)?;
         }
 
-        let (module, options, warnings) = codegen::codegen(context);
+        let (module, options, warnings) =
+            codegen::codegen(context).map_err(BindgenError::Codegen)?;
 
-        GenerateResult::Ok(Box::new(Bindings {
+        Ok(Bindings {
             options,
             warnings,
             module,
-        }))
+        })
     }
 
     /// Write these bindings as source text to a file.