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, + } + }; +}