diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 3f096b51a0..aefbace829 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -2022,6 +2022,27 @@ impl<'c> Translation<'c> { // If this function is just a regular inline if is_inline && !attrs.contains(&c_ast::Attribute::AlwaysInline) { mk_ = mk_.single_attr("inline"); + + // * In C99, a function defined inline will never, and a function defined extern + // inline will always, emit an externally visible function. + // * If a non-static function is declared inline, then it must be defined in the + // same translation unit. The inline definition that does not use extern is + // not externally visible and does not prevent other translation units from + // defining the same function. This makes the inline keyword an alternative to + // static for defining functions inside header files, which may be included in + // multiple translation units of the same program. + // * always_inline implies inline - + // https://gcc.gnu.org/ml/gcc-help/2007-01/msg00051.html + // even if the `inline` keyword isn't present + // * gnu_inline instead applies gnu89 rules. extern inline will not emit an + // externally visible function. + if is_global && is_extern && !attrs.contains(&c_ast::Attribute::GnuInline) { + self.use_feature("linkage"); + // ensures that public inlined rust function can be used in other modules + mk_ = mk_.single_attr("linkage = \"external\""); + } + // NOTE: it does not seem necessary to have an else branch here that + // specifies internal linkage in all other cases due to name mangling by rustc. } Ok(ConvertedDecl::Item( diff --git a/scripts/test_translator.py b/scripts/test_translator.py index 3b361c429e..b9b3e0a003 100755 --- a/scripts/test_translator.py +++ b/scripts/test_translator.py @@ -343,7 +343,16 @@ def run(self) -> List[TestOutcome]: self.generated_files["c_obj"].extend(static_library.obj_files) rust_file_builder = RustFileBuilder() - rust_file_builder.add_features(["libc", "extern_types", "simd_ffi", "stdsimd", "const_transmute", "nll", "custom_attribute"]) + rust_file_builder.add_features([ + "libc", + "extern_types", + "simd_ffi", + "stdsimd", + "const_transmute", + "nll", + "custom_attribute", + "linkage", + ]) # .c -> .rs for c_file in self.c_files: diff --git a/tests/items/src/test_fn_attrs.rs b/tests/items/src/test_fn_attrs.rs index 412c8f1849..52cdc72550 100644 --- a/tests/items/src/test_fn_attrs.rs +++ b/tests/items/src/test_fn_attrs.rs @@ -41,7 +41,7 @@ pub fn test_fn_attrs() { // extern void inline __attribute__((always_inline)) always_inline_extern(void) {} // extern void inline __attribute__((__gnu_inline__)) gnu_inline_extern(void) {} // extern void inline __attribute__((gnu_inline, always_inline)) always_inline_gnu_inline_extern(void) {} - assert!(src.contains("#[inline]\npub unsafe extern \"C\" fn rust_inline_extern")); + assert!(src.contains("#[inline]\n#[linkage = \"external\"]\npub unsafe extern \"C\" fn rust_inline_extern")); assert!(src.contains("#[inline(always)]\npub unsafe extern \"C\" fn rust_always_inline_extern")); assert!(src.contains("#[inline]\nunsafe extern \"C\" fn rust_gnu_inline_extern")); assert!(src.contains("#[inline(always)]\nunsafe extern \"C\" fn rust_always_inline_gnu_inline_extern"));