Add rs_std::SliceRef type

This is needed by Crubit for translating between C++ spans and Rust's slices.

PiperOrigin-RevId: 661409789
Change-Id: I30284a2588c2486892e712f507f59176da3b2959
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index 3b24687..b23e2bc 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -31,7 +31,9 @@
 use rustc_middle::ty::{self, Ty, TyCtxt}; // See <internal link>/ty.html#import-conventions
 use rustc_span::def_id::{DefId, LocalDefId, LOCAL_CRATE};
 use rustc_span::symbol::{kw, sym, Symbol};
-use rustc_target::abi::{Abi, FieldsShape, Integer, Layout, Primitive, Scalar};
+use rustc_target::abi::{
+    Abi, AddressSpace, FieldsShape, Integer, Layout, Pointer, Primitive, Scalar,
+};
 use rustc_target::spec::PanicStrategy;
 use rustc_trait_selection::infer::InferCtxtExt;
 use rustc_type_ir::RegionKind;
@@ -494,6 +496,28 @@
     Ok(CcSnippet { prereqs, tokens: quote! { #tokens #const_qualifier #pointer_sigil } })
 }
 
+/// Checks that `ty` has the same ABI as `rs_std::SliceRef`.
+fn check_slice_layout<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) {
+    // Check the assumption from `rust_builtin_type_abi_assumptions.md` that Rust's
+    // slice has the same ABI as `rs_std::SliceRef`.
+    let layout = tcx
+        .layout_of(ty::ParamEnv::empty().and(ty))
+        .expect("`layout_of` is expected to succeed for `{ty}` type")
+        .layout;
+    assert_eq!(8, layout.align().abi.bytes());
+    assert_eq!(16, layout.size().bytes());
+    assert!(matches!(
+        layout.abi(),
+        Abi::ScalarPair(
+            Scalar::Initialized { value: Pointer(AddressSpace(_)), .. },
+            Scalar::Initialized {
+                value: Primitive::Int(Integer::I64, /* signedness = */ false),
+                ..
+            }
+        )
+    ));
+}
+
 /// Formats `ty` into a `CcSnippet` that represents how the type should be
 /// spelled in a C++ declaration of a function parameter or field.
 fn format_ty_for_cc<'tcx>(
@@ -647,6 +671,9 @@
         }
 
         ty::TyKind::RawPtr(pointee_mid, mutbl) => {
+            if let ty::TyKind::Slice(_) = pointee_mid.kind() {
+                check_slice_layout(db.tcx(), ty.mid());
+            }
             let mut pointee_hir = None;
             if let Some(hir) = ty.hir(db) {
                 if let rustc_hir::TyKind::Ptr(mut_p) = hir.kind {
@@ -660,6 +687,9 @@
         }
 
         ty::TyKind::Ref(region, referent_mid, mutability) => {
+            if let ty::TyKind::Slice(_) = referent_mid.kind() {
+                check_slice_layout(db.tcx(), ty.mid());
+            }
             let mut referent_hir = None;
             if let Some(hir) = ty.hir(db) {
                 if let rustc_hir::TyKind::Ref(_, mut_p, ..) = &hir.kind {
@@ -7673,6 +7703,8 @@
                 "The following Rust type is not supported yet: [i32; 42]",
             ),
             (
+                // Check that the failure for slices is about not being supported and not failed
+                // asserts about ABI and layout.
                 "&'static [i32]", // TyKind::Slice (nested underneath TyKind::Ref)
                 "Failed to format the referent of the reference type `&'static [i32]`: \
                  The following Rust type is not supported yet: [i32]",
diff --git a/docs/overview/rust_builtin_type_abi_assumptions.md b/docs/overview/rust_builtin_type_abi_assumptions.md
index 01b08d6..a6c94d6 100644
--- a/docs/overview/rust_builtin_type_abi_assumptions.md
+++ b/docs/overview/rust_builtin_type_abi_assumptions.md
@@ -32,7 +32,7 @@
 `crubit/support/rs_std/rs_char.h`).
 
 The assumptions are verified by assertions that verify the properties of the
-target achitecture when `cc_bindings_from_rs` runs (`layout.align()`,
+target architecture when `cc_bindings_from_rs` runs (`layout.align()`,
 `layout.size()`, and `layout.abi()` assertions in `format_ty_for_cc` in
 `cc_bindings_from_rs/bindings.rs`). Similar assertions are verified on C++ side
 in `support/rs_std/rs_char_test.cc`. These assertions seem unlikely to fail, but
@@ -51,10 +51,14 @@
 
 Rust does *not* document the ABI of slice references (i.e. if the pointer comes
 before or after the length in memory). `cc_bindings_from_rs` assumes that `&[T]`
-has the same ABI as (future) `rs_std::slice<T>` - a C++ struct with 2 fields: a
-`T*` pointer, and the `size_t` number of slice elements. TODO: Add runtime
-assertions to `bindings.rs` to further verify these assumptions. TODO: Specify a
-plan of action when the assertions fail.
+has the same layout as `rs_std::slice<T>` - a C++ struct with 2 fields: a `T*`
+pointer, and the `size_t` number of slice elements. `check_slice_layout` inside
+`crubit/cc_bindings_from_rs/bindings.rs` verifies the assumptions on the Rust
+slice each time a reference or a pointer to a slice is processed by
+`format_ty_for_cc`. Similar assertions are verified on C++ side in
+`support/rs_std/slice_ref_test.cc`. The response to the unlikely failure of
+these assertions can be changing the types from which `rs_std::SliceRef` is
+built to match the Rust side again.
 
 `cc_bindings_from_rs` does *not* assume that `&[T]` and `rs_std::slice<T>` have
 the same ABI as
diff --git a/support/rs_std/BUILD b/support/rs_std/BUILD
index 6dd2497..5bc0537 100644
--- a/support/rs_std/BUILD
+++ b/support/rs_std/BUILD
@@ -32,3 +32,35 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_library(
+    name = "slice_ref",
+    hdrs = ["slice_ref.h"],
+    # Enable bidirectional bindings (via crubit_internal_rust_type).
+    aspect_hints = ["//features:experimental"],
+    visibility = [
+        "//visibility:public",
+    ],
+
+    # Try to avoid unnecessary dependencies, so that Crubit users have to pull in as little as
+    # possible. Using mature Abseil APIs seems okay - we should be able to assume that Crubit users
+    # have a version of Abseil that is relatively recent (although we can't rely on an exact version
+    # and/or exact absl/base/options.h).
+    deps = [
+        "//support/internal:bindings_support",
+        "@abseil-cpp//absl/base:core_headers",
+        "@abseil-cpp//absl/types:span",
+    ],
+)
+
+crubit_cc_test(
+    name = "slice_ref_test",
+    srcs = ["slice_ref_test.cc"],
+    deps = [
+        ":slice_ref",
+        "//testing/base/public:gmock_no_google3",
+        "//testing/fuzzing:fuzztest_gunit_main_no_google3",
+        "//testing/fuzzing:fuzztest_no_google3",
+        "@abseil-cpp//absl/types:span",
+    ],
+)
diff --git a/support/rs_std/slice_ref.h b/support/rs_std/slice_ref.h
new file mode 100644
index 0000000..47cfe1b
--- /dev/null
+++ b/support/rs_std/slice_ref.h
@@ -0,0 +1,71 @@
+// 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_SUPPORT_RS_STD_SLICEREF_H_
+#define THIRD_PARTY_CRUBIT_SUPPORT_RS_STD_SLICEREF_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <span>  // NOLINT(build/c++20); <internal link>
+
+#include "absl/base/attributes.h"
+#include "absl/types/span.h"
+#include "support/internal/attribute_macros.h"
+
+namespace rs_std {
+
+// `rs_std::SliceRef` is a C++ representation of a pointer or reference to a
+// Rust slice. `SliceRef<int const>` is like a `&[c_int]` or `*const [c_int]`,
+// while `SliceRef<int>` is like a `&[c_int]` or `*mut [c_int]`. `SliceRef` is
+// trivially destructible, copyable, and moveable.
+// `rust_builtin_type_abi_assumptions.md` documents the ABI compatibility of
+// these types.
+template <typename T>
+class CRUBIT_INTERNAL_RUST_TYPE("&[]")
+    ABSL_ATTRIBUTE_TRIVIAL_ABI SliceRef final {
+ public:
+  // Creates a default `SliceRef` - one that represents an empty slice.
+  // To mirror slices in Rust, the data pointer is not null.
+  constexpr SliceRef() noexcept : ptr_(alignof(T)), size_(0) {}
+
+  // This constructor cannot be constexpr because it calls `reinterpret_cast`.
+  // The `reinterpret_cast`, in turn, is needed if we want to keep `ptr_` an
+  // `uintptr_t` instead of a `T*`, which we do to avoid storing dangling
+  // pointers. The `reinterpret_cast` preserves both invariants required for
+  // `ptr_`, because the resulting value comes from a valid, non-null pointer.
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  SliceRef(absl::Span<T> span) noexcept
+      : ptr_(span.empty() ? alignof(T)
+                          : reinterpret_cast<uintptr_t>(span.data())),
+        size_(span.size()) {}
+
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr operator std::span<T>() const {
+    return std::span<T>(size_ > 0 ? static_cast<T*>(ptr_) : nullptr, size_);
+  }
+
+  constexpr SliceRef(const SliceRef&) = default;
+  constexpr SliceRef& operator=(const SliceRef&) = default;
+  constexpr SliceRef(SliceRef&&) = default;
+  constexpr SliceRef& operator=(SliceRef&&) = default;
+  ~SliceRef() = default;
+
+  // The `reinterpret_cast` is safe thanks to invariant (2) (see the definition
+  // of `ptr_`).
+  T* data() const { return size_ > 0 ? reinterpret_cast<T*>(ptr_) : nullptr; }
+  size_t size() const { return size_; }
+
+  absl::Span<T> to_span() const { return absl::Span<T>(data(), size()); }
+
+ private:
+  // Stick to the following invariants when changing the data member values:
+  // (1) `ptr_` is never 0 (to mirror slices in Rust).
+  // (2) if `size_ > 0` then `reinterpret_cast<T*>(ptr_)` is a valid pointer.
+  uintptr_t ptr_;
+  size_t size_;
+};
+
+}  // namespace rs_std
+
+#endif  // THIRD_PARTY_CRUBIT_SUPPORT_RS_STD_SLICEREF_H_
diff --git a/support/rs_std/slice_ref_test.cc b/support/rs_std/slice_ref_test.cc
new file mode 100644
index 0000000..dbdcd5e
--- /dev/null
+++ b/support/rs_std/slice_ref_test.cc
@@ -0,0 +1,145 @@
+// 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
+
+#include "support/rs_std/slice_ref.h"
+
+#include <stdint.h>
+
+#include <array>
+#include <bit>
+#include <concepts>
+#include <cstddef>
+#include <type_traits>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "testing/fuzzing/fuzztest.h"
+#include "absl/types/span.h"
+
+namespace {
+using ::testing::IsNull;
+using ::testing::Not;
+
+// Check that `rs_std::SliceRef` is trivially destructible, copyable, and
+// moveable. It is not trivial because it has a non-trivial constructor, and
+// that's because its default constructor needs to be user-defined to make sure
+// that `ptr_` is never null. The latter is needed so that interpreting the data
+// as a slice in Rust won't result in a (in Rust) forbidden null pointer.
+static_assert(std::is_nothrow_constructible_v<rs_std::SliceRef<const uint8_t>>);
+static_assert(
+    std::is_trivially_destructible_v<rs_std::SliceRef<const uint8_t>>);
+static_assert(std::is_trivially_copyable_v<rs_std::SliceRef<const uint8_t>>);
+static_assert(
+    std::is_trivially_copy_constructible_v<rs_std::SliceRef<const uint8_t>>);
+static_assert(
+    std::is_trivially_copy_assignable_v<rs_std::SliceRef<const uint8_t>>);
+static_assert(
+    std::is_trivially_move_constructible_v<rs_std::SliceRef<const uint8_t>>);
+static_assert(
+    std::is_trivially_move_assignable_v<rs_std::SliceRef<const uint8_t>>);
+// Note: `rs_std::SliceRef` is not trivially constructible because it's default
+// constructor ensures that the data pointer is not null.
+
+// `SliceRef` does on purpose not have `operator==`, because <internal link>
+// did not specify that `SliceRef` should be comparable.
+static_assert(!std::equality_comparable<rs_std::SliceRef<int>>);
+
+// Verify that the layout of `rs_std::SliceRef` is as expected and described in
+// `rust_builtin_type_abi_assumptions.md`. Sample a few wrapped types to make
+// sure that the layout is the same for all of them.
+// `sizeof(uintptr_t)` is guaranteed to be the size of `usize` in Rust.
+constexpr size_t kSliceRefSize = sizeof(uintptr_t) * 2;
+constexpr size_t kSliceRefAlign = alignof(uintptr_t);
+static_assert(sizeof(rs_std::SliceRef<const uint8_t>) == kSliceRefSize);
+static_assert(alignof(rs_std::SliceRef<const uint8_t>) == kSliceRefAlign);
+static_assert(std::is_standard_layout_v<rs_std::SliceRef<const uint8_t>>);
+static_assert(sizeof(rs_std::SliceRef<char>) == kSliceRefSize);
+static_assert(alignof(rs_std::SliceRef<char>) == kSliceRefAlign);
+static_assert(std::is_standard_layout_v<rs_std::SliceRef<char>>);
+static_assert(sizeof(rs_std::SliceRef<int64_t>) == kSliceRefSize);
+static_assert(alignof(rs_std::SliceRef<int64_t>) == kSliceRefAlign);
+static_assert(std::is_standard_layout_v<rs_std::SliceRef<int64_t>>);
+
+// Slice assumes that the Rust slice layout is first the pointer and then the
+// size. This does not appear to be standardised, so instead there are runtime
+// checks for this in `format_ty_for_cc` in `cc_bindings_from_rs/bindings.rs`.
+static_assert(sizeof(rs_std::SliceRef<const uint8_t>) ==
+              sizeof(const uint8_t*) + sizeof(size_t));
+static_assert(alignof(rs_std::SliceRef<const uint8_t>) ==
+              alignof(const uint8_t*));
+static_assert(std::is_standard_layout_v<rs_std::SliceRef<const uint8_t>>);
+
+TEST(SliceTest, Comparison) {
+  static constexpr std::array<uint8_t, 5> kArr = {1, 2, 3, 4, 5};
+  static constexpr std::array<uint8_t, 5> kArrCopy = {1, 2, 3, 4, 5};
+
+  const rs_std::SliceRef<const uint8_t> s1(kArr);
+  const rs_std::SliceRef<const uint8_t> s1_copy = s1;
+  const rs_std::SliceRef<const uint8_t> s2(kArrCopy);
+
+  EXPECT_EQ(s1.to_span(), s1.to_span());
+  EXPECT_EQ(s1.to_span(), s1_copy.to_span());
+  EXPECT_EQ(s1.to_span(), s2.to_span());
+  const rs_std::SliceRef<const uint8_t> s1_prefix(
+      absl::MakeSpan(kArr.data(), kArr.size() - 1));
+  const rs_std::SliceRef<const uint8_t> s1_suffix(
+      absl::MakeSpan(kArr.data() + 1, kArr.size() - 1));
+  const rs_std::SliceRef<const uint8_t> s1_infix(
+      absl::MakeSpan(kArr.data() + 1, kArr.size() - 2));
+
+  EXPECT_NE(s1.to_span(), s1_prefix.to_span());
+  EXPECT_NE(s1.to_span(), s1_suffix.to_span());
+  EXPECT_NE(s1.to_span(), s1_infix.to_span());
+}
+
+TEST(SliceTest, FromAndTo) {
+  static constexpr std::array<uint8_t, 5> kArr = {1, 2, 3, 4, 5};
+  const rs_std::SliceRef<const uint8_t> s(kArr);
+  EXPECT_EQ(absl::Span<const uint8_t>(kArr), s.to_span());
+}
+
+// To test the value of `data_`, the test includes the knowledge of
+// `SliceRef`'s layout to extract it via a peeker struct.
+struct SliceRefFields {
+  const void* ptr;
+  size_t size;
+};
+
+// This test checks that the assumption of Peeker having the same layout as
+// `SliceRef` is correct.
+TEST(SliceTest, Layout) {
+  static constexpr std::array<uint8_t, 5> kArr = {2, 3, 5};
+  const rs_std::SliceRef<const uint8_t> s(kArr);
+  const auto fields = std::bit_cast<SliceRefFields>(s);
+  EXPECT_EQ(fields.ptr, kArr.data());
+  EXPECT_EQ(fields.size, kArr.size());
+}
+
+TEST(SliceTest, Empty) {
+  static constexpr uint8_t kEmpty[] = {};
+  const rs_std::SliceRef<const uint8_t> empty(absl::MakeSpan(kEmpty, 0));
+  static constexpr rs_std::SliceRef<const uint8_t> default_constructed;
+  EXPECT_EQ(empty.to_span(), default_constructed.to_span());
+
+  const auto fields = std::bit_cast<SliceRefFields>(empty);
+  EXPECT_THAT(fields.ptr, Not(IsNull()));
+  EXPECT_EQ(fields.size, 0);
+
+  // While `empty.data_` is not null, `data()` converts it to null for
+  // compatibility with `std::span`.
+  EXPECT_THAT(empty.data(), IsNull());
+  EXPECT_EQ(empty.size(), 0);
+}
+
+void Fuzzer(std::vector<uint8_t> data) {
+  const rs_std::SliceRef<const uint8_t> s(data);
+  EXPECT_EQ(absl::Span<const uint8_t>(data), s.to_span());
+  const std::vector<uint8_t> data_copy(s.data(), s.data() + s.size());
+  EXPECT_EQ(data, data_copy);
+}
+
+FUZZ_TEST(SliceFuzzTest, Fuzzer);
+
+}  // namespace