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 selector! macro zero-cost #2

Open
nvzqz opened this issue Sep 11, 2020 · 4 comments
Open

Make selector! macro zero-cost #2

nvzqz opened this issue Sep 11, 2020 · 4 comments
Labels
help wanted Extra attention is needed

Comments

@nvzqz
Copy link
Owner

nvzqz commented Sep 11, 2020

Currently, the selector! macro calls Sel::register, which in turn calls sel_registerName. This is inefficient compared to @selector in Objective-C.

On my machine, I measure:

Initial Subsequent
Low 2.15µs 100ns
High 2.45µs 210ns

This is relatively small compared to objc_msgSend. However, one of the goals of this crate is to be zero-cost compared to the same code written directly in Objective-C.

Related issues:


I made an attempt in nvzqz/rust-selector-example, however it fails to build at link time. See that repo for details.

The culprit seems to be the literal_pointers link option. It builds fine with just __DATA,__objc_selrefs, but I get "unrecognized selector" when trying to use the selector.

@nvzqz nvzqz added the help wanted Extra attention is needed label Sep 11, 2020
nvzqz added a commit that referenced this issue Dec 17, 2020
This uses the same approach as native selectors in Objective-C. Swift
selectors are similar, and in this case would use `L_selector(class)`
for the selector reference and `L_selector_data(class)` for the "class"
string data.

This is a step towards #2.

However, this currently fails to compile when used externally. That is
because a private symbols (required by linker for selector sections)
cannot cross compilation units. This appears to be a bug in LLVM.
See rust-lang/rust#53929 for current status.
@codyd51
Copy link

codyd51 commented Oct 14, 2022

A couple guesses as to why this fails (the first being the likely culprit, but I'd be curious to hear if you know otherwise):

  1. In a binary, all internal pointers use an arbitrary virtual memory base address. When the binary is loaded, all the internal metadata pointers will be adjusted using the real load address that dyld chose at runtime (this is called a 'rebase'). dyld relies on metadata in the __LINKEDIT segment to find the pointers to rebase. Since you're not producing any metadata informing dyld about your pointer to the selector literal in __objc_methname, the system might see it as an invalid value and skip over it when processing selrefs. I haven't checked the source or verified this behaviour, though. Note that the data format in __LINKEDIT changed significantly in iOS 15. The details are a bit gruesome, but I made a longer writeup here

  2. You might also need a type encoding for the selector in __objc_methtype.

@codyd51
Copy link

codyd51 commented Oct 15, 2022

For the same reason I would have expected difficulties using the CFConstantStrings you’re generating, since they contain rebased pointers to __cstring (though I’d be very curious to hear if it works in practice!)

@madsmtm
Copy link

madsmtm commented Jan 7, 2023

In my fork of objc I am having issues with CFConstantStrings - fruity exhibits this as well, try:

fn main() {
    let s = || fruity::ns_string!("abc");
    println!("{}", s());
}

Linkers and dynamic linkers are very much opaque pieces of software to me, are there some resources you can recommend to get a better understanding of such details (I'm asking you because you seem very knowledgable in this area)? The writeup you provided is very cool, but sadly I feel I lack the necessary foundation to properly understand it.

(Also, I've implemented a static selector macro that somewhat works (but is still very brittle), search for unstable-static-sel in this file).

@codyd51
Copy link

codyd51 commented Jan 8, 2023

It's really neat to have the hunch confirmed, thank you for letting me know!

Unfortunately I don't know of a good resource to point to for a '10,000 foot' view. For me, I look at and parse many binaries for work, so I've gotten familiar with the structure that way. I personally think that parsing binaries is probably the best way to gain familiarity, as you're 'forced' to understand all the pieces. I would just throw in: while they may be opaque to you now, there's absolutely no magic and it's all approachable. That said, everybody here is deep into Rust/Darwin land so that might go without saying =).

If you're not already doing this, I would strongly recommend compiling a binary containing some simple structure (like the ObjC equivalent of the fruity example you gave above) with the first-party toolchain, then crack open the resulting binary with Hopper/IDA and click around to see how the internal Mach-O structures reference each other, and have a look at all the resulting Mach-O sections. Any time you're having a bug with your tooling, look at the diff in the resulting binaries.

Learning the __LINKEDIT structures is a little bit trickier, particularly because Hopper (at least) will hide them from you and parse them upfront, for user convenience. For me, I learned how these structures are laid out by parsing them.

For work, I regularly make internal educational content describing various bits of esoterica. A couple months ago, I happened to make a ~10 minute video covering some conceptual introductions to dynamic linking, as well as the __LINKEDIT change I mentioned above, which might be useful for you? The video isn't available publicly, but I can send you a copy of it if you tell me a good way to reach you.

Lastly, I'm happy to answer other questions that you might have now or in the future! Feel free to ping in-thread or elsewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants