Create a simple FFI crate

This CL documents how to call C++ functions from rust, and creates a
simple example.

Bug: 528225104
Change-Id: If5d8681aa05ac60887dd6ff9002187106a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23420
Commit-Queue: Matt Stark <msta@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/build/gen.py b/build/gen.py
index 675402b..392249f 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -722,6 +722,7 @@
         'src/gn/err.cc',
         'src/gn/escape.cc',
         'src/gn/exec_process.cc',
+        'src/gn/ffi/label.cc',
         'src/gn/filesystem_utils.cc',
         'src/gn/file_writer.cc',
         'src/gn/frameworks_utils.cc',
diff --git a/src/gn/ffi/label.cc b/src/gn/ffi/label.cc
new file mode 100644
index 0000000..c0ddbdb
--- /dev/null
+++ b/src/gn/ffi/label.cc
@@ -0,0 +1,18 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/label.h"
+#include "cxx.h"
+
+extern "C" {
+
+rust::Str GetLabelDir(const Label& label) {
+  return label.dir().value();
+}
+
+rust::Str GetLabelName(const Label& label) {
+  return label.name();
+}
+
+}  // extern "C"
diff --git a/src/gn/starlark/Cargo.lock b/src/gn/starlark/Cargo.lock
index 9e55395..512a232 100644
--- a/src/gn/starlark/Cargo.lock
+++ b/src/gn/starlark/Cargo.lock
@@ -633,6 +633,17 @@
 ]
 
 [[package]]
+name = "ffi"
+version = "0.1.0"
+dependencies = [
+ "cxx",
+ "starlark",
+ "starlark_derive",
+ "thiserror",
+ "types",
+]
+
+[[package]]
 name = "find-msvc-tools"
 version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src/gn/starlark/Cargo.toml b/src/gn/starlark/Cargo.toml
index d216093..f98fdc8 100644
--- a/src/gn/starlark/Cargo.toml
+++ b/src/gn/starlark/Cargo.toml
@@ -13,6 +13,7 @@
 [workspace]
 members = [
     ".",
+    "crates/ffi",
     "crates/types",
 ]
 
diff --git a/src/gn/starlark/crates/ffi/Cargo.toml b/src/gn/starlark/crates/ffi/Cargo.toml
new file mode 100644
index 0000000..11ab908
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "ffi"
+version = "0.1.0"
+edition = "2021"
+
+[lints]
+workspace = true
+
+[lib]
+test = false
+doctest = false
+
+[dependencies]
+starlark = { workspace = true }
+starlark_derive = { workspace = true }
+cxx = { workspace = true }
+types = { path = "../types" }
+thiserror = { workspace = true }
diff --git a/src/gn/starlark/crates/ffi/build.rs b/src/gn/starlark/crates/ffi/build.rs
new file mode 100644
index 0000000..163b2c0
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/build.rs
@@ -0,0 +1,18 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+fn main() {
+    // When running with ninja, it should set NINJA_OUT_DIR.
+    // If running directly with cargo, we know nothing about the output directory,
+    // so we fall back to assuming the default one.
+    let out_dir = if let Ok(out_dir) = std::env::var("NINJA_OUT_DIR") {
+        std::path::PathBuf::from(out_dir)
+    } else {
+        let manifest_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
+        manifest_dir.join("../../../../../out")
+    };
+    println!("cargo:rustc-link-search=native={}", out_dir.display());
+    println!("cargo:rustc-link-lib=static=gn_lib");
+    println!("cargo:rustc-link-lib=static=base");
+}
diff --git a/src/gn/starlark/crates/ffi/src/label.rs b/src/gn/starlark/crates/ffi/src/label.rs
new file mode 100644
index 0000000..15a85ad
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/src/label.rs
@@ -0,0 +1,35 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::declare_opaque_type;
+use types::{LabelRef, PackageRef};
+
+declare_opaque_type!(pub Label);
+
+impl Label {
+    /// Returns the directory part of the label (the package path).
+    pub fn package(&self) -> &PackageRef {
+        extern "C" {
+            fn GetLabelDir(label: &Label) -> &str;
+        }
+        // Safety: GetLabelDir is guarunteed to return a string that starts with "//".
+        unsafe { PackageRef::new_unchecked(GetLabelDir(self)) }
+    }
+
+    /// Returns the name part of the label.
+    pub fn name(&self) -> &str {
+        extern "C" {
+            fn GetLabelName(label: &Label) -> &str;
+        }
+        unsafe { GetLabelName(self) }
+    }
+
+    /// Returns a `LabelRef` referencing the directory and name of this label.
+    pub fn as_ref<'a>(&'a self) -> LabelRef<'a> {
+        LabelRef {
+            package: self.package(),
+            name: self.name(),
+        }
+    }
+}
diff --git a/src/gn/starlark/crates/ffi/src/lib.rs b/src/gn/starlark/crates/ffi/src/lib.rs
new file mode 100644
index 0000000..15f108e
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/src/lib.rs
@@ -0,0 +1,65 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Low-level FFI bindings and opaque types for interoperating between the C++ GN
+//! codebase and the Rust Starlark interpreter crates.
+//!
+//! **Type Equivalence**
+//!
+//! WARNING: We have no way of checking that the C++ type signatures match the
+//! rust type signatures. This means that if you change the signature of a
+//! function on one side, you *might* get a link error, or might just get UB.
+//!
+//! As long as C++ and rust see a type identically, we don't care how it works under the hood.
+//! * Most custom types will need to be passed by reference
+//!   * C++ &T is just a non-null pointer.
+//!   * Rust references are just non-null pointers
+//!   * Rust Option<&T> are pointers
+//! * If you define a struct in C++ and a #[repr(C)] one in rust, you can pass by value instead of by reference.
+//!   * cxx.h does this for some rust types in C++ (rust::*, eg. rust::Str = &str)
+//!   * cxx.h does this for some C++ types in rust (cxx::*, eg. cxx::CxxVector<T> = std::vector<T>)
+//!
+//! **Exposing rust functions to C++**
+//!
+//! Declare a function `#[no_mangle] pub extern "C"` in rust
+//! ```rust
+//! // foo.rs
+//! #[no_mangle]
+//! pub extern "C" fn my_function(foo: &Foo) -> &str {
+//!     foo.name()
+//! }
+//! ```
+//!
+//! Declare the C++ function for this in a header file.
+//! A clean C++ API has not yet been implemented.
+//!
+//! **Exposing C++ functions to rust**
+//!
+//! Define an `extern "C"` function in C++. Do not define a header file, as it
+//! should not be included from C++ code.
+//! ```cpp
+//! extern "C" rust::Str GetLabelName(const Label& label) {
+//!   return rust::Str(label.name());
+//! }
+//! ```
+//!
+//! Declare the equivalent extern "C" function in rust. Do so inside a function
+//! definition, as the canonical way to access this function *safely*.
+//! For example:
+//! ```rust
+//! declare_opaque_type!(Label);
+//! impl Label {
+//!     fn name(&self) -> &str {
+//!         extern "C" {
+//!             fn GetLabelName(label: &Label) -> &str;
+//!         }
+//!         unsafe { GetLabelName(self) }
+//!     }
+//! }
+//! ```
+
+pub mod label;
+pub mod opaque;
+
+pub use label::Label;
diff --git a/src/gn/starlark/crates/ffi/src/opaque.rs b/src/gn/starlark/crates/ffi/src/opaque.rs
new file mode 100644
index 0000000..310310d
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/src/opaque.rs
@@ -0,0 +1,27 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Declares a C++ type that is opaque to rust.
+///
+/// | C++ Type            | Rust Type                    |
+/// |---------------------|------------------------------|
+/// | `const OpaqueType&` | `&'a OpaqueType`             |
+/// | `OpaqueType&`       | `&'a mut OpaqueType`         |
+/// | `const OpaqueType*` | `Option<&'a OpaqueType>`     |
+/// | `OpaqueType*`       | `Option<&'a mut OpaqueType>` |
+/// | `OpaqueType`        | **DO NOT USE**               |
+#[macro_export]
+macro_rules! declare_opaque_type {
+    ($name:ident) => {
+        $crate::declare_opaque_type!(pub $name);
+    };
+    ($vis:vis $name:ident) => {
+        #[repr(C)]
+        $vis struct $name {
+            // Private member prevents external construction or instantiation by-value.
+            // Non-zero size (1 byte) prevents Rust from optimizing references as ZSTs.
+            _private: u8,
+        }
+    };
+}