Pass C ABI compatible type by value so that we don't need to assume copyability.

PiperOrigin-RevId: 671863119
Change-Id: I524d89bcc32dacf9f2abc4f3dc3b8af8c577cb06
diff --git a/rs_bindings_from_cc/generate_bindings/generate_func.rs b/rs_bindings_from_cc/generate_bindings/generate_func.rs
index 39ce478..df443b3 100644
--- a/rs_bindings_from_cc/generate_bindings/generate_func.rs
+++ b/rs_bindings_from_cc/generate_bindings/generate_func.rs
@@ -1928,11 +1928,14 @@
                 Some("&") => Ok(quote! { * #ident }),
                 Some("&&") => Ok(quote! { std::move(* #ident) }),
                 _ => {
+                    let rs_type_kind = db.rs_type_kind(p.type_.rs_type.clone())?;
                     // non-Unpin types are wrapped by a pointer in the thunk.
-                    if !db.rs_type_kind(p.type_.rs_type.clone())?.is_c_abi_compatible_by_value() {
+                    if !rs_type_kind.is_c_abi_compatible_by_value() {
                         Ok(quote! { std::move(* #ident) })
-                    } else {
+                    } else if rs_type_kind.is_primitive() || rs_type_kind.referent().is_some() {
                         Ok(quote! { #ident })
+                    } else {
+                        Ok(quote! { std::move( #ident) })
                     }
                 }
             }
diff --git a/rs_bindings_from_cc/generate_bindings/lib.rs b/rs_bindings_from_cc/generate_bindings/lib.rs
index 84da6b4..97b6207 100644
--- a/rs_bindings_from_cc/generate_bindings/lib.rs
+++ b/rs_bindings_from_cc/generate_bindings/lib.rs
@@ -2276,6 +2276,27 @@
     }
 
     #[gtest]
+    fn test_c_abi_compatible_type_by_value_with_move() -> Result<()> {
+        let ir = ir_from_cc(
+            r#"
+                typedef int MyTypedefDecl;
+
+                inline void f(MyTypedefDecl a, void* b, int c) {}
+            "#,
+        )?;
+        let BindingsTokens { rs_api_impl, .. } = generate_bindings_tokens(ir)?;
+        assert_cc_matches!(
+            rs_api_impl,
+            quote! {
+                extern "C" void __rust_thunk___Z1fiPvi(MyTypedefDecl a, void* b, int c) {
+                    f(std::move(a), b, c);
+                }
+            }
+        );
+        Ok(())
+    }
+
+    #[gtest]
     fn test_type_alias() -> Result<()> {
         let ir = ir_from_cc(
             r#"
@@ -2311,7 +2332,7 @@
         assert_cc_matches!(
             rs_api_impl,
             quote! {
-                extern "C" void __rust_thunk___Z1fi(MyTypedefDecl t) { f(t); }
+                extern "C" void __rust_thunk___Z1fi(MyTypedefDecl t) { f(std::move(t)); }
             }
         );
         Ok(())
diff --git a/rs_bindings_from_cc/generate_bindings/rs_snippet.rs b/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
index d961486..a04cab1 100644
--- a/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
+++ b/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
@@ -441,6 +441,10 @@
         matches!(self, RsTypeKind::BridgeType { .. })
     }
 
+    pub fn is_primitive(&self) -> bool {
+        matches!(self, RsTypeKind::Primitive { .. })
+    }
+
     /// Returns the features required to use this type which are not already
     /// enabled.
     ///
diff --git a/rs_bindings_from_cc/test/golden/c_abi_compatible_type.h b/rs_bindings_from_cc/test/golden/c_abi_compatible_type.h
new file mode 100644
index 0000000..93cf7c3
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/c_abi_compatible_type.h
@@ -0,0 +1,22 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_C_ABI_COMPATIBLE_TYPE_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_C_ABI_COMPATIBLE_TYPE_H_
+
+struct [[clang::annotate("crubit_internal_rust_type", "i8")]] [[clang::annotate(
+    "crubit_internal_same_abi")]] MyI8 {
+  unsigned char field;
+};
+
+struct X {
+  int a;
+};
+
+MyI8 ffi(MyI8 a, X b);
+
+typedef int MyTypedefDecl;
+
+inline void f(MyTypedefDecl a, void* b, int c) {}
+#endif  // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_C_ABI_COMPATIBLE_TYPE_H_
diff --git a/rs_bindings_from_cc/test/golden/c_abi_compatible_type_rs_api.rs b/rs_bindings_from_cc/test/golden/c_abi_compatible_type_rs_api.rs
new file mode 100644
index 0000000..e393e1b
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/c_abi_compatible_type_rs_api.rs
@@ -0,0 +1,81 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Automatically @generated Rust bindings for the following C++ target:
+// //rs_bindings_from_cc/test/golden:c_abi_compatible_type_cc
+// Features: experimental, supported
+
+#![rustfmt::skip]
+#![feature(custom_inner_attributes, negative_impls, register_tool)]
+#![allow(stable_features)]
+#![no_std]
+#![register_tool(__crubit)]
+#![allow(improper_ctypes)]
+#![allow(nonstandard_style)]
+#![allow(dead_code)]
+#![deny(warnings)]
+
+// Type bindings for struct MyI8 suppressed due to being mapped to an existing Rust type (i8)
+
+#[derive(Clone, Copy)]
+#[repr(C)]
+#[__crubit::annotate(cpp_type = "X")]
+pub struct X {
+    pub a: ::core::ffi::c_int,
+}
+impl !Send for X {}
+impl !Sync for X {}
+forward_declare::unsafe_define!(forward_declare::symbol!("X"), crate::X);
+
+// Error while generating bindings for item 'X::X':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// Error while generating bindings for item 'X::X':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// Error while generating bindings for item 'X::X':
+// Parameter #0 is not supported: Unsupported type 'X &&': Unsupported type: && without lifetime
+
+// Error while generating bindings for item 'X::operator=':
+// `self` has no lifetime. Use lifetime annotations or `#pragma clang lifetime_elision` to create bindings for this function.
+
+// Error while generating bindings for item 'X::operator=':
+// Parameter #0 is not supported: Unsupported type 'X &&': Unsupported type: && without lifetime
+
+#[inline(always)]
+pub fn ffi(a: i8, mut b: crate::X) -> i8 {
+    unsafe { crate::detail::__rust_thunk___Z3ffi4MyI81X(a, &mut b) }
+}
+
+pub type MyTypedefDecl = ::core::ffi::c_int;
+
+#[inline(always)]
+pub unsafe fn f(a: crate::MyTypedefDecl, b: *mut ::core::ffi::c_void, c: ::core::ffi::c_int) {
+    crate::detail::__rust_thunk___Z1fiPvi(a, b, c)
+}
+
+mod detail {
+    #[allow(unused_imports)]
+    use super::*;
+    extern "C" {
+        pub(crate) fn __rust_thunk___Z3ffi4MyI81X(a: i8, b: &mut crate::X) -> i8;
+        pub(crate) fn __rust_thunk___Z1fiPvi(
+            a: crate::MyTypedefDecl,
+            b: *mut ::core::ffi::c_void,
+            c: ::core::ffi::c_int,
+        );
+    }
+}
+
+const _: () = {
+    assert!(::core::mem::size_of::<i8>() == 1);
+    assert!(::core::mem::align_of::<i8>() == 1);
+
+    assert!(::core::mem::size_of::<crate::X>() == 4);
+    assert!(::core::mem::align_of::<crate::X>() == 4);
+    static_assertions::assert_impl_all!(crate::X: Clone);
+    static_assertions::assert_impl_all!(crate::X: Copy);
+    static_assertions::assert_not_impl_any!(crate::X: Drop);
+    assert!(::core::mem::offset_of!(crate::X, a) == 0);
+};
diff --git a/rs_bindings_from_cc/test/golden/c_abi_compatible_type_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/c_abi_compatible_type_rs_api_impl.cc
new file mode 100644
index 0000000..2f24565
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/c_abi_compatible_type_rs_api_impl.cc
@@ -0,0 +1,34 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Automatically @generated Rust bindings for the following C++ target:
+// //rs_bindings_from_cc/test/golden:c_abi_compatible_type_cc
+// Features: experimental, supported
+
+#include "support/internal/cxx20_backports.h"
+#include "support/internal/offsetof.h"
+#include "support/internal/sizeof.h"
+
+#include <cstddef>
+#include <memory>
+
+// Public headers of the C++ library being wrapped.
+#include "rs_bindings_from_cc/test/golden/c_abi_compatible_type.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wthread-safety-analysis"
+
+static_assert(CRUBIT_SIZEOF(struct X) == 4);
+static_assert(alignof(struct X) == 4);
+static_assert(CRUBIT_OFFSET_OF(a, struct X) == 0);
+
+extern "C" struct MyI8 __rust_thunk___Z3ffi4MyI81X(struct MyI8 a, struct X* b) {
+  return ffi(std::move(a), std::move(*b));
+}
+
+extern "C" void __rust_thunk___Z1fiPvi(MyTypedefDecl a, void* b, int c) {
+  f(std::move(a), b, c);
+}
+
+#pragma clang diagnostic pop