Skip to content

Commit

Permalink
Support Vec<OpaqueRustType> as a fn arg (#20)
Browse files Browse the repository at this point in the history
We also fix the `RustVec`'s `deinit` to only free the underlying
`Vec<T>` if the RustVec is owned. This prevents a double free
after passing an owned `RustVec` from Swift to Rust.
  • Loading branch information
chinedufn committed Jan 26, 2022
1 parent 3a4d8db commit b26dcf9
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 238 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,67 @@ class VecTests: XCTestCase {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

func testRustVecU8() throws {
let start: [UInt8] = [1, 9, 3, 4, 5]
let vec = create_vec_u8(start.toUnsafeBufferPointer())

XCTAssertEqual(vec.len(), 5)

vec.push(value: 10)
XCTAssertEqual(vec.len(), 6)

XCTAssertEqual(vec.pop(), 10)
XCTAssertEqual(vec.len(), 5)

XCTAssertEqual(vec.get(index: 1), 9)
func testRustVecU8Len() throws {
let vec = RustVec<UInt8>()
XCTAssertEqual(vec.len(), 0)
vec.push(value: 123)
XCTAssertEqual(vec.len(), 1)
}

func testRustVecI32() throws {
let start: [Int32] = [1, 9, 3, 4, 5]
let vec = create_vec_i32(start.toUnsafeBufferPointer())

XCTAssertEqual(vec.len(), 5)

vec.push(value: 10)
XCTAssertEqual(vec.len(), 6)

XCTAssertEqual(vec.pop(), 10)
XCTAssertEqual(vec.len(), 5)
func testRustVecU8Pop() throws {
let vec = RustVec<UInt8>()
vec.push(value: 123)
let popped = vec.pop()
XCTAssertEqual(popped, 123)
XCTAssertEqual(vec.len(), 0)
}
func testRustVecU8Get() throws {
let vec = RustVec<UInt8>()
vec.push(value: 111)
vec.push(value: 222)
XCTAssertEqual(vec.get(index: 1), 222)
}
func testRustVecU8Iterator() throws {
let vec = RustVec<UInt8>()
vec.push(value: 111)
vec.push(value: 222)

XCTAssertEqual(vec.get(index: 1), 9)
var iterations = 0
for (index, val) in vec.enumerated() {
XCTAssertEqual(val, vec[index])
iterations += 1
}
XCTAssertEqual(iterations, 2)
}

func testVecOfOpaqueRustType() throws {
let vec = create_vec_opaque_rust_type()

func testVecOfOpaqueRustTypeLen() throws {
let vec = RustVec<ARustTypeInsideVecT>()
XCTAssertEqual(vec.len(), 0)
vec.push(value: ARustTypeInsideVecT("hello world"))
XCTAssertEqual(vec.len(), 1)
XCTAssertEqual(vec.get(index: 0)!.text().toString(), "hello there, friend")
}
func testVecOfOpaqueRustTypeGet() throws {
let vec: RustVec<ARustTypeInsideVecT> = RustVec()
vec.push(value: ARustTypeInsideVecT("hello world"))
XCTAssertEqual(vec.get(index: 0)!.text().toString(), "hello world")
}
func testVecOfOpaqueRustTypePop() throws {
let vec: RustVec<ARustTypeInsideVecT> = RustVec()
vec.push(value: ARustTypeInsideVecT("hello world"))

XCTAssertEqual(vec.len(), 1)
let popped = vec.pop()
XCTAssertEqual(popped?.text().toString(), "hello there, friend")

XCTAssertEqual(popped?.text().toString(), "hello world")
XCTAssertEqual(vec.len(), 0)
XCTAssertNil(vec.pop())
XCTAssertNil(vec.get(index: 1))

let text = "My pushed text"
vec.push(value: ARustTypeInsideVecT(text))
XCTAssertEqual(vec.len(), 1)
XCTAssertEqual(vec.get(index: 0)!.text().toString(), "My pushed text")
}

func testRustVecIterator() throws {
let numbers: [Int32] = [5, 6, 7]
let vec = create_vec_i32(numbers.toUnsafeBufferPointer())
/// Verify that a Vec<T> of opaque Rust types can be used as an argument and return
/// type for extern "Rust" functions.
func testReflectVecOfOpaqueRustType() throws {
let vec: RustVec<ARustTypeInsideVecT> = RustVec()
vec.push(value: ARustTypeInsideVecT("hello world"))

for (index, val) in vec.enumerated() {
XCTAssertEqual(val, numbers[index])
}
let reflected = rust_reflect_vec_opaque_rust_type(vec)
XCTAssertEqual(reflected.len(), 1)
XCTAssertEqual(reflected.get(index: 0)!.text().toString(), "hello world")
}

/// Verify that we can construct a RustVec of every primitive type.
Expand Down
7 changes: 5 additions & 2 deletions crates/swift-bridge-ir/src/bridged_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ impl BridgedType {
}
StdLibType::Vec(_) => {
quote_spanned! {span=>
unsafe { Box::from_raw(#value) }
unsafe { * Box::from_raw(#value) }
}
}
StdLibType::Option(bridged_option) => {
Expand Down Expand Up @@ -1197,7 +1197,10 @@ impl BridgedType {
)
}
StdLibType::Vec(_) => {
format!("{}.ptr", value)
format!(
"{{ let val = {value}; val.isOwned = false; return val.ptr }}()",
value = value
)
}
StdLibType::Option(option) => {
option.convert_swift_expression_to_ffi_compatible(value, type_pos)
Expand Down
174 changes: 1 addition & 173 deletions crates/swift-bridge-ir/src/codegen/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mod function_attribute_codegen_tests;
mod option_codegen_tests;
mod shared_struct_codegen_tests;
mod string_codegen_tests;
mod vec_codegen_tests;

/// Test code generation for freestanding Swift function that takes an opaque Rust type argument.
mod extern_swift_freestanding_fn_with_owned_opaque_rust_type_arg {
Expand Down Expand Up @@ -160,179 +161,6 @@ func __swift_bridge__some_function (_ arg: __private__PointerToSwiftType) {
}
}

/// Test code generation for Rust function that returns a Vec<T> where T is an opaque Rust type.
mod extern_rust_fn_return_vec_of_opaque_rust_type {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type MyRustType;

fn some_function () -> Vec<MyRustType>;
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
const _: () = {
#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_MyRustType$new"]
pub extern "C" fn _new() -> *mut Vec<super::MyRustType> {
Box::into_raw(Box::new(Vec::new()))
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_MyRustType$drop"]
pub extern "C" fn _drop(vec: *mut Vec<super::MyRustType>) {
let vec = unsafe { Box::from_raw(vec) };
drop(vec)
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_MyRustType$len"]
pub extern "C" fn _len(vec: *const Vec<super::MyRustType>) -> usize {
unsafe { &*vec }.len()
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_MyRustType$get"]
pub extern "C" fn _get(vec: *const Vec<super::MyRustType>, index: usize) -> *const super::MyRustType {
let vec = unsafe { & *vec };
if let Some(val) = vec.get(index) {
val as *const super::MyRustType
} else {
std::ptr::null()
}
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_MyRustType$get_mut"]
pub extern "C" fn _get_mut(vec: *mut Vec<super::MyRustType>, index: usize) -> *mut super::MyRustType {
let vec = unsafe { &mut *vec };
if let Some(val) = vec.get_mut(index) {
val as *mut super::MyRustType
} else {
std::ptr::null::<super::MyRustType>() as *mut super::MyRustType
}
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_MyRustType$push"]
pub extern "C" fn _push(vec: *mut Vec<super::MyRustType>, val: *mut super::MyRustType) {
unsafe { &mut *vec }.push(unsafe { *Box::from_raw(val) })
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_MyRustType$pop"]
pub extern "C" fn _pop(vec: *mut Vec<super::MyRustType>) -> *mut super::MyRustType {
let vec = unsafe { &mut *vec };
if let Some(val) = vec.pop() {
Box::into_raw(Box::new(val))
} else {
std::ptr::null::<super::MyRustType>() as *mut super::MyRustType
}
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_MyRustType$as_ptr"]
pub extern "C" fn _as_ptr(vec: *const Vec<super::MyRustType>) -> *const super::MyRustType {
unsafe { & *vec }.as_ptr()
}
};
})
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsManyAfterTrim(vec![
r#"
func some_function() -> RustVec<MyRustType> {
RustVec(ptr: __swift_bridge__$some_function())
}"#,
//
r#"
extension MyRustType: Vectorizable {
static func vecOfSelfNew() -> UnsafeMutableRawPointer {
__swift_bridge__$Vec_MyRustType$new()
}
static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) {
__swift_bridge__$Vec_MyRustType$drop(vecPtr)
}
static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: MyRustType) {
__swift_bridge__$Vec_MyRustType$push(vecPtr, {value.isOwned = false; return value.ptr;}())
}
static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional<Self> {
let pointer = __swift_bridge__$Vec_MyRustType$pop(vecPtr)
if pointer == nil {
return nil
} else {
return (MyRustType(ptr: pointer!) as! Self)
}
}
static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<MyRustTypeRef> {
let pointer = __swift_bridge__$Vec_MyRustType$get(vecPtr, index)
if pointer == nil {
return nil
} else {
return MyRustTypeRef(ptr: pointer!)
}
}
static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<MyRustTypeRefMut> {
let pointer = __swift_bridge__$Vec_MyRustType$get_mut(vecPtr, index)
if pointer == nil {
return nil
} else {
return MyRustTypeRefMut(ptr: pointer!)
}
}
static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt {
__swift_bridge__$Vec_MyRustType$len(vecPtr)
}
}
"#,
])
}

const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim(
r#"
#include <stdint.h>
typedef struct MyRustType MyRustType;
void __swift_bridge__$MyRustType$_free(void* self);
void* __swift_bridge__$Vec_MyRustType$new(void);
void __swift_bridge__$Vec_MyRustType$drop(void* vec_ptr);
void __swift_bridge__$Vec_MyRustType$push(void* vec_ptr, void* item_ptr);
void* __swift_bridge__$Vec_MyRustType$pop(void* vec_ptr);
void* __swift_bridge__$Vec_MyRustType$get(void* vec_ptr, uintptr_t index);
void* __swift_bridge__$Vec_MyRustType$get_mut(void* vec_ptr, uintptr_t index);
uintptr_t __swift_bridge__$Vec_MyRustType$len(void* vec_ptr);
void* __swift_bridge__$Vec_MyRustType$as_ptr(void* vec_ptr);
void* __swift_bridge__$some_function(void);
"#,
);

#[test]
fn extern_rust_fn_return_vec_of_opaque_rust_type() {
CodegenTest {
bridge_module: bridge_module_tokens().into(),
expected_rust_tokens: expected_rust_tokens(),
expected_swift_code: expected_swift_code(),
expected_c_header: EXPECTED_C_HEADER,
}
.test();
}
}

struct CodegenTest {
bridge_module: BridgeModule,
// Gets turned into a Vec<String> and compared to a Vec<String> of the generated Rust tokens.
Expand Down

0 comments on commit b26dcf9

Please sign in to comment.