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

Add some examples/documentation for standard use cases (e..g, getting an ObjC struct into rust) #582

Closed
marcpabst opened this issue Feb 11, 2024 · 16 comments · Fixed by #584
Labels
A-objc2 Affects the `objc2`, `objc-sys` and/or `objc2-encode` crates bug Something isn't working

Comments

@marcpabst
Copy link

I'm attempting to use objc2 for Apple iOS API interoperability, and I'm really struggling with basic tasks, such as retrieving an Objective-C struct (simd_float4x4, https://developer.apple.com/documentation/arkit/aranchor/2867981-transform?language=objc) from an object into Rust. Naively, I assumed this would work, but it doesn't:

#[repr(C)]
struct SimdFloat4x4 {
    columns: [SimdFloat4; 4],
}


#[repr(C)]
struct SimdFloat4 {
    x: f32,
    y: f32,
    z: f32,
    w: f32,
}

and then

let face_transform:  SimdFloat4x4 = unsafe { msg_send![anchor, transform] };

Could we have some more examples for things like this?

@marcpabst
Copy link
Author

I think I will need to revise this question - turns out this is probbaly related with how Objective C handled SIMD types. I will close this one (tho I think this librry still lacks examples) and open a new one.

@madsmtm
Copy link
Owner

madsmtm commented Feb 12, 2024

Well, it does say in the docs for msg_send!:

All arguments, as well as the return type, must implement Encode

And SimdFloat4 doesn't, because you haven't implemented that trait.

But I definitely recognize that this is not visible, and there should be some more introductory-level docs for this!

@marcpabst
Copy link
Author

marcpabst commented Feb 13, 2024

I think I was a bit tired and frustrated when writing this, my aplogies for that. I actually tried implementing Encode, but the code I copied in here is objc code (which actually works as intended). But as it turns out, SimdFloat4 is super opaque and apple seems to do some funny things under the hood. I only managed to get the first column out, the rest is all gibberish.

Now using some ObjectiveC glue code (basically reading all elements out and creating an array from that on the Objective C side) it works. But maybe worth keeping in mind when support for simd types is added!

@madsmtm
Copy link
Owner

madsmtm commented Feb 13, 2024

I think I was a bit tired and frustrated when writing this, my aplogies for that

No problem, it didn't register as rude to me.

I actually tried implementing Encode

Huh, yeah, I see what you mean, Encoding can't actually represent the encoding of SIMD types, as they don't actually have an encoding (@encode(simd_float4) == "" and @encode(simd_float4x4) == "{?=[4]}", which our encoding routines can't handle).

This may actually a problem ABI-wise, because we use the encoding as part of figuring out the correct calling convention; but then again, a type like typedef __attribute__((__ext_vector_type__(1))) long double simd_longdouble; also breaks in Clang, so maybe it's not really an issue in practice.

I'll reopen this until I've investigated things a bit more, this is an issue that should be fixed.

@madsmtm madsmtm reopened this Feb 13, 2024
@madsmtm madsmtm added bug Something isn't working A-objc2 Affects the `objc2`, `objc-sys` and/or `objc2-encode` crates labels Feb 13, 2024
@marcpabst
Copy link
Author

Just to add some context, using objc I can get the first column of the matrix by "lying" and telling msg_send! I'm expecting a [f32, 4] array (or something memory-equivalent). But when I tell msg_send! I want a [f32, 16], it all breaks and all values are off.

@madsmtm
Copy link
Owner

madsmtm commented Feb 13, 2024

From my reading of Clang's source code, the ABI expects us to use objc_msgSend and not objc_msgSend_stret in the following cases:

  • On ARM, 128bit types with the AAPCS16_VFP ABI (maybe some kind of floating point vectors?).
  • On x86 Darwin, 128bit vectors.
  • On x86_64, 256bit and 512bit vectors. Also unaligned structures are forced in objc_msgSend_stret?
  • Aarch64 is heaven, there's only ever objc_msgSend.

Though the code is quite involved, and I glossed over a lot of the details.

But it does seem like vector types at the very least need some sort of different handling to have the correct message-sending ABI. I don't know how vector types relate to SIMD, but they sound similar, so maybe SIMD types do also use a different ABI?

@marcpabst
Copy link
Author

Yep, that's what I saw as well. I'm on aarch64 (iPad/MacBook Pro M2), so objc_msgSend should be the way to go, Does msg_send! default to objc_msgSend_stret?

@madsmtm
Copy link
Owner

madsmtm commented Feb 14, 2024

msg_send! picks the right message sending function based on the return type, see:

/// On the below architectures we can statically find the correct method to
/// call from the return type, by looking at its `EncodeReturn` impl.
#[allow(clippy::missing_safety_doc)]
unsafe trait MsgSendFn: EncodeReturn {
const MSG_SEND: Imp;
const MSG_SEND_SUPER: Imp;
}
#[cfg(target_arch = "aarch64")]
/// `objc_msgSend_stret` is not even available in arm64.
///
/// <https://twitter.com/gparker/status/378079715824660480>
unsafe impl<T: EncodeReturn> MsgSendFn for T {
const MSG_SEND: Imp = ffi::objc_msgSend;
const MSG_SEND_SUPER: Imp = ffi::objc_msgSendSuper;
}
#[cfg(target_arch = "arm")]
/// Double-word sized fundamental data types don't use stret, but any
/// composite type larger than 4 bytes does.
///
/// <https://web.archive.org/web/20191016000656/http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf>
/// <https://developer.arm.com/documentation/ihi0042/latest>
/// <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/clang/lib/CodeGen/Targets/ARM.cpp#L531>
unsafe impl<T: EncodeReturn> MsgSendFn for T {
const MSG_SEND: Imp = {
if let Encoding::LongLong | Encoding::ULongLong | Encoding::Double = T::ENCODING_RETURN
{
ffi::objc_msgSend
} else if mem::size_of::<T>() <= 4 {
ffi::objc_msgSend
} else {
ffi::objc_msgSend_stret
}
};
const MSG_SEND_SUPER: Imp = {
if let Encoding::LongLong | Encoding::ULongLong | Encoding::Double = T::ENCODING_RETURN
{
ffi::objc_msgSendSuper
} else if mem::size_of::<T>() <= 4 {
ffi::objc_msgSendSuper
} else {
ffi::objc_msgSendSuper_stret
}
};
}
#[cfg(target_arch = "x86")]
/// Structures 1 or 2 bytes in size are placed in EAX.
/// Structures 4 or 8 bytes in size are placed in: EAX and EDX.
/// Structures of other sizes are placed at the address supplied by the caller.
///
/// <https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/130-IA-32_Function_Calling_Conventions/IA32.html>
/// <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/clang/lib/CodeGen/Targets/X86.cpp#L472>
unsafe impl<T: EncodeReturn> MsgSendFn for T {
const MSG_SEND: Imp = {
// See https://github.com/apple-oss-distributions/objc4/blob/objc4-818.2/runtime/message.h#L156-L172
if let Encoding::Float | Encoding::Double | Encoding::LongDouble = T::ENCODING_RETURN {
ffi::objc_msgSend_fpret
} else if let 0 | 1 | 2 | 4 | 8 = mem::size_of::<T>() {
ffi::objc_msgSend
} else {
ffi::objc_msgSend_stret
}
};
const MSG_SEND_SUPER: Imp = {
if let 0 | 1 | 2 | 4 | 8 = mem::size_of::<T>() {
ffi::objc_msgSendSuper
} else {
ffi::objc_msgSendSuper_stret
}
};
}
#[cfg(target_arch = "x86_64")]
/// If the size of an object is larger than two eightbytes, it has class
/// MEMORY. If the type has class MEMORY, then the caller provides space for
/// the return value and passes the address of this storage.
///
/// <https://www.uclibc.org/docs/psABI-x86_64.pdf>
/// <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/clang/lib/CodeGen/Targets/X86.cpp#L2532>
unsafe impl<T: EncodeReturn> MsgSendFn for T {
const MSG_SEND: Imp = {
// See https://github.com/apple-oss-distributions/objc4/blob/objc4-818.2/runtime/message.h#L156-L172
if let Encoding::LongDouble = T::ENCODING_RETURN {
ffi::objc_msgSend_fpret
} else if let Encoding::LongDoubleComplex = T::ENCODING_RETURN {
ffi::objc_msgSend_fp2ret
} else if mem::size_of::<T>() <= 16 {
ffi::objc_msgSend
} else {
ffi::objc_msgSend_stret
}
};
const MSG_SEND_SUPER: Imp = {
if mem::size_of::<T>() <= 16 {
ffi::objc_msgSendSuper
} else {
ffi::objc_msgSendSuper_stret
}
};
}

But I think this logic is subtly wrong for vector types, and that's what I want to fix.

All of this is only tangentially related to your problem of not being able to implement Encode correctly, and then panicking, that part should be fairly easy to solve.

@marcpabst
Copy link
Author

But I tried with the objc crate that I assume used the same or simmiliar logics (but lacks the verification of encodings by default) - I presume this might be related to the way structs are handled?

@madsmtm
Copy link
Owner

madsmtm commented Feb 15, 2024

I actually suspect the code example you provided might technically be wrong too on Aarch64, as the ABI of SIMD types isn't the same as the ABI of a C struct - it happens to work since you use the large simd_float4x4, but passing a type like simd_float4 across an ABI boundary requires you to use the corresponding Simd<f32, 4> / core::arch::aarch64::float32x4_t.

In general, I'm unsure of the state of SIMD FFI support? There's feature(simd_ffi), but that hasn't gotten far.

@madsmtm
Copy link
Owner

madsmtm commented Feb 15, 2024

Re examples: There are some in objc2::encode, but is there another place where it would have been more visible / more easily understandable?

@marcpabst
Copy link
Author

I think the examples are quite good already, but as some point it might be worth to add a short user guide / getting started page and then link to the examples.

@madsmtm
Copy link
Owner

madsmtm commented May 10, 2024

I've added Encoding::None in #584 to allow you to specify when Clang doesn't emit an encoding for a type, no timeline on a release.

Note that this does not mean that objc2 officially supports SIMD types; the wrong message-sending function may still be chosen, and Rust's own support for it is yet lacking.

@marcpabst
Copy link
Author

Great, thank you! I haven't looked into this for a while but will definitely come back to this shortly for an ongoing project.

@marcpabst
Copy link
Author

Returning to this, before I give it another shot - do you think accessing SIMD vectors will now work?

Note that this does not mean that objc2 officially supports SIMD types; the wrong message-sending function may still be chosen, and Rust’s own support for it is yet lacking.

Not entirely sure how to interpret this - does this mean that we still need upstream support, or is there a chance it might just work?

@madsmtm
Copy link
Owner

madsmtm commented May 28, 2024

I think you can try it out with the new encoding, might be that you're lucky, and that you can get the specific methods to work that you need - though I will still warn that it's fully undefined behaviour until we get support for SIMD in FFI in Rust (the simd_ffi feature that I linked earlier) (I suspect the issue is the same as it was with i128, Rust / Clang / GCC don't agree on the C ABI for these).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-objc2 Affects the `objc2`, `objc-sys` and/or `objc2-encode` crates bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants