Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elide types with one FFI representation #178

Merged
merged 1 commit into from
Feb 24, 2023
Merged

Conversation

chinedufn
Copy link
Owner

@chinedufn chinedufn commented Feb 24, 2023

This commit modifies our codegen to elide arguments and return types
that have exactly one FFI representation, such as () struct Foo; and
enum MyEnum { OneVariant }.

For example, the following bridge module:

#[swift_bridge::bridge]
mod ffi {
    struct UnitStruct1;
    struct UnitStruct2{};
    struct UnitStruct3();

    extern "Rust" {
        fn some_function(
            arg1: UnitStruct1,
            arg2: UnitStruct2,
            arg3: UnitStruct3,
        ) -> UnitStruct1;
    }
}

Will now generate the following foreign function interface:

#[link_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() {
    {super::some_function(UnitStruct1, UnitStruct2, UnitStruct3); ()}
}

Notice that the extern function does not take any arguments.

This is invisible to the user, since they'll call a function that looks
like:

// Swift (generated code)

func some_function(arg1: UnitStruct1, arg2: UnitStruct2, arg3: UnitStruct3) -> UnitStruct1 {
    { let _ = __swift_bridge__$some_function(); UnitStruct1() }()
}

Zero-sized types are not C FFI safe, so bridging them
requires some degree of shenanigans.

Before this commit we would bridge unit structs using an unused u8.
This meant that we would waste one byte of memory per bridged
transparent struct instance.

Now, to be fair, this was fairly tiny overhead, and an unimaginably
small number of users should ever have a use case where they're bridging
enough empty structs for a single byte of overhead per struct to be
noticed.

But, where's the fun in wasted memory?

As of this commit unit structs and other types that have a single
possible FFI representation have a 0 byte FFI representation since
we don't bridge them at all and they instead just "appear" on the
other side of the boundary.


While implementing this change we added a
.has_exactly_one_representation(&self) -> bool method to the
BridgeableType trait.

This will be useful in the future for other use cases where knowing that
a type has exactly one FFI representation is important.

For example, we plan to implement support for Result<(), E> in the future.
When E is a type that is bridged via a pointer, Result<(), E> can be
bridged using a single pointer, since the T = () case can be encoded as
a null pointer.

This generalizes for any T where T is a type that has exactly one
FFI representation.

Notably, this includes single variant enums such as
MyEnum { OneVariant }.

Although, perhaps not that notably, since I'm still not sure whether
anyone will have an FFI use case that involves bridging single-repr
types.

Who knows. Developers will surprise you.

This commit modifies our codegen to elide arguments and return types
that have exactly one FFI representation, such as `()` `struct Foo;` and
`enum MyEnum { OneVariant }`.

For example, the following bridge module:

```rust
#[swift_bridge::bridge]
mod ffi {
    struct UnitStruct1;
    struct UnitStruct2{};
    struct UnitStruct3();

    extern "Rust" {
        fn some_function(
            arg1: UnitStruct1,
            arg2: UnitStruct2,
            arg3: UnitStruct3,
        ) -> UnitStruct1;
    }
}
```

Will now generate the following foreign function interface:

```rust
#[link_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() {
    {super::some_function(UnitStruct1, UnitStruct2, UnitStruct3); ()}
}
```

Notice that the extern function does not take any arguments.

This is invisible to the user, since they'll call a function that looks
like:

```swift
// Swift (generated code)

func some_function(arg1: UnitStruct1, arg2: UnitStruct2, arg3: UnitStruct3) -> UnitStruct1 {
    { let _ = __swift_bridge__$some_function(); UnitStruct1 }()
}
```

---

Zero-sized types are not C FFI safe, so bridging them
requires some degree of shenanigans.

Before this commit we would bridge unit structs using an unused `u8`.
This meant that we would waste one byte of memory per bridged
transparent struct instance.

Now, to be fair, this was fairly tiny overhead, and an unimaginably
small number of users should ever have a use case where they're bridging
enough empty structs for a single byte of overhead per struct to be
noticed.

But, where's the fun in wasted memory?

As of this commit unit structs and other types that have a single
possible FFI representation have a 0 byte FFI representation since
we don't bridge them at all and they instead just "appear" on the
other side of the boundary.

---

While implementing this change we added a
`.has_exactly_one_representation(&self) -> bool` method to the
`BridgeableType` trait.

This will be useful in the future for other use cases where knowing that
a type has exactly one FFI representation is important.

For example, we plan to implement support for `Result<(), E>` in the future.
When `E` is a type that is bridged via a pointer, `Result<(), E>` can be
bridged using a single pointer, since the `T = ()` case can be encoded as
a null pointer.

This generalizes for any `T` where `T` is a type that has exactly one
FFI representation.

Notably, this includes single variant enums such as
`MyEnum { OneVariant }`.

Although, perhaps not that notably, since I'm still not sure whether
anyone will have an FFI use case that involves bridging single-repr
types.

Who knows. Developers will surprise you.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant