Implement GnValue FFI and starlark/rust conversions Bug: 528225104 Change-Id: Ib0c5c1c764ad6bb40f3127ea8daa1d346a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23883 Reviewed-by: Richard Wang <richardwa@google.com> Reviewed-by: Takuto Ikuta <tikuta@google.com> Commit-Queue: Matt Stark <msta@google.com>
diff --git a/build/gen.py b/build/gen.py index e3f2c46..d947852 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -731,6 +731,8 @@ 'src/gn/escape.cc', 'src/gn/exec_process.cc', 'src/gn/ffi/bridge.cc', + 'src/gn/ffi/scope.cc', + 'src/gn/ffi/value.cc', 'src/gn/filesystem_utils.cc', 'src/gn/file_writer.cc', 'src/gn/frameworks_utils.cc',
diff --git a/src/gn/ffi/bridge.cc b/src/gn/ffi/bridge.cc index 1346937..e7eb883 100644 --- a/src/gn/ffi/bridge.cc +++ b/src/gn/ffi/bridge.cc
@@ -1,23 +1,32 @@ // This file is generated by src/gn/ffi/update_bridge.sh. Do not edit manually. // Source: src/gn/ffi/starlark/crates/ffi/src/bridge.rs +#include "gn/ffi/scope.h" #include "gn/ffi/test_with_scope.h" +#include "gn/ffi/value.h" #include "gn/label.h" #include "gn/output_file.h" #include "gn/scope.h" #include "gn/settings.h" #include "gn/source_dir.h" #include "gn/test_with_scope.h" +#include "gn/value.h" #include <array> +#include <cassert> #include <cstddef> #include <cstdint> +#include <iterator> #include <memory> #include <new> +#include <stdexcept> #include <string> #include <type_traits> #include <utility> #if __cplusplus >= 201703L #include <string_view> #endif +#if __cplusplus >= 202002L +#include <ranges> +#endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wmissing-declarations" @@ -30,12 +39,24 @@ inline namespace cxxbridge1 { // #include "rust/cxx.h" +#ifndef CXXBRIDGE1_PANIC +#define CXXBRIDGE1_PANIC +template <typename Exception> +void panic [[noreturn]](const char *msg); +#endif // CXXBRIDGE1_PANIC + namespace { template <typename T> class impl; } // namespace class String; +class Opaque; + +template <typename T> +::std::size_t size_of(); +template <typename T> +::std::size_t align_of(); #ifndef CXXBRIDGE1_RUST_STR #define CXXBRIDGE1_RUST_STR @@ -96,6 +117,319 @@ inline Str cxx_to_rust(const std::string &s) { return Str(s); } #endif // CXXBRIDGE1_RUST_STR +#ifndef CXXBRIDGE1_RUST_SLICE +#define CXXBRIDGE1_RUST_SLICE +namespace detail { +template <bool> +struct copy_assignable_if {}; + +template <> +struct copy_assignable_if<false> { + copy_assignable_if() noexcept = default; + copy_assignable_if(const copy_assignable_if &) noexcept = default; + copy_assignable_if &operator=(const copy_assignable_if &) & noexcept = delete; + copy_assignable_if &operator=(copy_assignable_if &&) & noexcept = default; +}; +} // namespace detail + +template <typename T> +class Slice final + : private detail::copy_assignable_if<std::is_const<T>::value> { +public: + using value_type = T; + + Slice() noexcept; + Slice(T *, std::size_t count) noexcept; + + template <typename C> + explicit Slice(C &c) : Slice(c.data(), c.size()) {} + + Slice &operator=(const Slice<T> &) & noexcept = default; + Slice &operator=(Slice<T> &&) & noexcept = default; + + T *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + T &operator[](std::size_t n) const noexcept; + T &at(std::size_t n) const; + T &front() const noexcept; + T &back() const noexcept; + + Slice(const Slice<T> &) noexcept = default; + ~Slice() noexcept = default; + + class iterator; + iterator begin() const noexcept; + iterator end() const noexcept; + + void swap(Slice &) noexcept; + +private: + class uninit; + Slice(uninit) noexcept; + friend impl<Slice>; + friend void sliceInit(void *, const void *, std::size_t) noexcept; + friend void *slicePtr(const void *) noexcept; + friend std::size_t sliceLen(const void *) noexcept; + + std::array<std::uintptr_t, 2> repr; +}; + +#ifdef __cpp_deduction_guides +template <typename C> +explicit Slice(C &c) + -> Slice<std::remove_reference_t<decltype(*std::declval<C>().data())>>; +#endif // __cpp_deduction_guides + +template <typename T> +class Slice<T>::iterator final { +public: +#if __cplusplus >= 202002L + using iterator_category = std::contiguous_iterator_tag; +#else + using iterator_category = std::random_access_iterator_tag; +#endif + using value_type = T; + using element_type = T; + using difference_type = std::ptrdiff_t; + using pointer = typename std::add_pointer<T>::type; + using reference = typename std::add_lvalue_reference<T>::type; + + reference operator*() const noexcept; + pointer operator->() const noexcept; + reference operator[](difference_type) const noexcept; + + iterator &operator++() noexcept; + iterator operator++(int) noexcept; + iterator &operator--() noexcept; + iterator operator--(int) noexcept; + + iterator &operator+=(difference_type) noexcept; + iterator &operator-=(difference_type) noexcept; + iterator operator+(difference_type) const noexcept; + friend inline iterator operator+(difference_type lhs, iterator rhs) noexcept { + return rhs + lhs; + } + iterator operator-(difference_type) const noexcept; + difference_type operator-(const iterator &) const noexcept; + + bool operator==(const iterator &) const noexcept; + bool operator!=(const iterator &) const noexcept; + bool operator<(const iterator &) const noexcept; + bool operator<=(const iterator &) const noexcept; + bool operator>(const iterator &) const noexcept; + bool operator>=(const iterator &) const noexcept; + +private: + friend class Slice; + void *pos; + std::size_t stride; +}; + +#if __cplusplus >= 202002L +static_assert(std::ranges::contiguous_range<rust::Slice<const uint8_t>>); +static_assert(std::contiguous_iterator<rust::Slice<const uint8_t>::iterator>); +#endif + +template <typename T> +Slice<T>::Slice() noexcept { + sliceInit(this, reinterpret_cast<void *>(align_of<T>()), 0); +} + +template <typename T> +Slice<T>::Slice(T *s, std::size_t count) noexcept { + assert(s != nullptr || count == 0); + sliceInit(this, + s == nullptr && count == 0 + ? reinterpret_cast<void *>(align_of<T>()) + : const_cast<typename std::remove_const<T>::type *>(s), + count); +} + +template <typename T> +T *Slice<T>::data() const noexcept { + return reinterpret_cast<T *>(slicePtr(this)); +} + +template <typename T> +std::size_t Slice<T>::size() const noexcept { + return sliceLen(this); +} + +template <typename T> +std::size_t Slice<T>::length() const noexcept { + return this->size(); +} + +template <typename T> +bool Slice<T>::empty() const noexcept { + return this->size() == 0; +} + +template <typename T> +T &Slice<T>::operator[](std::size_t n) const noexcept { + assert(n < this->size()); + auto ptr = static_cast<char *>(slicePtr(this)) + size_of<T>() * n; + return *reinterpret_cast<T *>(ptr); +} + +template <typename T> +T &Slice<T>::at(std::size_t n) const { + if (n >= this->size()) { + panic<std::out_of_range>("rust::Slice index out of range"); + } + return (*this)[n]; +} + +template <typename T> +T &Slice<T>::front() const noexcept { + assert(!this->empty()); + return (*this)[0]; +} + +template <typename T> +T &Slice<T>::back() const noexcept { + assert(!this->empty()); + return (*this)[this->size() - 1]; +} + +template <typename T> +typename Slice<T>::iterator::reference +Slice<T>::iterator::operator*() const noexcept { + return *static_cast<T *>(this->pos); +} + +template <typename T> +typename Slice<T>::iterator::pointer +Slice<T>::iterator::operator->() const noexcept { + return static_cast<T *>(this->pos); +} + +template <typename T> +typename Slice<T>::iterator::reference Slice<T>::iterator::operator[]( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto ptr = static_cast<char *>(this->pos) + this->stride * n; + return *reinterpret_cast<T *>(ptr); +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator++() noexcept { + this->pos = static_cast<char *>(this->pos) + this->stride; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator++(int) noexcept { + auto ret = iterator(*this); + this->pos = static_cast<char *>(this->pos) + this->stride; + return ret; +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator--() noexcept { + this->pos = static_cast<char *>(this->pos) - this->stride; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator--(int) noexcept { + auto ret = iterator(*this); + this->pos = static_cast<char *>(this->pos) - this->stride; + return ret; +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator+=( + typename Slice<T>::iterator::difference_type n) noexcept { + this->pos = static_cast<char *>(this->pos) + this->stride * n; + return *this; +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator-=( + typename Slice<T>::iterator::difference_type n) noexcept { + this->pos = static_cast<char *>(this->pos) - this->stride * n; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator+( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto ret = iterator(*this); + ret.pos = static_cast<char *>(this->pos) + this->stride * n; + return ret; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator-( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto ret = iterator(*this); + ret.pos = static_cast<char *>(this->pos) - this->stride * n; + return ret; +} + +template <typename T> +typename Slice<T>::iterator::difference_type +Slice<T>::iterator::operator-(const iterator &other) const noexcept { + auto diff = std::distance(static_cast<char *>(other.pos), + static_cast<char *>(this->pos)); + return diff / static_cast<typename Slice<T>::iterator::difference_type>( + this->stride); +} + +template <typename T> +bool Slice<T>::iterator::operator==(const iterator &other) const noexcept { + return this->pos == other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator!=(const iterator &other) const noexcept { + return this->pos != other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator<(const iterator &other) const noexcept { + return this->pos < other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator<=(const iterator &other) const noexcept { + return this->pos <= other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator>(const iterator &other) const noexcept { + return this->pos > other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator>=(const iterator &other) const noexcept { + return this->pos >= other.pos; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::begin() const noexcept { + iterator it; + it.pos = slicePtr(this); + it.stride = size_of<T>(); + return it; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::end() const noexcept { + iterator it = this->begin(); + it.pos = static_cast<char *>(it.pos) + it.stride * this->size(); + return it; +} + +template <typename T> +void Slice<T>::swap(Slice &rhs) noexcept { + std::swap(*this, rhs); +} +#endif // CXXBRIDGE1_RUST_SLICE + #ifndef CXXBRIDGE1_IS_COMPLETE #define CXXBRIDGE1_IS_COMPLETE namespace detail { @@ -108,6 +442,62 @@ } // namespace detail #endif // CXXBRIDGE1_IS_COMPLETE +#ifndef CXXBRIDGE1_LAYOUT +#define CXXBRIDGE1_LAYOUT +class layout { + template <typename T> + friend std::size_t size_of(); + template <typename T> + friend std::size_t align_of(); + template <typename T> + static typename std::enable_if<std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_size_of() { + return T::layout::size(); + } + template <typename T> + static typename std::enable_if<!std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_size_of() { + return sizeof(T); + } + template <typename T> + static + typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type + size_of() { + return do_size_of<T>(); + } + template <typename T> + static typename std::enable_if<std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_align_of() { + return T::layout::align(); + } + template <typename T> + static typename std::enable_if<!std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_align_of() { + return alignof(T); + } + template <typename T> + static + typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type + align_of() { + return do_align_of<T>(); + } +}; + +template <typename T> +std::size_t size_of() { + return layout::size_of<T>(); +} + +template <typename T> +std::size_t align_of() { + return layout::align_of<T>(); +} +#endif // CXXBRIDGE1_LAYOUT + namespace { template <bool> struct deleter_if { template <typename T> void operator()(T *) {} @@ -119,12 +509,65 @@ } // namespace cxxbridge1 } // namespace rust +#if __cplusplus >= 201402L +#define CXX_DEFAULT_VALUE(value) = value +#else +#define CXX_DEFAULT_VALUE(value) +#endif + +struct Any; +struct SliceAny; +struct KeyValue; +enum class ValueType : ::std::uint8_t; using OutputFile = ::OutputFile; using SourceDir = ::SourceDir; using Label = ::Label; using Settings = ::Settings; using Scope = ::Scope; using TestWithScope = ::TestWithScope; +using Value = ::Value; +using ParseNode = ::ParseNode; + +#ifndef CXXBRIDGE1_STRUCT_Any +#define CXXBRIDGE1_STRUCT_Any +struct Any final { + ::std::uint8_t _private CXX_DEFAULT_VALUE(0); + + using IsRelocatable = ::std::true_type; +}; +#endif // CXXBRIDGE1_STRUCT_Any + +#ifndef CXXBRIDGE1_STRUCT_SliceAny +#define CXXBRIDGE1_STRUCT_SliceAny +struct SliceAny final { + ::std::size_t len CXX_DEFAULT_VALUE(0); + ::Any *ptr CXX_DEFAULT_VALUE(nullptr); + + using IsRelocatable = ::std::true_type; +}; +#endif // CXXBRIDGE1_STRUCT_SliceAny + +#ifndef CXXBRIDGE1_STRUCT_KeyValue +#define CXXBRIDGE1_STRUCT_KeyValue +struct KeyValue final { + ::rust::Str key; + ::Value const &value; + + using IsRelocatable = ::std::true_type; +}; +#endif // CXXBRIDGE1_STRUCT_KeyValue + +#ifndef CXXBRIDGE1_ENUM_ValueType +#define CXXBRIDGE1_ENUM_ValueType +enum class ValueType : ::std::uint8_t { + None = 0, + Boolean = 1, + Integer = 2, + String = 3, + List = 4, + Scope = 5, +}; +#endif // CXXBRIDGE1_ENUM_ValueType extern "C" { void cxxbridge1$196$OutputFile$value(::OutputFile const &self, ::rust::Str *return$) noexcept { @@ -152,6 +595,16 @@ new (return$) ::Label const *(&(self.*toolchain_label$)()); } +void cxxbridge1$196$NewScope(::Scope const &parent_scope, ::rust::Slice<::rust::Str const> keys, ::std::unique_ptr<::Scope> &out_scope, ::SliceAny *return$) noexcept { + ::SliceAny (*NewScope$)(::Scope const &, ::rust::Slice<::rust::Str const>, ::std::unique_ptr<::Scope> &) = ::NewScope; + new (return$) ::SliceAny(NewScope$(parent_scope, keys, out_scope)); +} + +void cxxbridge1$196$GetScopeItems(::Scope const &scope, ::SliceAny *return$) noexcept { + ::SliceAny (*GetScopeItems$)(::Scope const &) = ::GetScopeItems; + new (return$) ::SliceAny(GetScopeItems$(scope)); +} + ::Settings const *cxxbridge1$196$Scope$settings_cxx(::Scope const &self) noexcept { ::Settings const *(::Scope::*settings_cxx$)() const = &::Scope::settings; return (self.*settings_cxx$)(); @@ -167,6 +620,95 @@ return (self.*scope_cxx$)(); } +::Value *cxxbridge1$196$NewValueForTesting() noexcept { + ::std::unique_ptr<::Value> (*NewValueForTesting$)() = ::NewValueForTesting; + return NewValueForTesting$().release(); +} + +::std::size_t cxxbridge1$196$ValueSize() noexcept { + ::std::size_t (*ValueSize$)() = ::ValueSize; + return ValueSize$(); +} + +::ValueType cxxbridge1$196$Value$kind(::Value const &self) noexcept { + Value::Type (::Value::*kind$)() const = &::Value::type; + return ::rust::cxx_to_rust((self.*kind$)()); +} + +void cxxbridge1$196$Value$boolean_value(::Value const &self, bool const **return$) noexcept { + bool const &(::Value::*boolean_value$)() const = &::Value::boolean_value; + new (return$) bool const *(&(self.*boolean_value$)()); +} + +void cxxbridge1$196$Value$int_value(::Value const &self, ::std::int64_t const **return$) noexcept { + ::std::int64_t const &(::Value::*int_value$)() const = &::Value::int_value; + new (return$) ::std::int64_t const *(&(self.*int_value$)()); +} + +void cxxbridge1$196$Value$string_value(::Value const &self, ::rust::Str *return$) noexcept { + const std::string& (::Value::*string_value$)() const = &::Value::string_value; + new (return$) ::rust::Str(::rust::cxx_to_rust((self.*string_value$)())); +} + +void cxxbridge1$196$list_value_cxx(::Value const &val, ::SliceAny *return$) noexcept { + ::SliceAny (*list_value_cxx$)(::Value const &) = ::GetValueList; + new (return$) ::SliceAny(list_value_cxx$(val)); +} + +::Scope const *cxxbridge1$196$Value$scope_value(::Value const &self) noexcept { + ::Scope const *(::Value::*scope_value$)() const = &::Value::scope_value; + return (self.*scope_value$)(); +} + +void cxxbridge1$196$SetValueNone(::Value &val, ::ParseNode const *origin) noexcept { + void (*SetValueNone$)(::Value &, ::ParseNode const *) = ::SetValueNone; + SetValueNone$(val, origin); +} + +void cxxbridge1$196$SetValueBool(::Value &val, ::ParseNode const *origin, bool b) noexcept { + void (*SetValueBool$)(::Value &, ::ParseNode const *, bool) = ::SetValueBool; + SetValueBool$(val, origin, b); +} + +void cxxbridge1$196$SetValueInt(::Value &val, ::ParseNode const *origin, ::std::int64_t i) noexcept { + void (*SetValueInt$)(::Value &, ::ParseNode const *, ::std::int64_t) = ::SetValueInt; + SetValueInt$(val, origin, i); +} + +void cxxbridge1$196$SetValueString(::Value &val, ::ParseNode const *origin, ::rust::Str s) noexcept { + void (*SetValueString$)(::Value &, ::ParseNode const *, ::rust::Str) = ::SetValueString; + SetValueString$(val, origin, s); +} + +::Any *cxxbridge1$196$SetValueList(::Value &val, ::ParseNode const *origin, ::std::size_t size) noexcept { + ::Any *(*SetValueList$)(::Value &, ::ParseNode const *, ::std::size_t) = ::SetValueList; + return SetValueList$(val, origin, size); +} + +void cxxbridge1$196$SetValueScope(::Value &val, ::ParseNode const *origin, ::Scope *scope) noexcept { + void (*SetValueScope$)(::Value &, ::ParseNode const *, ::std::unique_ptr<::Scope>) = ::SetValueScope; + SetValueScope$(val, origin, ::std::unique_ptr<::Scope>(scope)); +} + +static_assert(::rust::detail::is_complete<::std::remove_extent<::Scope>::type>::value, "definition of `::Scope` is required"); +static_assert(sizeof(::std::unique_ptr<::Scope>) == sizeof(void *), ""); +static_assert(alignof(::std::unique_ptr<::Scope>) == alignof(void *), ""); +void cxxbridge1$unique_ptr$Scope$null(::std::unique_ptr<::Scope> *ptr) noexcept { + ::new (ptr) ::std::unique_ptr<::Scope>(); +} +void cxxbridge1$unique_ptr$Scope$raw(::std::unique_ptr<::Scope> *ptr, ::std::unique_ptr<::Scope>::pointer raw) noexcept { + ::new (ptr) ::std::unique_ptr<::Scope>(raw); +} +::std::unique_ptr<::Scope>::element_type const *cxxbridge1$unique_ptr$Scope$get(::std::unique_ptr<::Scope> const &ptr) noexcept { + return ptr.get(); +} +::std::unique_ptr<::Scope>::pointer cxxbridge1$unique_ptr$Scope$release(::std::unique_ptr<::Scope> &ptr) noexcept { + return ptr.release(); +} +void cxxbridge1$unique_ptr$Scope$drop(::std::unique_ptr<::Scope> *ptr) noexcept { + ::rust::deleter_if<::rust::detail::is_complete<::Scope>::value>{}(ptr); +} + static_assert(::rust::detail::is_complete<::std::remove_extent<::TestWithScope>::type>::value, "definition of `::TestWithScope` is required"); static_assert(sizeof(::std::unique_ptr<::TestWithScope>) == sizeof(void *), ""); static_assert(alignof(::std::unique_ptr<::TestWithScope>) == alignof(void *), ""); @@ -185,4 +727,23 @@ void cxxbridge1$unique_ptr$TestWithScope$drop(::std::unique_ptr<::TestWithScope> *ptr) noexcept { ::rust::deleter_if<::rust::detail::is_complete<::TestWithScope>::value>{}(ptr); } + +static_assert(::rust::detail::is_complete<::std::remove_extent<::Value>::type>::value, "definition of `::Value` is required"); +static_assert(sizeof(::std::unique_ptr<::Value>) == sizeof(void *), ""); +static_assert(alignof(::std::unique_ptr<::Value>) == alignof(void *), ""); +void cxxbridge1$unique_ptr$Value$null(::std::unique_ptr<::Value> *ptr) noexcept { + ::new (ptr) ::std::unique_ptr<::Value>(); +} +void cxxbridge1$unique_ptr$Value$raw(::std::unique_ptr<::Value> *ptr, ::std::unique_ptr<::Value>::pointer raw) noexcept { + ::new (ptr) ::std::unique_ptr<::Value>(raw); +} +::std::unique_ptr<::Value>::element_type const *cxxbridge1$unique_ptr$Value$get(::std::unique_ptr<::Value> const &ptr) noexcept { + return ptr.get(); +} +::std::unique_ptr<::Value>::pointer cxxbridge1$unique_ptr$Value$release(::std::unique_ptr<::Value> &ptr) noexcept { + return ptr.release(); +} +void cxxbridge1$unique_ptr$Value$drop(::std::unique_ptr<::Value> *ptr) noexcept { + ::rust::deleter_if<::rust::detail::is_complete<::Value>::value>{}(ptr); +} } // extern "C"
diff --git a/src/gn/ffi/bridge.h b/src/gn/ffi/bridge.h index 39ec711..7ec8e6e 100644 --- a/src/gn/ffi/bridge.h +++ b/src/gn/ffi/bridge.h
@@ -1,31 +1,54 @@ // This file is generated by src/gn/ffi/update_bridge.sh. Do not edit manually. // Source: src/gn/ffi/starlark/crates/ffi/src/bridge.rs #pragma once +#include "gn/ffi/scope.h" #include "gn/ffi/test_with_scope.h" +#include "gn/ffi/value.h" #include "gn/label.h" #include "gn/output_file.h" #include "gn/scope.h" #include "gn/settings.h" #include "gn/source_dir.h" #include "gn/test_with_scope.h" +#include "gn/value.h" #include <array> +#include <cassert> +#include <cstddef> #include <cstdint> +#include <iterator> #include <memory> +#include <stdexcept> #include <string> +#include <type_traits> #if __cplusplus >= 201703L #include <string_view> #endif +#if __cplusplus >= 202002L +#include <ranges> +#endif namespace rust { inline namespace cxxbridge1 { // #include "rust/cxx.h" +#ifndef CXXBRIDGE1_PANIC +#define CXXBRIDGE1_PANIC +template <typename Exception> +void panic [[noreturn]](const char *msg); +#endif // CXXBRIDGE1_PANIC + namespace { template <typename T> class impl; } // namespace class String; +class Opaque; + +template <typename T> +::std::size_t size_of(); +template <typename T> +::std::size_t align_of(); #ifndef CXXBRIDGE1_RUST_STR #define CXXBRIDGE1_RUST_STR @@ -85,12 +108,446 @@ inline Str cxx_to_rust(const std::string &s) { return Str(s); } #endif // CXXBRIDGE1_RUST_STR + +#ifndef CXXBRIDGE1_RUST_SLICE +#define CXXBRIDGE1_RUST_SLICE +namespace detail { +template <bool> +struct copy_assignable_if {}; + +template <> +struct copy_assignable_if<false> { + copy_assignable_if() noexcept = default; + copy_assignable_if(const copy_assignable_if &) noexcept = default; + copy_assignable_if &operator=(const copy_assignable_if &) & noexcept = delete; + copy_assignable_if &operator=(copy_assignable_if &&) & noexcept = default; +}; +} // namespace detail + +template <typename T> +class Slice final + : private detail::copy_assignable_if<std::is_const<T>::value> { +public: + using value_type = T; + + Slice() noexcept; + Slice(T *, std::size_t count) noexcept; + + template <typename C> + explicit Slice(C &c) : Slice(c.data(), c.size()) {} + + Slice &operator=(const Slice<T> &) & noexcept = default; + Slice &operator=(Slice<T> &&) & noexcept = default; + + T *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + T &operator[](std::size_t n) const noexcept; + T &at(std::size_t n) const; + T &front() const noexcept; + T &back() const noexcept; + + Slice(const Slice<T> &) noexcept = default; + ~Slice() noexcept = default; + + class iterator; + iterator begin() const noexcept; + iterator end() const noexcept; + + void swap(Slice &) noexcept; + +private: + class uninit; + Slice(uninit) noexcept; + friend impl<Slice>; + friend void sliceInit(void *, const void *, std::size_t) noexcept; + friend void *slicePtr(const void *) noexcept; + friend std::size_t sliceLen(const void *) noexcept; + + std::array<std::uintptr_t, 2> repr; +}; + +#ifdef __cpp_deduction_guides +template <typename C> +explicit Slice(C &c) + -> Slice<std::remove_reference_t<decltype(*std::declval<C>().data())>>; +#endif // __cpp_deduction_guides + +template <typename T> +class Slice<T>::iterator final { +public: +#if __cplusplus >= 202002L + using iterator_category = std::contiguous_iterator_tag; +#else + using iterator_category = std::random_access_iterator_tag; +#endif + using value_type = T; + using element_type = T; + using difference_type = std::ptrdiff_t; + using pointer = typename std::add_pointer<T>::type; + using reference = typename std::add_lvalue_reference<T>::type; + + reference operator*() const noexcept; + pointer operator->() const noexcept; + reference operator[](difference_type) const noexcept; + + iterator &operator++() noexcept; + iterator operator++(int) noexcept; + iterator &operator--() noexcept; + iterator operator--(int) noexcept; + + iterator &operator+=(difference_type) noexcept; + iterator &operator-=(difference_type) noexcept; + iterator operator+(difference_type) const noexcept; + friend inline iterator operator+(difference_type lhs, iterator rhs) noexcept { + return rhs + lhs; + } + iterator operator-(difference_type) const noexcept; + difference_type operator-(const iterator &) const noexcept; + + bool operator==(const iterator &) const noexcept; + bool operator!=(const iterator &) const noexcept; + bool operator<(const iterator &) const noexcept; + bool operator<=(const iterator &) const noexcept; + bool operator>(const iterator &) const noexcept; + bool operator>=(const iterator &) const noexcept; + +private: + friend class Slice; + void *pos; + std::size_t stride; +}; + +#if __cplusplus >= 202002L +static_assert(std::ranges::contiguous_range<rust::Slice<const uint8_t>>); +static_assert(std::contiguous_iterator<rust::Slice<const uint8_t>::iterator>); +#endif + +template <typename T> +Slice<T>::Slice() noexcept { + sliceInit(this, reinterpret_cast<void *>(align_of<T>()), 0); +} + +template <typename T> +Slice<T>::Slice(T *s, std::size_t count) noexcept { + assert(s != nullptr || count == 0); + sliceInit(this, + s == nullptr && count == 0 + ? reinterpret_cast<void *>(align_of<T>()) + : const_cast<typename std::remove_const<T>::type *>(s), + count); +} + +template <typename T> +T *Slice<T>::data() const noexcept { + return reinterpret_cast<T *>(slicePtr(this)); +} + +template <typename T> +std::size_t Slice<T>::size() const noexcept { + return sliceLen(this); +} + +template <typename T> +std::size_t Slice<T>::length() const noexcept { + return this->size(); +} + +template <typename T> +bool Slice<T>::empty() const noexcept { + return this->size() == 0; +} + +template <typename T> +T &Slice<T>::operator[](std::size_t n) const noexcept { + assert(n < this->size()); + auto ptr = static_cast<char *>(slicePtr(this)) + size_of<T>() * n; + return *reinterpret_cast<T *>(ptr); +} + +template <typename T> +T &Slice<T>::at(std::size_t n) const { + if (n >= this->size()) { + panic<std::out_of_range>("rust::Slice index out of range"); + } + return (*this)[n]; +} + +template <typename T> +T &Slice<T>::front() const noexcept { + assert(!this->empty()); + return (*this)[0]; +} + +template <typename T> +T &Slice<T>::back() const noexcept { + assert(!this->empty()); + return (*this)[this->size() - 1]; +} + +template <typename T> +typename Slice<T>::iterator::reference +Slice<T>::iterator::operator*() const noexcept { + return *static_cast<T *>(this->pos); +} + +template <typename T> +typename Slice<T>::iterator::pointer +Slice<T>::iterator::operator->() const noexcept { + return static_cast<T *>(this->pos); +} + +template <typename T> +typename Slice<T>::iterator::reference Slice<T>::iterator::operator[]( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto ptr = static_cast<char *>(this->pos) + this->stride * n; + return *reinterpret_cast<T *>(ptr); +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator++() noexcept { + this->pos = static_cast<char *>(this->pos) + this->stride; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator++(int) noexcept { + auto ret = iterator(*this); + this->pos = static_cast<char *>(this->pos) + this->stride; + return ret; +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator--() noexcept { + this->pos = static_cast<char *>(this->pos) - this->stride; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator--(int) noexcept { + auto ret = iterator(*this); + this->pos = static_cast<char *>(this->pos) - this->stride; + return ret; +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator+=( + typename Slice<T>::iterator::difference_type n) noexcept { + this->pos = static_cast<char *>(this->pos) + this->stride * n; + return *this; +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator-=( + typename Slice<T>::iterator::difference_type n) noexcept { + this->pos = static_cast<char *>(this->pos) - this->stride * n; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator+( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto ret = iterator(*this); + ret.pos = static_cast<char *>(this->pos) + this->stride * n; + return ret; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator-( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto ret = iterator(*this); + ret.pos = static_cast<char *>(this->pos) - this->stride * n; + return ret; +} + +template <typename T> +typename Slice<T>::iterator::difference_type +Slice<T>::iterator::operator-(const iterator &other) const noexcept { + auto diff = std::distance(static_cast<char *>(other.pos), + static_cast<char *>(this->pos)); + return diff / static_cast<typename Slice<T>::iterator::difference_type>( + this->stride); +} + +template <typename T> +bool Slice<T>::iterator::operator==(const iterator &other) const noexcept { + return this->pos == other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator!=(const iterator &other) const noexcept { + return this->pos != other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator<(const iterator &other) const noexcept { + return this->pos < other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator<=(const iterator &other) const noexcept { + return this->pos <= other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator>(const iterator &other) const noexcept { + return this->pos > other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator>=(const iterator &other) const noexcept { + return this->pos >= other.pos; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::begin() const noexcept { + iterator it; + it.pos = slicePtr(this); + it.stride = size_of<T>(); + return it; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::end() const noexcept { + iterator it = this->begin(); + it.pos = static_cast<char *>(it.pos) + it.stride * this->size(); + return it; +} + +template <typename T> +void Slice<T>::swap(Slice &rhs) noexcept { + std::swap(*this, rhs); +} +#endif // CXXBRIDGE1_RUST_SLICE + +#ifndef CXXBRIDGE1_IS_COMPLETE +#define CXXBRIDGE1_IS_COMPLETE +namespace detail { +namespace { +template <typename T, typename = std::size_t> +struct is_complete : std::false_type {}; +template <typename T> +struct is_complete<T, decltype(sizeof(T))> : std::true_type {}; +} // namespace +} // namespace detail +#endif // CXXBRIDGE1_IS_COMPLETE + +#ifndef CXXBRIDGE1_LAYOUT +#define CXXBRIDGE1_LAYOUT +class layout { + template <typename T> + friend std::size_t size_of(); + template <typename T> + friend std::size_t align_of(); + template <typename T> + static typename std::enable_if<std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_size_of() { + return T::layout::size(); + } + template <typename T> + static typename std::enable_if<!std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_size_of() { + return sizeof(T); + } + template <typename T> + static + typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type + size_of() { + return do_size_of<T>(); + } + template <typename T> + static typename std::enable_if<std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_align_of() { + return T::layout::align(); + } + template <typename T> + static typename std::enable_if<!std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_align_of() { + return alignof(T); + } + template <typename T> + static + typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type + align_of() { + return do_align_of<T>(); + } +}; + +template <typename T> +std::size_t size_of() { + return layout::size_of<T>(); +} + +template <typename T> +std::size_t align_of() { + return layout::align_of<T>(); +} +#endif // CXXBRIDGE1_LAYOUT } // namespace cxxbridge1 } // namespace rust +#if __cplusplus >= 201402L +#define CXX_DEFAULT_VALUE(value) = value +#else +#define CXX_DEFAULT_VALUE(value) +#endif + +struct Any; +struct SliceAny; +struct KeyValue; +enum class ValueType : ::std::uint8_t; using OutputFile = ::OutputFile; using SourceDir = ::SourceDir; using Label = ::Label; using Settings = ::Settings; using Scope = ::Scope; using TestWithScope = ::TestWithScope; +using Value = ::Value; +using ParseNode = ::ParseNode; + +#ifndef CXXBRIDGE1_STRUCT_Any +#define CXXBRIDGE1_STRUCT_Any +struct Any final { + ::std::uint8_t _private CXX_DEFAULT_VALUE(0); + + using IsRelocatable = ::std::true_type; +}; +#endif // CXXBRIDGE1_STRUCT_Any + +#ifndef CXXBRIDGE1_STRUCT_SliceAny +#define CXXBRIDGE1_STRUCT_SliceAny +struct SliceAny final { + ::std::size_t len CXX_DEFAULT_VALUE(0); + ::Any *ptr CXX_DEFAULT_VALUE(nullptr); + + using IsRelocatable = ::std::true_type; +}; +#endif // CXXBRIDGE1_STRUCT_SliceAny + +#ifndef CXXBRIDGE1_STRUCT_KeyValue +#define CXXBRIDGE1_STRUCT_KeyValue +struct KeyValue final { + ::rust::Str key; + ::Value const &value; + + using IsRelocatable = ::std::true_type; +}; +#endif // CXXBRIDGE1_STRUCT_KeyValue + +#ifndef CXXBRIDGE1_ENUM_ValueType +#define CXXBRIDGE1_ENUM_ValueType +enum class ValueType : ::std::uint8_t { + None = 0, + Boolean = 1, + Integer = 2, + String = 3, + List = 4, + Scope = 5, +}; +#endif // CXXBRIDGE1_ENUM_ValueType
diff --git a/src/gn/ffi/scope.cc b/src/gn/ffi/scope.cc new file mode 100644 index 0000000..f3a658e --- /dev/null +++ b/src/gn/ffi/scope.cc
@@ -0,0 +1,67 @@ +// 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/ffi/scope.h" +#include "gn/ffi/bridge.h" +#include "gn/ffi/slice.h" +#include "gn/scope.h" +#include "gn/value.h" + +SliceAny NewScope(const Scope& parent_scope, + rust::Slice<const rust::Str> keys, + std::unique_ptr<Scope>& out_scope) { + auto new_scope = std::make_unique<Scope>(&parent_scope); + new_scope->set_source_dir(parent_scope.GetSourceDir()); + // We detach because GN always detaches when result_mode of the parse tree is + // RETURNS_SCOPE (which is what starlark creates new scopes for). + new_scope->DetachFromContaining(); + + std::vector<Value*> placeholders; + placeholders.reserve(keys.size()); + for (const auto& key : keys) { + std::string_view key_sv(key.data(), key.size()); + Value* val = new_scope->SetValue(key_sv, Value(), nullptr); + placeholders.push_back(val); + } + + out_scope = std::move(new_scope); + return IntoSlice(std::move(placeholders)); +} + +// Unlike regular GN scoping rules, this does not extract from variables defined +// in outer scopes. This is because starlark treats scopes as equivalent to +// "struct" objects, and as the **kwargs to pass to functions. Thus, accessing +// values from outer scopes would be very wierd. +// Consider the following example: +// +// # //:example.scl +// def my_macro(srcs, my_struct): +// my_struct.bar +// +// # BUILD.gn +// load("//:example.scl", "my_macro") +// +// foo = 1 +// my_macro() { +// srcs = ... +// my_struct = { +// bar = 2 +// } +// } +// +// In this example, if we included parent scopes as well: +// * my_macro would complain that it got an unexpected parameter "foo" +// * my_struct.srcs would also be accessible. +SliceAny GetScopeItems(const Scope& scope) { + Scope::KeyValueMap scope_values; + scope.GetCurrentScopeValues(&scope_values); + + std::vector<KeyValue> vec; + vec.reserve(scope_values.size()); + for (const auto& pair : scope_values) { + vec.push_back( + KeyValue{rust::Str(pair.first.data(), pair.first.size()), pair.second}); + } + return IntoSlice(std::move(vec)); +}
diff --git a/src/gn/ffi/scope.h b/src/gn/ffi/scope.h new file mode 100644 index 0000000..dc3e309 --- /dev/null +++ b/src/gn/ffi/scope.h
@@ -0,0 +1,29 @@ +// 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. + +#ifndef TOOLS_GN_FFI_SCOPE_H_ +#define TOOLS_GN_FFI_SCOPE_H_ + +#include <memory> + +#include "cxx.h" + +class Scope; +struct SliceAny; + +// Constructs a new child Scope, populates placeholder Values for the given +// keys, and returns a "std::vector<Value&>" where vec[i] is the value for +// keys[i]. +// +// Safety: Rust is required to convert this to an OwnedSlice<&Value>. +SliceAny NewScope(const Scope& parent_scope, + rust::Slice<const rust::Str> keys, + std::unique_ptr<Scope>& out_scope); + +// Returns a "std::vector<KeyValue>"-like object. +// +// Safety: Rust is required to convert this to an OwnedSlice<KeyValue>. +SliceAny GetScopeItems(const Scope& scope); + +#endif // TOOLS_GN_FFI_SCOPE_H_
diff --git a/src/gn/ffi/slice.h b/src/gn/ffi/slice.h new file mode 100644 index 0000000..8729eba --- /dev/null +++ b/src/gn/ffi/slice.h
@@ -0,0 +1,54 @@ +// 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. + +#ifndef TOOLS_GN_FFI_SLICE_H_ +#define TOOLS_GN_FFI_SLICE_H_ + +#include <array> +#include <cstdint> +#include <type_traits> +#include <vector> + +#include "gn/ffi/bridge.h" + +// Consumes a std::vector<T> to return a Slice to rust. +// This is akin to unique_pointer.release(), where we give up ownership of the +// slice and pass it to rust. +// +// Safety: Rust is *required* to cast this to an OwnedSlice<T>. +// This will guaruntee that the vector is destroyed. +template <typename T> +inline SliceAny IntoSlice(std::vector<T> vec) { + // Rust knows how to free the slice itself (just call free on the pointer), + // but does not know how to call the destructor on individual elements. + static_assert(std::is_trivially_destructible_v<T>, + "T must be trivially destructible to avoid leaks"); + if (vec.empty()) { + return SliceAny{0, nullptr}; + } + SliceAny slice{vec.size(), reinterpret_cast<Any*>(vec.data())}; + + // Construct on stack buffer to prevent C++ compiler from running destructor + // on vec + std::array<uint8_t, sizeof(std::vector<T>)> buf; + new (&buf) std::vector<T>(std::move(vec)); + + return slice; +} + +// Returns a view of a std::vector, which may or may not be const. +// +// Safety: This uses const_cast to cast away constness because SliceAny is a +// unified FFI representation that uses a mutable Any* pointer (which is +// required to support both mutable and immutable slices in Rust). +// +// If the API intends to return immutable objects, the rust caller is +// responsible for wrapping the API in a function that returns Immutable<T>. +template <typename T> +inline SliceAny AsSlice(const std::vector<T>& vec) { + return SliceAny{vec.size(), + reinterpret_cast<Any*>(const_cast<T*>(vec.data()))}; +} + +#endif // TOOLS_GN_FFI_SLICE_H_
diff --git a/src/gn/ffi/value.cc b/src/gn/ffi/value.cc new file mode 100644 index 0000000..dce39d8 --- /dev/null +++ b/src/gn/ffi/value.cc
@@ -0,0 +1,57 @@ +// 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/ffi/value.h" + +#include <new> +#include <string> + +#include "gn/ffi/bridge.h" +#include "gn/ffi/slice.h" + +namespace rust { +ValueType cxx_to_rust(Value::Type t) { + return static_cast<ValueType>(t); +} +} // namespace rust + +size_t ValueSize() { + return sizeof(Value); +} + +void SetValueNone(Value& self, const ParseNode* origin) { + new (&self) Value(origin, Value::NONE); +} + +void SetValueBool(Value& self, const ParseNode* origin, bool b) { + new (&self) Value(origin, b); +} + +void SetValueInt(Value& self, const ParseNode* origin, int64_t i) { + new (&self) Value(origin, i); +} + +void SetValueString(Value& self, const ParseNode* origin, rust::Str s) { + new (&self) Value(origin, std::string(s.data(), s.size())); +} + +Any* SetValueList(Value& self, const ParseNode* origin, size_t size) { + new (&self) Value(origin, Value::LIST); + self.list_value().resize(size); + return reinterpret_cast<Any*>(self.list_value().data()); +} + +void SetValueScope(Value& self, + const ParseNode* origin, + std::unique_ptr<Scope> scope) { + new (&self) Value(origin, std::move(scope)); +} + +SliceAny GetValueList(const Value& self) { + return AsSlice(self.list_value()); +} + +std::unique_ptr<Value> NewValueForTesting() { + return std::make_unique<Value>(); +}
diff --git a/src/gn/ffi/value.h b/src/gn/ffi/value.h new file mode 100644 index 0000000..5fe8106 --- /dev/null +++ b/src/gn/ffi/value.h
@@ -0,0 +1,51 @@ +// 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. + +#ifndef TOOLS_GN_FFI_VALUE_H_ +#define TOOLS_GN_FFI_VALUE_H_ + +#include <memory> + +#include "cxx.h" +#include "gn/value.h" + +class Scope; +class ParseNode; +struct SliceAny; + +enum class ValueType : uint8_t; + +// Teach rust how to convert Value::Type to an enum that rust is aware of. +namespace rust { +ValueType cxx_to_rust(Value::Type t); +} + +size_t ValueSize(); +// SetValue* is called with potentially uninitialized Value objects. +// These functions roughly correspond to calling the corresponding constructor +// with in-place construction. +void SetValueNone(Value& self, const ParseNode* origin); +void SetValueBool(Value& self, const ParseNode* origin, bool b); +void SetValueInt(Value& self, const ParseNode* origin, int64_t i); +void SetValueString(Value& self, const ParseNode* origin, rust::Str s); +struct Any; +// Sets the value to a list of `size` elements. Returns a pointer to the start +// of the vector. +// +// Safety: Rust is required to convert this to a Slice<Value>(pointer, size) +Any* SetValueList(Value& self, const ParseNode* origin, size_t size); +void SetValueScope(Value& self, + const ParseNode* origin, + std::unique_ptr<Scope> scope); +// Returns a "std::vector<Value>". +// +// Safety: Rust is required to convert this to a Slice<Value>. +SliceAny GetValueList(const Value& self); + +// GN values are never created in starlark in production code. +// If a value may ever be returned, it will be passed as a mutable output +// parameter. +std::unique_ptr<Value> NewValueForTesting(); + +#endif // TOOLS_GN_FFI_VALUE_H_
diff --git a/src/gn/starlark/crates/build_helper.rs b/src/gn/starlark/crates/build_helper.rs index 7a943eb..30886ac 100644 --- a/src/gn/starlark/crates/build_helper.rs +++ b/src/gn/starlark/crates/build_helper.rs
@@ -3,11 +3,11 @@ // found in the LICENSE file. fn require_lib(out_dir: &std::path::Path, name: &str) { - println!("cargo:rustc-link-lib=static={}", name); + println!("cargo:rustc-link-lib=static={name}"); let lib = if cfg!(target_os = "windows") { - format!("{}.lib", name) + format!("{name}.lib") } else { - format!("lib{}.a", name) + format!("lib{name}.a") }; println!("cargo:rerun-if-changed={}", out_dir.join(lib).display()); }
diff --git a/src/gn/starlark/crates/ffi/src/bridge.rs b/src/gn/starlark/crates/ffi/src/bridge.rs index 8898d2a..96f55d4 100644 --- a/src/gn/starlark/crates/ffi/src/bridge.rs +++ b/src/gn/starlark/crates/ffi/src/bridge.rs
@@ -16,16 +16,44 @@ // CxxBridge requires a module, but we don't want one. So we make a private one // and re-export all fields. mod dummy { + struct Any { + _private: u8, + } + + // A &[T] compatible with both opaque and non-opaque types. + #[derive(Clone, Copy)] + struct SliceAny { + len: usize, + ptr: *mut Any, + } + + struct KeyValue<'a> { + key: &'a str, + value: &'a Value, + } + + #[derive(Clone, Copy)] + enum ValueType { + None = 0, + Boolean = 1, + Integer = 2, + String = 3, + List = 4, + Scope = 5, + } unsafe extern "C++" { // include! simply tells cxxbridge to put the #include in the generated C++ // source code. It does not do anything on the rust side. + include!("gn/ffi/scope.h"); include!("gn/ffi/test_with_scope.h"); + include!("gn/ffi/value.h"); include!("gn/label.h"); include!("gn/output_file.h"); include!("gn/scope.h"); include!("gn/settings.h"); include!("gn/source_dir.h"); include!("gn/test_with_scope.h"); + include!("gn/value.h"); type OutputFile; #[cxx_return_type = "std::string_view"] @@ -44,13 +72,72 @@ pub(in crate::settings) fn toolchain_label(self: &Settings) -> &Label; type Scope; + // Constructs a new child Scope, populates placeholder Values for the given + // keys, and returns an owned slice of references to the placeholders. + // For example, NewScope(&scope, ["foo", "bar"]) would return + // [scope["foo"], scope["bar"]]. + // The caller is then responsible for filling in the values as needed. + pub(in crate::scope) fn NewScope( + parent_scope: &Scope, + keys: &[&str], + out_scope: &mut UniquePtr<Scope>, + ) -> SliceAny; + // Returns an OwnedSlice<KeyValue> corresponding to references to each element. + pub(in crate::scope) fn GetScopeItems(scope: &Scope) -> SliceAny; #[rust_name = "settings_cxx"] - pub(crate) fn settings(self: &Scope) -> *const Settings; + pub(in crate::scope) fn settings(self: &Scope) -> *const Settings; type TestWithScope; pub(in crate::test_with_scope) fn NewTestWithScope() -> UniquePtr<TestWithScope>; #[rust_name = "scope_cxx"] pub(in crate::test_with_scope) fn scope(self: Pin<&mut TestWithScope>) -> *mut Scope; + + type Value; + type ParseNode; + // We allow dead code because this isn't used in production and we + // can't tag things in the bridge with cfg(test). + #[allow(dead_code)] + pub(in crate::value) fn NewValueForTesting() -> UniquePtr<Value>; + pub(in crate::value) fn ValueSize() -> usize; + #[cxx_return_type = "Value::Type"] + #[cxx_name = "type"] + // We can't call this "type" in rust since it's a keyword. + pub(in crate::value) fn kind(self: &Value) -> ValueType; + pub(in crate::value) fn boolean_value(self: &Value) -> &bool; + pub(in crate::value) fn int_value(self: &Value) -> &i64; + #[cxx_return_type = "const std::string&"] + pub(in crate::value) fn string_value(self: &Value) -> &str; + #[cxx_name = "GetValueList"] + pub(in crate::value) fn list_value_cxx(val: &Value) -> SliceAny; + pub(in crate::value) fn scope_value(self: &Value) -> *const Scope; + pub(in crate::value) unsafe fn SetValueNone(val: Pin<&mut Value>, origin: *const ParseNode); + pub(in crate::value) unsafe fn SetValueBool( + val: Pin<&mut Value>, + origin: *const ParseNode, + b: bool, + ); + pub(in crate::value) unsafe fn SetValueInt( + val: Pin<&mut Value>, + origin: *const ParseNode, + i: i64, + ); + pub(in crate::value) unsafe fn SetValueString( + val: Pin<&mut Value>, + origin: *const ParseNode, + s: &str, + ); + // Initialises self as a list of `size` elements and returns a pointer to the + // start. + pub(in crate::value) unsafe fn SetValueList( + val: Pin<&mut Value>, + origin: *const ParseNode, + size: usize, + ) -> *mut Any; + pub(in crate::value) unsafe fn SetValueScope( + val: Pin<&mut Value>, + origin: *const ParseNode, + scope: UniquePtr<Scope>, + ); } }
diff --git a/src/gn/starlark/crates/ffi/src/lib.rs b/src/gn/starlark/crates/ffi/src/lib.rs index d9a9fda..2e698a0 100644 --- a/src/gn/starlark/crates/ffi/src/lib.rs +++ b/src/gn/starlark/crates/ffi/src/lib.rs
@@ -18,10 +18,17 @@ //! these types in their own files. mod bridge; mod label; +mod mutability; +mod opaque; mod output_file; mod scope; mod settings; +mod slice; mod test_with_scope; +mod value; -pub use bridge::{Label, OutputFile, Scope, Settings, SourceDir}; +pub use bridge::{KeyValue, Label, OutputFile, Scope, Settings, SourceDir, Value, ValueType}; +pub use mutability::Immutable; +pub use opaque::{NonOpaque, OpaqueSized}; +pub use slice::{OwnedSlice, Slice}; pub use test_with_scope::TestWithScope;
diff --git a/src/gn/starlark/crates/ffi/src/mutability.rs b/src/gn/starlark/crates/ffi/src/mutability.rs new file mode 100644 index 0000000..46027da --- /dev/null +++ b/src/gn/starlark/crates/ffi/src/mutability.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. + +use std::ops::Deref; + +/// A wrapper that enforces compile-time immutability for its inner type. +/// +/// It only implements `Deref`, meaning that once wrapped, callers can only +/// access read-only methods taking `&self`. +pub struct Immutable<T>(T); + +impl<T> From<T> for Immutable<T> { + #[inline(always)] + fn from(inner: T) -> Self { + Self(inner) + } +} + +impl<T> Deref for Immutable<T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +}
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..3676c2d --- /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. + +use std::pin::Pin; + +/// Trait for types that are opaque, but that we need to know the size of. +/// +/// This is usually because we intend to iterate over arrays of them. +pub trait OpaqueSized { + /// Returns the size of the type in bytes. + fn size() -> usize; +} + +impl OpaqueSized for crate::bridge::Value { + #[inline(always)] + fn size() -> usize { + crate::bridge::ValueSize() + } +} + +/// Marker trait indicating that a type is not an opaque type. +pub trait NonOpaque {} + +impl<'a> NonOpaque for crate::bridge::KeyValue<'a> {} +impl<T> NonOpaque for *mut T {} +impl<T> NonOpaque for Pin<&mut T> {}
diff --git a/src/gn/starlark/crates/ffi/src/scope.rs b/src/gn/starlark/crates/ffi/src/scope.rs index 8e4c85a..e063d7a 100644 --- a/src/gn/starlark/crates/ffi/src/scope.rs +++ b/src/gn/starlark/crates/ffi/src/scope.rs
@@ -2,11 +2,45 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use crate::Scope; +use std::pin::Pin; + +use starlark::values::FrozenValue; + +use crate::{bridge::Value, Immutable, OwnedSlice, Scope}; + impl Scope { + pub(crate) fn new<'b>( + parent: &Self, + keys: &[&str], + ) -> (cxx::UniquePtr<Self>, OwnedSlice<Pin<&'b mut Value>>) { + let mut nested_scope = cxx::UniquePtr::<Self>::null(); + let values = crate::bridge::NewScope(parent, keys, &mut nested_scope); + (nested_scope, values.into()) + } + /// Returns the settings for the given scope. pub fn settings(&self) -> &crate::Settings { // Safety: Settings pointer is always valid and non-null. unsafe { self.settings_cxx().as_ref() }.unwrap() } + + /// Returns the items currently in the scope (not including parent scopes). + pub fn items(&self) -> Immutable<OwnedSlice<crate::bridge::KeyValue<'_>>> { + let slice = crate::bridge::GetScopeItems(self); + Immutable::from(crate::OwnedSlice::<crate::bridge::KeyValue>::from(slice)) + } + + /// Converts Scope items to Starlark key-value pairs. + pub fn get_kv<'a>( + &'a self, + frozen_heap: &starlark::values::FrozenHeap, + ) -> Vec<(&'a str, FrozenValue)> { + let owned = self.items(); + let mut items = Vec::new(); + // Iterate over the KeyValue contiguous slice: + for pair in owned.as_slice() { + items.push((pair.key, pair.value.to_rust(frozen_heap))); + } + items + } }
diff --git a/src/gn/starlark/crates/ffi/src/slice.rs b/src/gn/starlark/crates/ffi/src/slice.rs new file mode 100644 index 0000000..acc9945 --- /dev/null +++ b/src/gn/starlark/crates/ffi/src/slice.rs
@@ -0,0 +1,140 @@ +// 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 std::{ + ffi::c_void, + marker::PhantomData, + ops::{Deref, DerefMut}, + pin::Pin, +}; + +use crate::opaque::{NonOpaque, OpaqueSized}; + +/// A &[T]-like object. +/// +/// Use this for one of two reasons: +/// * C++ returns a slice of an opaque type, for which &[T] does not work. +/// * C++ returns a std::vector<T>, in which case you should use +/// `OwnedSlice<T>`. +/// +/// Note that either as_slice or iter is implemented, but not both, depending +/// on whether T is opaque. +pub struct Slice<T> { + raw: crate::bridge::SliceAny, + _marker: PhantomData<T>, +} + +impl<T> From<crate::bridge::SliceAny> for Slice<T> { + #[inline(always)] + fn from(raw: crate::bridge::SliceAny) -> Self { + Self { + raw, + _marker: PhantomData, + } + } +} + +impl<T: OpaqueSized> Slice<T> { + /// Returns a mutable iterator over the elements of the slice. + #[inline(always)] + pub fn iter_mut(&mut self) -> impl Iterator<Item = Pin<&mut T>> { + let size = T::size(); + let mut current = self.raw.ptr; + (0..self.raw.len).map(move |_| { + let ptr = current; + current = unsafe { current.add(size) }; + // Safety: The pointer is valid and exclusive for the lifetime of the iteration. + unsafe { Pin::new_unchecked(&mut *ptr.cast::<T>()) } + }) + } + + /// Returns an iterator over the elements of the slice. + #[inline(always)] + pub fn iter(&self) -> impl Iterator<Item = &T> { + let size = T::size(); + let mut current = self.raw.ptr; + (0..self.raw.len).map(move |_| { + let ptr = current; + current = unsafe { current.add(size) }; + // Safety: The pointer is valid for the lifetime of Slice. + unsafe { &*ptr.cast::<T>() } + }) + } +} + +impl<T: NonOpaque> Slice<T> { + /// Returns a view of the slice as a standard mutable rust slice. + #[inline(always)] + pub fn as_slice_mut(&mut self) -> &mut [T] { + if self.raw.len == 0 { + &mut [] + } else { + // Safety: T implements NonOpaque, thus guaranteeing its size is correct. + // (If T is opaque, rust is lied to and thinks a T is a u8, but &T remains + // correct). + unsafe { std::slice::from_raw_parts_mut(self.raw.ptr.cast::<T>(), self.raw.len) } + } + } + + /// Returns a view of the slice as a standard rust slice. + #[inline(always)] + pub fn as_slice(&self) -> &[T] { + if self.raw.len == 0 { + &[] + } else { + // Safety: T implements NonOpaque, thus guaranteeing its size is correct, + // and the memory is valid for read access for the lifetime of Slice. + unsafe { std::slice::from_raw_parts(self.raw.ptr.cast::<T>(), self.raw.len) } + } + } +} + +/// A std::vector<T> for which ownership has been papssed to rust. +/// +/// To create an OwnedSlice, create a std::vector and call `ReleaseVector` +/// to release ownership of the slice. +pub struct OwnedSlice<T> { + slice: Slice<T>, +} + +impl<T> From<crate::bridge::SliceAny> for OwnedSlice<T> { + #[inline(always)] + fn from(raw: crate::bridge::SliceAny) -> Self { + Self { + slice: Slice::from(raw), + } + } +} + +impl<T> Deref for OwnedSlice<T> { + type Target = Slice<T>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.slice + } +} + +impl<T> DerefMut for OwnedSlice<T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.slice + } +} + +impl<T> Drop for OwnedSlice<T> { + #[inline(always)] + fn drop(&mut self) { + // We don't write this function, this is the libc free function. + extern "C" { + fn free(ptr: *mut c_void); + } + + // Safety: Calling free is safe. The pointer is guarunteed to be valid and owned + // by us. + unsafe { + free(self.slice.raw.ptr.cast::<c_void>()); + } + } +}
diff --git a/src/gn/starlark/crates/ffi/src/value.rs b/src/gn/starlark/crates/ffi/src/value.rs new file mode 100644 index 0000000..95a26c0 --- /dev/null +++ b/src/gn/starlark/crates/ffi/src/value.rs
@@ -0,0 +1,199 @@ +// 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 std::pin::Pin; + +use starlark::values::{list::ListRef, structs::StructRef, FrozenValue}; + +pub use crate::bridge::ParseNode; +use crate::{ + bridge::{SliceAny, Value, ValueType}, + Immutable, Scope, Slice, +}; + +impl Value { + fn list_value(&self) -> Immutable<Slice<Self>> { + Immutable::from(Slice::from(crate::bridge::list_value_cxx(self))) + } + + pub fn to_rust(&self, frozen_heap: &starlark::values::FrozenHeap) -> FrozenValue { + match self.kind() { + ValueType::None => FrozenValue::new_none(), + ValueType::Boolean => FrozenValue::new_bool(*self.boolean_value()), + ValueType::Integer => frozen_heap.alloc(*self.int_value()), + ValueType::String => frozen_heap.alloc(self.string_value()), + ValueType::List => { + let slice = self.list_value(); + let items: Vec<_> = slice.iter().map(|item| item.to_rust(frozen_heap)).collect(); + frozen_heap.alloc(items) + }, + ValueType::Scope => { + let scope_ptr = self.scope_value(); + // Safety: C++ Value invariants guarantee that scope_value() is never null + // when the type is SCOPE. + let scope = unsafe { &*scope_ptr }; + frozen_heap.alloc(starlark::values::structs::AllocStruct( + scope.get_kv(frozen_heap), + )) + }, + _ => unreachable!(), + } + } + + pub fn assign<'v>( + mut self: Pin<&mut Self>, + val: starlark::values::Value<'v>, + scope: &mut Scope, + origin: *const ParseNode, + ) { + if val.is_none() { + // Safety: Just an FFI function. + unsafe { + crate::bridge::SetValueNone(self.as_mut(), origin); + } + } else if let Some(s) = val.unpack_str() { + // Safety: Just an FFI function. + unsafe { + crate::bridge::SetValueString(self.as_mut(), origin, s); + } + } else if let Some(b) = val.unpack_bool() { + // Safety: Just an FFI function. + unsafe { + crate::bridge::SetValueBool(self.as_mut(), origin, b); + } + } else if let Some(i) = val.unpack_i32() { + // Safety: Just an FFI function. + unsafe { + crate::bridge::SetValueInt(self.as_mut(), origin, i64::from(i)); + } + } else if let Some(l) = ListRef::from_value(val) { + let mut slice: Slice<Self> = SliceAny { + // Safety: Just an FFI function. + ptr: unsafe { crate::bridge::SetValueList(self.as_mut(), origin, l.len()) }, + len: l.len(), + } + .into(); + for (el_pin, src) in slice.iter_mut().zip(l.iter()) { + el_pin.assign(src, scope, origin); + } + } else if let Some(s) = StructRef::from_value(val) { + let keys: Vec<&str> = s.iter().map(|(k, _)| k.as_str()).collect(); + let (nested_scope, mut values) = Scope::new(scope, &keys); + + for (v_starlark, v_cxx) in s.iter().map(|(_, v)| v).zip(values.as_slice_mut()) { + v_cxx.as_mut().assign(v_starlark, scope, origin); + } + + // Safety: Just an FFI function. + unsafe { + crate::bridge::SetValueScope(self.as_mut(), origin, nested_scope); + } + } else { + todo!("Arbitrary starlark values not (yet) supported"); + } + } +} + +#[cfg(test)] +mod tests { + use starlark::values::FrozenHeap; + + use super::*; + use crate::TestWithScope; + + fn back_and_forth<'v>( + heap: &FrozenHeap, + val: starlark::values::Value<'v>, + ) -> starlark::values::Value<'v> { + let mut setup = TestWithScope::new(); + let scope = setup.scope(); + + let mut value = crate::bridge::NewValueForTesting(); + value.pin_mut().assign(val, scope, std::ptr::null()); + value.to_rust(heap).to_value() + } + + #[test] + fn test_none_conversion() { + let heap = FrozenHeap::new(); + assert!(back_and_forth(&heap, FrozenValue::new_none().to_value()).is_none()); + } + + #[test] + fn test_bool_conversion() { + let heap = FrozenHeap::new(); + assert_eq!( + back_and_forth(&heap, heap.alloc(true).to_value()).unpack_bool(), + Some(true) + ); + assert_eq!( + back_and_forth(&heap, heap.alloc(false).to_value()).unpack_bool(), + Some(false) + ); + } + + #[test] + fn test_int_conversion() { + let heap = FrozenHeap::new(); + assert_eq!( + back_and_forth(&heap, heap.alloc(123456789i32).to_value()).unpack_i32(), + Some(123456789) + ); + } + + #[test] + fn test_string_conversion() { + let heap = FrozenHeap::new(); + assert_eq!( + back_and_forth(&heap, heap.alloc("hello world").to_value()).unpack_str(), + Some("hello world") + ); + assert_eq!( + back_and_forth( + &heap, + heap.alloc("hello long string without SSO optimizations") + .to_value(), + ) + .unpack_str(), + Some("hello long string without SSO optimizations") + ); + } + + #[test] + fn test_list_conversion() { + let heap = FrozenHeap::new(); + let list_ref = ListRef::from_value(back_and_forth( + &heap, + heap.alloc(vec![heap.alloc(42), heap.alloc("hello")]) + .to_value(), + )) + .unwrap(); + assert_eq!(list_ref.len(), 2); + let mut iter = list_ref.iter(); + assert_eq!(iter.next().unwrap().unpack_i32(), Some(42)); + assert_eq!(iter.next().unwrap().unpack_str(), Some("hello")); + } + + #[test] + fn test_struct_conversion() { + let heap = FrozenHeap::new(); + let struct_ref = StructRef::from_value(back_and_forth( + &heap, + heap.alloc(starlark::values::structs::AllocStruct(vec![ + ("foo", heap.alloc(100)), + ("bar", heap.alloc("baz")), + ])) + .to_value(), + )) + .unwrap(); + let get_field = |name: &str| { + struct_ref + .iter() + .find(|(k, _)| k.as_str() == name) + .map(|(_, v)| v) + }; + assert_eq!(get_field("foo").unwrap().unpack_i32(), Some(100)); + assert_eq!(get_field("bar").unwrap().unpack_str(), Some("baz")); + } +}
diff --git a/src/gn/value.h b/src/gn/value.h index d963447..ec4cb5a 100644 --- a/src/gn/value.h +++ b/src/gn/value.h
@@ -36,7 +36,7 @@ // Represents a variable value in the interpreter. class Value { public: - enum Type { + enum Type : uint8_t { NONE = 0, BOOLEAN, INTEGER,