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

Make extern Swift functions public to prevent stripping in Release builds #262

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-fails/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
generated/*
.build/*
target/*
Cargo.lock
TestReleaseFails/*
20 changes: 20 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-fails/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "test-release-fails"
version = "0.1.0"
edition = "2021"
publish = []

build = "build.rs"

[lib]
crate-type = ["staticlib"]

[build-dependencies]
swift-bridge-build = { git = "https://github.com/chinedufn/swift-bridge.git", branch = "master" }

[dependencies]
swift-bridge = { git = "https://github.com/chinedufn/swift-bridge.git", branch = "master", features = [
"async",
] }

[workspace]
26 changes: 26 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-fails/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// swift-tools-version:5.5.0
import PackageDescription
let package = Package(
name: "TestReleaseFails",
products: [
.library(
name: "TestReleaseFails",
targets: ["TestReleaseFails"]),
.executable(
name: "TestReleaseFailsRunner",
targets: ["TestReleaseFailsRunner"]),
],
dependencies: [],
targets: [
.binaryTarget(
name: "RustXcframework",
path: "RustXcframework.xcframework"
),
.target(
name: "TestReleaseFails",
dependencies: ["RustXcframework"]),
.executableTarget(
name: "TestReleaseFailsRunner",
dependencies: ["TestReleaseFails"])
]
)
7 changes: 7 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-fails/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Test Release Builds Fail
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's document:

  • Why it would infer that they were dead code (as in, mention func vs. public func)

  • show an example of a problematic cdecl function. Explain that we now prepend public.

    @_cdecl("__swift_bridge__$add") 
    func __swift_bridge__add (ptr: UnsafeMutableRawPointer) { /* .. */ }
    • This will help orient the reader and give them a better understanding of what we're testing
  • How the test works. How are we accomplishing what you've explained here? Maybe explain how the test works step by step.

  • How to run the test.

  • Mention that this test gets ran during test-swift-rust-integration.sh

All of this will help future maintainers understand what's going on and be in a better position to make changes/improvements to it.


This test is a reproduction case for when building for release,
previously as `extern "Swift"` functions were `internal`
the Swift compiler would strip these, as it would infer
that they were dead code, even though these need to be accessible
from the Rust side of the FFI.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

func add(lhs: Int32, rhs: Int32) -> Int32 {
lhs + rhs
}
13 changes: 13 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-fails/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::path::PathBuf;

fn main() {
let out_dir = PathBuf::from("./generated");

let bridges = vec!["src/lib.rs"];
for path in &bridges {
println!("cargo:rerun-if-changed={}", path);
}

swift_bridge_build::parse_bridges(bridges)
.write_all_concatenated(out_dir, env!("CARGO_PKG_NAME"));
}
44 changes: 44 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-fails/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash

PACKAGE_NAME=test-release-fails
LIBRARY_NAME=test_release_fails
SWIFT_PACKAGE_NAME=TestReleaseFails
EXECUTABLE_TARGET_NAME="$SWIFT_PACKAGE_NAME"Runner

set -e

THISDIR=$(dirname $0)
cd $THISDIR

echo "Building for macOS..."
rustup target add x86_64-apple-darwin aarch64-apple-darwin
cargo build --target x86_64-apple-darwin --release
cargo build --target aarch64-apple-darwin --release
mkdir -p ./target/universal-macos/release
lipo \
./target/aarch64-apple-darwin/release/lib$LIBRARY_NAME.a \
./target/x86_64-apple-darwin/release/lib$LIBRARY_NAME.a -create -output \
./target/universal-macos/release/lib$LIBRARY_NAME.a

function create_package {
swift-bridge-cli create-package \
--bridges-dir ./generated \
--out-dir $SWIFT_PACKAGE_NAME \
--ios ./target/universal-macos/release/lib$LIBRARY_NAME.a \
--name $SWIFT_PACKAGE_NAME
}

function patch_package {
cp Package.swift $SWIFT_PACKAGE_NAME/Package.swift
mkdir -p $SWIFT_PACKAGE_NAME/Sources/$EXECUTABLE_TARGET_NAME
cp main.swift $SWIFT_PACKAGE_NAME/Sources/$EXECUTABLE_TARGET_NAME/main.swift
cp SwiftExterns.swift $SWIFT_PACKAGE_NAME/Sources/$SWIFT_PACKAGE_NAME/SwiftExterns.swift
}

echo "Creating Swift package..."
create_package
patch_package

echo "Building for release..."
cd $SWIFT_PACKAGE_NAME
xcodebuild archive -scheme $EXECUTABLE_TARGET_NAME -archivePath ./build/$EXECUTABLE_TARGET_NAME.xcarchive -destination "platform=macOS"
4 changes: 4 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-fails/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Foundation
import TestReleaseFails

call_swift_add()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call_rust() to make it clear that we're calling into Rust.

Right now it seems like we're calling a Swift function.

14 changes: 14 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-fails/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
fn call_swift_add();
}

extern "Swift" {
fn add(lhs: i32, rhs: i32) -> i32;
}
}

fn call_swift_add() {
assert!(ffi::add(1, 1) == 2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
generated/*
.build/*
target/*
Cargo.lock
TestReleaseSucceeds/*
18 changes: 18 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-succeeds/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "test-release-succeeds"
version = "0.1.0"
edition = "2021"
publish = []

build = "build.rs"

[lib]
crate-type = ["staticlib"]

[build-dependencies]
swift-bridge-build = { path = "../../crates/swift-bridge-build" }

[dependencies]
swift-bridge = { path = "../../", features = ["async"] }

[workspace]
26 changes: 26 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-succeeds/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// swift-tools-version:5.5.0
import PackageDescription
let package = Package(
name: "TestReleaseSucceeds",
products: [
.library(
name: "TestReleaseSucceeds",
targets: ["TestReleaseSucceeds"]),
.executable(
name: "TestReleaseSucceedsRunner",
targets: ["TestReleaseSucceedsRunner"]),
],
dependencies: [],
targets: [
.binaryTarget(
name: "RustXcframework",
path: "RustXcframework.xcframework"
),
.target(
name: "TestReleaseSucceeds",
dependencies: ["RustXcframework"]),
.executableTarget(
name: "TestReleaseSucceedsRunner",
dependencies: ["TestReleaseSucceeds"])
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Test Release

This test makes sure that when building for release,
the Swift compiler doesn't strip functions that need to be accessible
from the Rust side of the FFI.
This test verifies that by making `extern "Swift"` functions `public`
release builds now succeed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

func add(lhs: Int32, rhs: Int32) -> Int32 {
lhs + rhs
}
13 changes: 13 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-succeeds/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::path::PathBuf;

fn main() {
let out_dir = PathBuf::from("./generated");

let bridges = vec!["src/lib.rs"];
for path in &bridges {
println!("cargo:rerun-if-changed={}", path);
}

swift_bridge_build::parse_bridges(bridges)
.write_all_concatenated(out_dir, env!("CARGO_PKG_NAME"));
}
44 changes: 44 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-succeeds/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash

PACKAGE_NAME=test-release-succeeds
LIBRARY_NAME=test_release_succeeds
SWIFT_PACKAGE_NAME=TestReleaseSucceeds
EXECUTABLE_TARGET_NAME="$SWIFT_PACKAGE_NAME"Runner

set -e

THISDIR=$(dirname $0)
cd $THISDIR

echo "Building for macOS..."
rustup target add x86_64-apple-darwin aarch64-apple-darwin
cargo build --target x86_64-apple-darwin --release
cargo build --target aarch64-apple-darwin --release
mkdir -p ./target/universal-macos/release
lipo \
./target/aarch64-apple-darwin/release/lib$LIBRARY_NAME.a \
./target/x86_64-apple-darwin/release/lib$LIBRARY_NAME.a -create -output \
./target/universal-macos/release/lib$LIBRARY_NAME.a

function create_package {
swift-bridge-cli create-package \
--bridges-dir ./generated \
--out-dir $SWIFT_PACKAGE_NAME \
--ios ./target/universal-macos/release/lib$LIBRARY_NAME.a \
--name $SWIFT_PACKAGE_NAME
}

function patch_package {
cp Package.swift $SWIFT_PACKAGE_NAME/Package.swift
mkdir -p $SWIFT_PACKAGE_NAME/Sources/$EXECUTABLE_TARGET_NAME
cp main.swift $SWIFT_PACKAGE_NAME/Sources/$EXECUTABLE_TARGET_NAME/main.swift
cp SwiftExterns.swift $SWIFT_PACKAGE_NAME/Sources/$SWIFT_PACKAGE_NAME/SwiftExterns.swift
}

echo "Creating Swift package..."
create_package
patch_package

echo "Building for release..."
cd $SWIFT_PACKAGE_NAME
xcodebuild archive -scheme $EXECUTABLE_TARGET_NAME -archivePath ./build/$EXECUTABLE_TARGET_NAME.xcarchive -destination "platform=macOS"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Foundation
import TestReleaseSucceeds

call_swift_add()
14 changes: 14 additions & 0 deletions SwiftRustIntegrationTestRunner/test-release-succeeds/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
fn call_swift_add();
}

extern "Swift" {
fn add(lhs: i32, rhs: i32) -> i32;
}
}

fn call_swift_add() {
assert!(ffi::add(1, 1) == 2);
}
1 change: 1 addition & 0 deletions crates/swift-bridge-ir/src/codegen/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ mod string_codegen_tests;
mod transparent_enum_codegen_tests;
mod transparent_struct_codegen_tests;
mod vec_codegen_tests;
mod visibility_codegen_tests;

struct CodegenTest {
bridge_module: BridgeModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ mod test_swift_takes_no_args_no_return_callback {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
{ let cb0 = __private__RustFnOnceCallbackNoArgsNoRet(ptr: callback); let _ = some_function(callback: { cb0.call() }) }()
}
"#,
Expand Down Expand Up @@ -149,7 +149,7 @@ class __private__RustFnOnceCallback$some_function$param0 {
"#,
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
{ let cb0 = __private__RustFnOnceCallback$some_function$param0(ptr: callback); let _ = some_function(callback: { arg0 in cb0.call(arg0) }) }()
}
"#,
Expand Down Expand Up @@ -248,7 +248,7 @@ class __private__RustFnOnceCallback$some_function$param0 {
"#,
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
{ let cb0 = __private__RustFnOnceCallback$some_function$param0(ptr: callback); let _ = some_function(callback: { cb0.call() }) }()
}
"#,
Expand Down Expand Up @@ -351,7 +351,7 @@ class __private__RustFnOnceCallback$some_function$param0 {
"#,
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
{ let cb0 = __private__RustFnOnceCallback$some_function$param0(ptr: callback); let _ = some_function(callback: { arg0 in cb0.call(arg0) }) }()
}
"#,
Expand Down Expand Up @@ -457,7 +457,7 @@ class __private__RustFnOnceCallback$some_function$param0 {
"#,
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
{ let cb0 = __private__RustFnOnceCallback$some_function$param0(ptr: callback); let _ = some_function(callback: { cb0.call() }) }()
}
"#,
Expand Down Expand Up @@ -566,7 +566,7 @@ class __private__RustFnOnceCallback$some_function$param0 {
"#,
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
{ let cb0 = __private__RustFnOnceCallback$some_function$param0(ptr: callback); let _ = some_function(callback: { arg0 in cb0.call(arg0) }) }()
}
"#,
Expand Down Expand Up @@ -690,7 +690,7 @@ class __private__RustFnOnceCallback$some_function$param1 {
"#,
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ arg0: UnsafeMutableRawPointer, _ arg1: UnsafeMutableRawPointer, _ arg2: UnsafeMutableRawPointer) {
public func __swift_bridge__some_function (_ arg0: UnsafeMutableRawPointer, _ arg1: UnsafeMutableRawPointer, _ arg2: UnsafeMutableRawPointer) {
{ let cb0 = __private__RustFnOnceCallbackNoArgsNoRet(ptr: arg0); let cb1 = __private__RustFnOnceCallback$some_function$param1(ptr: arg1); let cb2 = __private__RustFnOnceCallbackNoArgsNoRet(ptr: arg2); let _ = some_function(arg0: { cb0.call() }, arg1: { arg0 in cb1.call(arg0) }, arg2: { cb2.call() }) }()
}
"#,
Expand Down Expand Up @@ -793,7 +793,7 @@ class __private__RustFnOnceCallback$some_function$param0 {
"#,
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) {
{ let cb0 = __private__RustFnOnceCallback$some_function$param0(ptr: callback); let _ = some_function(callback: { arg0, arg1 in cb0.call(arg0, arg1) }) }()
}
"#,
Expand Down Expand Up @@ -865,7 +865,7 @@ mod test_swift_method_takes_no_args_no_return_callback {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
@_cdecl("__swift_bridge__$SomeType$some_method")
func __swift_bridge__SomeType_some_method (_ this: UnsafeMutableRawPointer, _ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__SomeType_some_method (_ this: UnsafeMutableRawPointer, _ callback: UnsafeMutableRawPointer) {
{ let cb1 = __private__RustFnOnceCallbackNoArgsNoRet(ptr: callback); let _ = Unmanaged<SomeType>.fromOpaque(this).takeUnretainedValue().some_method(callback: { cb1.call() }) }()
}
"#,
Expand Down Expand Up @@ -967,7 +967,7 @@ class __private__RustFnOnceCallback$SomeType$some_method$param1 {
"#,
r#"
@_cdecl("__swift_bridge__$SomeType$some_method")
func __swift_bridge__SomeType_some_method (_ this: UnsafeMutableRawPointer, _ callback: UnsafeMutableRawPointer) {
public func __swift_bridge__SomeType_some_method (_ this: UnsafeMutableRawPointer, _ callback: UnsafeMutableRawPointer) {
{ let cb1 = __private__RustFnOnceCallback$SomeType$some_method$param1(ptr: callback); let _ = Unmanaged<SomeType>.fromOpaque(this).takeUnretainedValue().some_method(callback: { arg0 in cb1.call(arg0) }) }()
}
"#,
Expand Down
Loading