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
1 change: 1 addition & 0 deletions crates/libs/bindgen/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub struct Config<'a> {
pub implement: bool,
pub implements: &'a Implements,
pub noexcept: bool,
pub middleware: bool,
pub specific_deps: bool,
pub derive: &'a Derive,
pub link: &'a str,
Expand Down
59 changes: 59 additions & 0 deletions crates/libs/bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub fn builder() -> Bindgen {
/// | `--implement` | Includes implementation traits for WinRT interfaces. |
/// | `--implements` | Includes implementation traits for the listed types only. |
/// | `--noexcept` | Assumes all WinRT methods do not raise exceptions. |
/// | `--middleware` | Emits compact bodies that tail-call into `windows_core::imp` helpers. |
/// | `--link` | Overrides the default `windows-link` implementation for system calls. |
///
///
Expand Down Expand Up @@ -308,6 +309,40 @@ pub fn builder() -> Bindgen {
/// need fine-grained dependency management and want to minimize your dependency tree, this option
/// provides that flexibility.
///
/// # `--middleware`
///
/// The `--middleware` option emits compact `pub fn` bodies that tail-call into a small set
/// of generic helpers in `windows_core::imp` (`call_in`, `call_in_out`, `call_compose`)
/// instead of expanding the full
/// `unsafe { let mut result__ = zeroed(); (vtable(self).M)(...).and_then(...) }` shape
/// inline at every call site. Public signatures are byte-identical; only the body shape
/// changes.
///
/// The helpers cover every WinRT receiver shape that `bindgen` produces for
/// `Result<T>`-returning methods:
///
/// * `Default` arm — `&self` instance methods (`call_in` / `call_in_out`).
/// * `None` / `Base` arm — instance methods reached via a `cast::<…>` prelude
/// (`call_in` / `call_in_out`, with `this` as receiver).
/// * `Static` arm — class statics dispatched through `Self::IFoo(|this| { … })`
/// (`call_in` / `call_in_out`, with `this` as receiver).
/// * `Composable` primary entry — non-aggregating constructors (`call_in_out`, with the
/// two outer/inner null placeholder slots spliced into the helper's argument tail to
/// keep the ABI byte-identical).
/// * `Composable` `_compose` aggregating entry — `call_compose`, which performs the
/// `Compose::compose` / vtable dispatch / `let _ = &derived__;` keep-alive /
/// `T::from_abi` sequence in one place.
///
/// Shapes that don't fit a helper (`noexcept`-style infallible returns, WinRT array
/// returns, `CloneType` returns such as `HSTRING`, bare `Type::Generic(_)` returns where
/// the impl block does not bound `<T as Type<T>>::Abi: Default`, the C++/Win32 surface)
/// fall back to today's inline expansion automatically, so the flag is always safe to
/// enable.
///
/// This is intended for "middleware" / "reactor" callers (such as `windows-reactor-rs`) that
/// forward calls through curated WinRT surfaces and want to minimize `bindings.rs` byte size,
/// LLVM IR size, and `MIR_borrow_checking` time. Default emission is unchanged.
///
#[track_caller]
#[must_use]
pub fn bindgen<I, S>(args: I) -> Warnings
Expand Down Expand Up @@ -370,6 +405,9 @@ where
"--noexcept" => {
builder.noexcept();
}
"--middleware" => {
builder.middleware();
}
"--specific-deps" => {
builder.specific_deps();
}
Expand Down Expand Up @@ -452,6 +490,7 @@ pub struct Bindgen {
package: bool,
implement: bool,
noexcept: bool,
middleware: bool,
specific_deps: bool,
sys: bool,
typedef: bool,
Expand Down Expand Up @@ -641,6 +680,25 @@ impl Bindgen {
self
}

/// Emit compact, helper-based call-site bodies for the shapes that fit.
///
/// When set, generated WinRT method bodies that match the canonical
/// shape (interface return or copyable scalar return, no winrt array,
/// `Result`-typed signature, `Default` interface kind) become a single
/// tail call into `windows_core::imp::call_in_out` / `call_in`. Public
/// signatures are byte-identical; only the body shape changes. Shapes
/// that don't fit a helper fall back to today's inline expansion, so
/// this flag is always safe to enable.
///
/// Default emission (without this flag) is unchanged. This is intended
/// for "middleware" / "reactor" callers that need to minimize
/// `bindings.rs` size and codegen cost; see the project documentation
/// for guidance.
pub fn middleware(&mut self) -> &mut Self {
self.middleware = true;
self
}

/// Use specific crate dependencies rather than `windows-core`.
pub fn specific_deps(&mut self) -> &mut Self {
self.specific_deps = true;
Expand Down Expand Up @@ -824,6 +882,7 @@ impl Bindgen {
implement: self.implement,
implements: &implements,
noexcept: self.noexcept,
middleware: self.middleware,
specific_deps: self.specific_deps,
link,
warnings: &warnings,
Expand Down
129 changes: 117 additions & 12 deletions crates/libs/bindgen/src/types/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,20 @@ impl Method {
}
};

// typed_args without the trailing return out-pointer; used by middleware-mode
// helper emission where the helper supplies the out-pointer slot itself. The
// trailing `,` is preserved when non-empty so it splices cleanly before the
// helper-supplied `result__` argument. For Composable's primary (non-aggregating)
// entry the two null placeholder slots (outer + inner) are spliced in here so
// the helper-emitted call site retains the same ABI.
let typed_args_only: TokenStream = if kind == InterfaceKind::Composable {
quote! { #(#typed_args,)* core::ptr::null_mut(), &mut core::ptr::null_mut(), }
} else if typed_args.is_empty() {
TokenStream::new()
} else {
quote! { #(#typed_args,)* }
};

let args = if kind == InterfaceKind::Composable {
// Composable factory methods take the `outer` controlling unknown and the
// out-pointer for the `inner` non-delegating object as their last two
Expand Down Expand Up @@ -537,9 +551,73 @@ impl Method {
// given `args` token stream. This is wrapped in a closure so we can build a parallel
// `_compose` variant for composable factory methods without duplicating the
// return-type plumbing below.
let build_vcall = |args: &TokenStream| -> TokenStream {
//
// `typed_args_only` contains the in-parameter expressions without the trailing
// out-pointer (or `null_mut()` placeholders for composable factories). When
// middleware mode is enabled and the return shape fits one of the
// `windows_core::imp::call_*` helpers, we emit a tail call into the helper using
// `typed_args_only` so the helper supplies the `result__` slot itself. Otherwise
// we fall back to the existing inline expansion using `args` (which already
// splices in the trailing out-pointer).
let build_vcall = |args: &TokenStream, typed_args_only: &TokenStream| -> TokenStream {
let vcall = quote! { (windows_core::Interface::vtable(#receiver).#vname)(windows_core::Interface::as_raw(#receiver), #args) };

// Middleware-mode helper emission. Only applies when:
// * `--middleware` is set,
// * the method does not use `noexcept`-style infallible return,
// * the return is not a WinRT array.
//
// The helper covers all four `kind`s that go through `build_vcall`:
//
// * `Default` (`#receiver = self`),
// * `None` / `Base` (`#receiver = this`, after a `cast::<…>` prelude),
// * `Static` (`#receiver = this`, inside the `Self::IFoo(|this| { … })`
// factory closure),
// * `Composable` primary (non-aggregating) entry (`#receiver = this`,
// inside the factory closure; the two outer/inner null placeholder
// slots are spliced into `typed_args_only` above).
//
// The `Composable` aggregating `_compose` variant goes through a
// dedicated `call_compose` helper emitted in the `InterfaceKind::Composable`
// arm below — it is *not* produced by this closure.
//
// It covers `Result<T>` interface returns (Abi = *mut c_void) and
// `Result<T>` copyable returns (Abi = T) via the `Default`-bounded
// `<T as Type<T>>::Abi` slot in `call_in_out`. The "transmute" branch
// (CloneType such as HSTRING, where Abi = MaybeUninit<T>) is intentionally
// not covered — it falls back to the inline expansion below.
let middleware_eligible =
config.middleware && !noexcept && !self.signature.return_type.is_winrt_array();

if middleware_eligible {
match &self.signature.return_type {
Type::Void => {
return quote! {
windows_core::imp::call_in(#receiver, |this__|
(windows_core::Interface::vtable(#receiver).#vname)(this__, #typed_args_only))
};
}
// `Type::Generic(_)` (a bare generic parameter such as `TResult`)
// is excluded: the helper requires `<T as Type<T>>::Abi: Default`,
// which isn't implied by the typical `T: RuntimeType + 'static`
// bound the generated impl block carries. Fall back to inline.
Type::Generic(_) => {}
return_type
if return_type.is_convertible()
|| return_type.is_copyable(config.reader) =>
{
// `call_in_out` requires `<T as Type<T>>::Abi: Default`. This
// holds for interface (`Abi = *mut c_void`) and copyable
// (`Abi = T` for primitives/enums/BOOL/HANDLE) returns.
return quote! {
windows_core::imp::call_in_out(#receiver, |this__, result__|
(windows_core::Interface::vtable(#receiver).#vname)(this__, #typed_args_only result__))
};
}
_ => {} // CloneType etc. — fall through to inline expansion.
}
}

match &self.signature.return_type {
Type::Void => {
if noexcept {
Expand Down Expand Up @@ -598,7 +676,7 @@ impl Method {
}
};

let vcall = build_vcall(&args);
let vcall = build_vcall(&args, &typed_args_only);

match kind {
InterfaceKind::Default => quote! {
Expand Down Expand Up @@ -663,22 +741,49 @@ impl Method {
// left with a dangling reference into the inner's vtables.
let interface_name = to_ident(trim_tick(interface.unwrap().def.name()));

// `_compose` body. In `--middleware` mode and when the return shape
// fits (interface or copyable scalar — see same eligibility rules as
// the `Default` arm), we collapse the body into a single tail call
// through `windows_core::imp::call_compose`, which performs the
// `Compose::compose` / vtable dispatch / `let _ = &derived__;`
// keep-alive / `from_abi` sequence in one place.
//
// Otherwise (or when middleware mode is off), emit the original
// inline expansion below.
let compose_eligible = config.middleware
&& !noexcept
&& !self.signature.return_type.is_winrt_array()
&& !matches!(&self.signature.return_type, Type::Generic(_))
&& (self.signature.return_type.is_convertible()
|| self.signature.return_type.is_copyable(config.reader));

let compose_body = if compose_eligible {
quote! {
windows_core::imp::call_compose(this, compose, |this__, derived__, base__, result__|
(windows_core::Interface::vtable(this).#vname)(this__, #(#typed_args,)* derived__, base__, result__))
}
} else {
quote! {
let (derived__, base__) = windows_core::Compose::compose(compose);
let mut result__ = core::mem::zeroed();
(windows_core::Interface::vtable(#receiver).#vname)(windows_core::Interface::as_raw(#receiver), #compose_args).ok()?;
// Suppress unused-variable warning: `derived__` is held alive
// for the duration of the factory call (so the factory can
// store a back-pointer to the outer in the inner) and then
// dropped at scope end — its sole owning ref is replaced by
// the delegating ref baked into `result__`.
let _ = &derived__;
windows_core::Type::from_abi(result__)
}
};

quote! {
pub fn #name<#(#generics,)*>(#(#params)*) #return_type #where_clause {
Self::#interface_name(|this| unsafe { #vcall })
}
pub fn #name_compose<#(#generics,)* T>(#(#params)* compose: T) #return_type #where_clause_compose {
Self::#interface_name(|this| unsafe {
let (derived__, base__) = windows_core::Compose::compose(compose);
let mut result__ = core::mem::zeroed();
(windows_core::Interface::vtable(#receiver).#vname)(windows_core::Interface::as_raw(#receiver), #compose_args).ok()?;
// Suppress unused-variable warning: `derived__` is held alive
// for the duration of the factory call (so the factory can
// store a back-pointer to the outer in the inner) and then
// dropped at scope end — its sole owning ref is replaced by
// the delegating ref baked into `result__`.
let _ = &derived__;
windows_core::Type::from_abi(result__)
#compose_body
})
}
}
Expand Down
Loading