Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions build/deps/rust.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ crate.annotation(
crate = "capnpc",
gen_binaries = ["capnpc-rust"],
)
crate.annotation(
# rust_icu's checked-in bindings and symbol-renaming macro are selected by this value. Keep it
# synchronized with Chromium ICU's U_ICU_VERSION_MAJOR_NUM in build/deps/gen/deps.MODULE.bazel.
build_script_env = {"BAZEL": "1"},
crate = "rust_icu_sys",
rustc_env = {"RUST_ICU_MAJOR_VERSION_NUMBER": "78"},
)
crate.from_cargo(
# TODO(cleanup): This name is a bit misleading now that we don't vendor. We can clean it up as a
# followup.
Expand Down
3 changes: 3 additions & 0 deletions build/wd_rust_crate.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def wd_rust_crate(
deps = [],
link_deps = [],
proc_macro_deps = [],
rustc_env = {},
data = [],
test_env = {},
test_tags = [],
Expand Down Expand Up @@ -142,6 +143,7 @@ def wd_rust_crate(
srcs = srcs,
deps = deps,
link_deps = link_deps + ["@@//deps:rust_runtime"],
rustc_env = rustc_env,
visibility = visibility,
data = data,
proc_macro_deps = proc_macro_deps,
Expand All @@ -159,6 +161,7 @@ def wd_rust_crate(
size = test_size,
tags = test_tags,
crate_features = crate_features,
rustc_env = rustc_env,
deps = test_deps,
proc_macro_deps = test_proc_macro_deps,
)
Expand Down
35 changes: 35 additions & 0 deletions deps/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions deps/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ nix = "0"
pico-args = "0"
ruff_python_ast = { git = "https://github.com/astral-sh/ruff", tag = "0.12.1" }
ruff_python_parser = { git = "https://github.com/astral-sh/ruff", tag = "0.12.1" }
# Use rust_icu's checked-in bindings for the Chromium ICU version linked by workerd. ICU itself is
# supplied by Bazel, so bindgen, icu-config, and rust_icu's library-linking features stay disabled.
rust_icu_sys = { version = "5.8", default-features = false, features = ["renaming", "icu_version_in_env"] }
# param_extractor depends on unbounded_depth feature
serde_json = { version = "1", features = ["unbounded_depth"] }
serde = { version = "1", features = ["derive"] }
Expand Down
9 changes: 9 additions & 0 deletions src/rust/cxx/PATCHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,14 @@ The in-tree fork contains these major behavioral changes:
`kj::Maybe`, `kj::Date`, exceptions, promises, and Rust futures.
- **`__WORKERD_CXX__`.** This preprocessor definition identifies the fork even though its crate and
generated symbol names remain compatible with cxx.
- **`c_char16`.** An additional builtin type, spelled `char16_t` in C++, for UTF-16 data. `u16`
means `uint16_t`, which C++ mangles and overloads separately from `char16_t`, so a bridge that
must name `char16_t` cannot use `u16`. The Rust side is an alias for `u16`, following `c_char`,
so no cast is needed at call sites; see the doc comment on `cxx::c_char16` for the limits that
follow from being an alias.
- **Unqualified emission of C++ fundamental types.** A `Pair` whose name is a fundamental type and
whose namespace is empty is written bare rather than with a leading `::`, because those names are
keywords: `::char16_t` is ill-formed where `::uint16_t` is fine. This lets an `ExternType` alias
bind a fundamental type that has no builtin spelling in a bridge.
- **Workerd build integration.** Bazel targets use workerd's in-tree Rust toolchain, crate vendor
repository, Cap'n Proto/KJ dependency, formatting, and lint configuration.
5 changes: 4 additions & 1 deletion src/rust/cxx/gen/src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
Some(Isize) => out.builtin.rust_isize = true,
Some(CxxString) => out.include.string = true,
Some(RustString) => out.builtin.rust_string = true,
Some(Bool | Char | F32 | F64) | None => {}
// bool, char, char16_t, float and double are C++ keywords; no
Comment thread
logan-gatlin marked this conversation as resolved.
// header supplies them.
Some(Bool | Char | Char16 | F32 | F64) | None => {}
},
Type::RustBox(_) => out.builtin.rust_box = true,
Type::RustVec(_) => out.builtin.rust_vec = true,
Expand Down Expand Up @@ -1374,6 +1376,7 @@ fn write_atom(out: &mut OutFile, atom: Atom) {
match atom {
Bool => write!(out, "bool"),
Char => write!(out, "char"),
Char16 => write!(out, "char16_t"),
U8 => write!(out, "::std::uint8_t"),
U16 => write!(out, "::std::uint16_t"),
U32 => write!(out, "::std::uint32_t"),
Expand Down
12 changes: 12 additions & 0 deletions src/rust/cxx/src/cxx.cc
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,14 @@ static_assert(sizeof(rust::isize) == sizeof(std::intptr_t),
static_assert(alignof(rust::isize) == alignof(std::intptr_t),
"unsupported ssize_t alignment");

// The cxx crate spells char16_t as `c_char16`, an alias for u16. char16_t's
// underlying type is uint_least16_t, which the standard permits to be wider
// than 16 bits; on such a target the alias would misdescribe the C++ type.
static_assert(sizeof(char16_t) == sizeof(std::uint16_t),
"unsupported char16_t size");
static_assert(alignof(char16_t) == alignof(std::uint16_t),
"unsupported char16_t alignment");

static_assert(std::is_trivially_copy_constructible<Str>::value,
"trivial Str(const Str &)");
static_assert(std::is_trivially_copy_assignable<Str>::value,
Expand Down Expand Up @@ -827,10 +835,14 @@ static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *),
FOR_EACH_TRIVIAL_STD_VECTOR(MACRO) \
MACRO(string, std::string)

// char16_t needs no _if_unique guard: [basic.fundamental] makes it a distinct
// type from every other fundamental type, so rust::Vec<char16_t> can never
// collide with another specialization.
#define FOR_EACH_RUST_VEC(MACRO) \
FOR_EACH_NUMERIC(MACRO) \
MACRO(bool, bool) \
MACRO(char, rust::detail::char_if_unique) \
MACRO(char16_t, char16_t) \
MACRO(usize, rust::detail::usize_if_unique) \
MACRO(isize, rust::detail::isize_if_unique) \
MACRO(string, rust::String) \
Expand Down
20 changes: 20 additions & 0 deletions src/rust/cxx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,26 @@ pub type String = CxxString;
/// import and use `CxxVector`.
pub type Vector<T> = CxxVector<T>;

/// Rust spelling of C++'s `char16_t`, for UTF-16 data crossing the bridge.
///
/// `char16_t` and `uint16_t` are layout-identical but distinct C++ types, so
/// C++ mangles them differently and overloads on them separately. `u16` in a
/// bridge means `uint16_t`; use `c_char16` where the signature must name
/// `char16_t`, which is the usual case for UTF-16 buffers.
///
/// This is a plain alias rather than a newtype, following `c_char`, so a
/// `&[c_char16]` argument accepts a `&[u16]` from `str::encode_utf16` or
/// `encoding_rs` with no cast. The alias means `c_char16` and `u16` are one
/// Rust type; only their C++ spelling differs. The two therefore cannot both
/// be used as the element type of a `CxxVector` in the same program, and
/// `CxxVector<c_char16>` is rejected for the same reason `CxxVector<c_char>`
/// is.
///
/// `char16_t` is guaranteed by the C++ standard to be a distinct type whose
/// underlying type is `uint_least16_t`. `cxx.cc` asserts that it really is 16
/// bits wide on the target, which is what makes this alias sound.
pub type c_char16 = u16;

// Not public API.
#[doc(hidden)]
pub mod private {
Expand Down
5 changes: 5 additions & 0 deletions src/rust/cxx/src/symbols/rust_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use core::ffi::c_char;
use core::mem;
use core::ptr;

use crate::c_char16;
use crate::rust_string::RustString;
use crate::rust_vec::RustVec;

Expand Down Expand Up @@ -78,5 +79,9 @@ rust_vec_shims_for_primitive!(f32);
rust_vec_shims_for_primitive!(f64);

rust_vec_shims!("char", c_char);
// c_char16 is an alias for u16, so these duplicate the u16 shims above under a
// second set of export names. The C++ side reaches them through
// rust::Vec<char16_t>, which mangles to the char16_t segment.
rust_vec_shims!("char16_t", c_char16);
rust_vec_shims!("string", RustString);
rust_vec_shims!("str", &str);
6 changes: 6 additions & 0 deletions src/rust/cxx/syntax/atom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ use crate::Type;
pub enum Atom {
Bool,
Char, // C char, not Rust char
// C++ char16_t. Layout-identical to u16 but a distinct C++ type, so it is a
// distinct atom: it exists to make overload resolution and name mangling on
// the C++ side pick char16_t rather than uint16_t.
Char16,
U8,
U16,
U32,
Expand All @@ -35,6 +39,7 @@ impl Atom {
match s {
"bool" => Some(Bool),
"c_char" => Some(Char),
"c_char16" => Some(Char16),
"u8" => Some(U8),
"u16" => Some(U16),
"u32" => Some(U32),
Expand Down Expand Up @@ -66,6 +71,7 @@ impl AsRef<str> for Atom {
match self {
Bool => "bool",
Char => "c_char",
Char16 => "c_char16",
U8 => "u8",
U16 => "u16",
U32 => "u32",
Expand Down
12 changes: 7 additions & 5 deletions src/rust/cxx/syntax/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ fn check_type_rust_vec(cx: &mut Check, ty: &Ty1) {
match Atom::from(&ident.rust) {
None
| Some(
Bool | Char | U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize | F32
| F64 | RustString,
Bool | Char | Char16 | U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64
| Isize | F32 | F64 | RustString,
) => return,
Some(CxxString) => {}
}
Expand Down Expand Up @@ -266,7 +266,7 @@ fn check_type_shared_ptr(cx: &mut Check, ptr: &Ty1) {
Bool | U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize | F32 | F64
| CxxString,
) => return,
Some(Char | RustString) => {}
Some(Char | Char16 | RustString) => {}
}
} else if let Type::CxxVector(_) = &ptr.inner {
cx.error(ptr, "std::shared_ptr<std::vector> is not supported yet");
Expand All @@ -289,7 +289,7 @@ fn check_type_weak_ptr(cx: &mut Check, ptr: &Ty1) {
Bool | U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize | F32 | F64
| CxxString,
) => return,
Some(Char | RustString) => {}
Some(Char | Char16 | RustString) => {}
}
} else if let Type::CxxVector(_) = &ptr.inner {
cx.error(ptr, "std::weak_ptr<std::vector> is not supported yet");
Expand Down Expand Up @@ -363,7 +363,7 @@ fn check_type_cxx_vector(cx: &mut Check, ptr: &Ty1) {
| Some(
U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize | F32 | F64 | CxxString,
) => return,
Some(Char) => { /* todo */ }
Some(Char | Char16) => { /* todo */ }
Some(Bool | RustString) => {}
}
}
Expand Down Expand Up @@ -900,6 +900,8 @@ fn describe(cx: &mut Check, ty: &Type) -> String {
"C++ string".to_owned()
} else if Atom::from(&ident.rust) == Some(Char) {
"C char".to_owned()
} else if Atom::from(&ident.rust) == Some(Char16) {
"C++ char16_t".to_owned()
} else {
ident.rust.to_string()
}
Expand Down
38 changes: 38 additions & 0 deletions src/rust/cxx/syntax/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ impl Pair {
}

pub fn to_fully_qualified(&self) -> String {
// A fundamental type's name is a keyword, not something declared in a
// scope, so it is spelled bare: `::char16_t` is ill-formed where
// `::uint16_t` is fine. Reaching this with a non-empty namespace means
// the name was not really a fundamental type, so leave it qualified
// and let C++ report it.
if self.namespace.is_empty() && self.cxx.is_fundamental() {
return self.cxx.to_string();
}

let mut fully_qualified = String::new();
for segment in &self.namespace {
fully_qualified += "::";
Expand Down Expand Up @@ -72,6 +81,35 @@ impl ForeignName {
Err(err) => Err(Error::new(span, err)),
}
}

/// True if this names a C++ fundamental type.
///
/// These are the only C++ type names that are keywords, and so the only
/// ones that cannot be written with a `::` qualification. Fixed-width
/// names like `uint16_t` are ordinary typedefs, not keywords, and are
/// deliberately absent.
///
/// Multi-token spellings such as `unsigned int` are absent because
/// [`ForeignName::parse`] cannot represent a name containing whitespace.
pub fn is_fundamental(&self) -> bool {
matches!(
self.text.as_str(),
"bool"
| "char"
| "char8_t"
| "char16_t"
| "char32_t"
| "double"
| "float"
| "int"
| "long"
| "short"
| "signed"
| "unsigned"
| "void"
| "wchar_t"
)
}
}

impl Display for ForeignName {
Expand Down
5 changes: 5 additions & 0 deletions src/rust/cxx/syntax/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ impl Namespace {
self.segments.iter()
}

/// True for the global namespace.
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}

pub fn parse_bridge_attr_namespace(input: ParseStream) -> Result<Self> {
if input.is_empty() {
return Ok(Self::ROOT);
Expand Down
4 changes: 2 additions & 2 deletions src/rust/cxx/syntax/pod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ impl Types<'_> {
let ident = &ident.rust;
if let Some(atom) = Atom::from(ident) {
match atom {
Bool | Char | U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64
| Isize | F32 | F64 => true,
Bool | Char | Char16 | U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32
| I64 | Isize | F32 | F64 => true,
CxxString | RustString => false,
}
} else if let Some(strct) = self.structs.get(ident) {
Expand Down
5 changes: 5 additions & 0 deletions src/rust/cxx/syntax/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ impl ToTokens for Type {
if ident.rust == Char {
let span = ident.rust.span();
tokens.extend(quote_spanned!(span=> ::cxx::core::ffi::));
} else if ident.rust == Char16 {
// Unlike c_char, there is no core::ffi equivalent of
// char16_t; the cxx crate defines c_char16 itself.
let span = ident.rust.span();
tokens.extend(quote_spanned!(span=> ::cxx::));
} else if ident.rust == CxxString {
let span = ident.rust.span();
tokens.extend(quote_spanned!(span=> ::cxx::));
Expand Down
Loading
Loading