Will Apple re-run macros when using bitcode? - ios

With bitcode, Apple say they can re-build my application on demand, whenever they think it is necessary.
If my source code contains preprocessor macros, when will those run?
When I build and archive my IPA locally? Or also when Apple re-builds the app?
I'm considering both custom macros, as well as built-in ones such as __DATE__ and __TIME__. Which date/time will it get if Apple re-builds the app in the app store?

No; Apple will have a 32 bit version and a 64 bit version for your app in bit code; that's all. All the #define's will be evaluated by your compiler and not be sent to Apple.
With what they have Apple can easily produce a new version of your app that will run on new versions of the ARM processor. They most likely could build a new version that would run on an Intel processor instead of an ARM processor (while their instruction set and implementation are very different, how these chips behave is actually very similar). Building a version for a little-endian PowerPC would be possible; a version for big-endian PowerPC would quite likely not work if your app includes code that needed to be different on a big-endian processor.
__DATE__ and __TIME__ would be the ones that were valid when you built the app.
The result of Apple building a new version for a new processor from bitcode should be exactly the same as if you had submitted the app with code for that processor. Obviously you don't have a compiler for ARM13 which will be introduced in 2022, but if Apple builds that version from your submitted bit code, it should be as if you had had that compiler today.

Related

Xcode 14 deprecates bitcode - but why?

The bounty expires in 7 days. Answers to this question are eligible for a +50 reputation bounty.
mfaani wants to reward an existing answer.
Xcode 14 Beta release notes are out, all thanks to the annual WWDC.
And alas, the Bitcode is now deprecated, and you'll get a warning message if you attempt to enable it.
And I was wondering, why has this happened? Was there any downside to using Bitcode? Was it somehow painful for Apple to maintain it? And how will per-iPhone-model compilation operate now?
Bitccode is actually just the LLVM intermediate language. When you compile source code using the LLVM toolchain, source code is translated into an intermediate language, named Bitcode. This Bitcode is then analyzed, optimized and finally translated to CPU instructions for the desired target CPU.
The advantage of doing it that way is that all LLVM based frontends (like clang) only need to translate source code to Bitcode, from there on it works the same regardless the source language as the LLVM toolchain doesn't care if the Bitcode was generated from C, C++, Obj-C, Rust, Swift or any other source language; once there is Bitcode, the rest of the workflow is always the same.
One benefit of Bitcode is that you can later on generate instructions for another CPU without having to re-compile the original source code. E.g. I may compile a C code to Bitcode and have LLVM generate a running binary for x86 CPUs in the end. If I save the Bitcode, however, I can later on tell LLVM to also create a running binary for an ARM CPU from that Bitcode, without having to compile anything and without access to the original C code. And the generated ARM code will be as good as if I had compiled to ARM from the very start.
Without the Bitcode, I would have to convert x86 code to ARM code and such a translation produces way worse code as the original intent of the code is often lost in the final compilation step to CPU code, which also involves CPU specific optimizations that make no sense for other CPUs, whereas Bitcode retains the original intent pretty well and only performs optimization that all CPUs will benefit from.
Having the Bitcode of all apps allowed Apple to re-compile that Bitcode for a specific CPU, either to make an App compatible with a different kind of CPU or an entirely different architecture or just to benefit from better optimizations of newer compiler versions. E.g. if Apple had tomorrow shiped an iPhone that uses a RISC-V instead of an ARM CPU, all apps with Bitcode could have been re-compiled to RISC-V and would natively support that new CPU architecture despite the author of the app having never even heard of RISC-V.
I think that was the idea why Apple wanted all Apps in Bitcode format. But that approach had issues to begin with. One issue is that Bitcode is not a frozen format, LLVM updates it with every release and they do not guarantee full backward compatibility. Bitcode has never been intended to be a stable representation for permanent storage or archival. Another problem is that you cannot use assembly code as no Bitcode is emitted for assembly code. Also you cannot use pre-built third party libraries that come without Bitcode.
And last but not least: AFAIK Apple has never used any of the Bitcode advantages so far. Despite requiring all apps to contain Bitcode in the past, the apps also had to contain pre-build fat binaries for all supported CPUs and Apple would always only just ship that pre-build code. E.g. for iPhones you used to once have a 32 Bit ARMv7 and a 64 Bit ARM64 version, as well as the Bitcode and during app thinning, Apple would remove either the 32 Bit or the 64 Bit version, as well as the Bitcode, and then ship whats left over. Fine, but they could have done so also if no Bitcode was there. Bitcode is not required to thin out architectures of a fat binary!
Bitcode would be required to re-build for a different architecture but Apple has never done that. No 32 Bit app magically became 64 bit by Apple re-compiling the Bitcode. And no 64 bit only app was magically available for 32 bit systems as Apple re-compiled the Bitcode on demand. As a developer, I can assure you, the iOS App Store always delivered exactly the binary code that you have built and signed yourself and never any code that Apple has themselves created from the Bitcode, so nothing was server side optimized. Even when Apple switched from Intel to M1, no macOS app magically got converted to native ARM, despite that would have been possible for all x86 apps in the app store for that Apple had the Bitcode. Instead Apple still shipped the x86 version and let it run in Rosetta 2.
So imposing various disadvantages onto developers by forcing all code to be available as Bitcode and then not using any of the advantages Bitcode would give you kinda makes the whole thing pointless. And now that all platforms migrated to ARM64 and in a couple of years there won't even be fat binaries anymore (once x86 support for Mac has been dropped), what's the point of continuing with that stuff? I guess Apple took the chance to bury that idea once and for all. Even if they one day add RISC-V to their platforms, developers can still ship fat binaries containing ARM64 and RISC-V code at the same time. This concept works well enough, is way simpler, and has no downsides other than "bigger binaries" and that's something server side app thinning can fix, as during download only the code for the current platform needs to be included.
Apple Watch Series 3 was the last device to not support 64-bit. (i.e. i386 or armv7)
Apple has now stopped supporting the Apple Watch Series 3. [1] They would have been happy to drop support for bitcode.
[1] https://www.xda-developers.com/watchos-9-not-coming-apple-watch-series-3
xcode remove armv7/armv7s/i386 targets support. bitcode use for build different cpu targets. but now all devices might be arm64 . and now no more developer use this tech. so deprecated maybe a wise choice
Bitcode was always pointless, as even if you compiled bitcode to another architecture, there's high chance it won't actually work because the ABI is different. For example, when you compile C program, the libc headers actually are different for every architecture. I'm glad they are finally getting rid of it, as it caused more problems than solved. At most they could've done is re-optimize the binary for the same architecture, or similar enough architecture. There is also the problem of unwanted symbols leaking in bitcode builds, so you either have to rename/obfuscate those or get hit by collisions (big problem if you are a library/framework vendor).

How does Swift 5.1 runtime work with older versions of iOS?

About a year ago, if you wanted to use Swift 4.2 for iOS development, you would have to install Xcode 10, which meant that you used iOS 12 SDK. As part your apps deployment, Swift 4.2 runtime would automatically be bundled with your app binary. This would mean that user installing your app would essentially download a copy of that Swift runtime that will enable your app work.
However, ABI stability came with Swift 5, and you no longer needed to bundle a runtime if your deployment target was iOS 12.2, since the runtime was now part of that iOS version. However, if you wanted to support iOS 10 and iOS 11, this Swift runtime would still be bundled with your app binary, and it would behave the same way as described above.
Documentation on swift.org states the same:
Apps deploying back to earlier OS releases will have a copy of the Swift runtime embedded inside them. Those copies of the runtime will be ignored — essentially inert — when running on OS releases that ship with the Swift runtime.
So far so good. If you use Xcode 10.2 with Swift 5.0, and you deploy your app to older iOS releases, you will still bundle Swift 5.0 runtime with it. Then, if your app is running on iOS 12, app will use the runtime provided by the iOS, and if it's running on e.g. iOS 11, it would use the runtime that was bundled as part of the app binary. Now the first question: Is that a correct assumption?
Now we come to Swift 5.1 and iOS 13 that will be released in September. Swift 5.1 includes some additional runtime features, e.g. opaque result types, which require Swift 5.1 runtime.
In WWDC 2019 session 402 "What's New in Swift", the speaker, when discussing the Swift 5.1 feature Opaque Result Type (SE-0244), mentions that the feature will only work on new OSes:
Requires new Swift runtime support
Available on macOS Catalina, iOS 13, tvOS 13, watchOS 6 and later
This is the confusing part for me. Wouldn't Swift runtime 5.1 be shipped with your app regardless if you support older iOS versions (e.g. iOS 10 as well), thus enabling it to use these new runtime features or am I just not understanding this correctly?
Now the first question: Is that a correct assumption?
Yes, that is correct.
Wouldn't Swift runtime 5.1 be shipped with your app regardless if you support older iOS versions (e.g. iOS 10 as well), thus enabling it to use these new runtime features or am I just not understanding this correctly?
The embedded runtime is not exactly the same runtime as the one found in your OS. E.g. the runtime in your OS is tightly integrated:
By being in the OS, the Swift runtime libraries can be tightly integrated with other components of the OS, particularly the Objective-C runtime and Foundation framework. The OS runtime libraries can also be incorporated into the dyld shared cache so that they have minimal memory and load time overhead compared to dylibs outside the shared cache.
Source: https://swift.org/blog/abi-stability-and-apple/
Of course, the embedded runtime cannot be tightly integrated into older systems. The embedded runtime can only support features that were already possible on the current system it is being executed. Features that require a newer systems are simply not present when your app runs on an older one.
Note that this has never been different for ObjC. If a class or a method only exists starting with a certain OS version, you can still deploy backwards to older system versions but then you cannot use that class/method there as it simply doesn't exist.
if (#available(iOS 13, *)) {
// Code requiring iOS 13
} else {
// Alternative code for older OS versions
}
or in Swift:
if #available(iOS 13, *) {
// Code requiring iOS 13
} else {
// Alternative code for older OS versions
}
Just like with ObjC, new Swift features will only be available for new OSes from now on. Only if it is possible to make these features also available for older OSes, regardless if these shipped a runtime or need to use the embedded one, this feature may also deploy backwards, though not necessarily all the way.
E.g. 10.15 introduces a new feature in its bundled runtime, then maybe this feature can also be made available for 10.14 and 10.13 using a shim library but not for 10.12 down to 10.9, then this feature will be tagged as "Requiring macOS 10.13 or newer".
If you deploy to 10.15, nothing has to be done, as the runtime of 10.15 supports the feature. If you deploy to 10.14 or 10.13, then the compiler will add shim library (like it would add an embedded runtime) and on 10.13 and 10.14 the code in this library will be used while on 10.15 and later the code in the runtime will be used. If you deploy to systems earlier than 10.13, this is okay but you must not use this feature on these systems then.
Of course, if a new feature can be made available even trough the embedded runtime, it can certainly also be made available using a shim library for all systems that shipped with an own runtime which just didn't support this feature, as the shim library can then use the same code that the embedded runtime uses.
The ability to sometimes make new features available even to older systems is explained by the very last question on that page:
Is there anything that can be done to allow runtime support for new Swift features to be backward deployed to older OSes?
It may be possible for some kinds of runtime functionality to be backward deployed, potentially using techniques such as embedding a “shim” runtime library within an app. However, this may not always be possible. The ability to successfully backward-deploy functionality is fundamentally constrained by the limitations and existing bugs of the shipped binary artifact in the old operating system. The Core Team will consider the backward deployment implications of new proposals under review on a case-by-case basis going forward
Source: https://swift.org/blog/abi-stability-and-apple/

Swift compatibility between versions for a library

I'm distributing libraries for other developers to use (http://empiric.al). I've noticed that between swift versions, even 2.0 to 2.1, I'll get Module file was created by a (newer/older) version of the compiler.
I need to be distribute in a future-proof way.
How can I make sure my compiled frameworks can be used by newer versions of Swift in the future so I don't have to recompile as soon as Apple puts a new beta out?
From Apple's website:
Binary Compatibility and Frameworks
While your app’s runtime compatibility is ensured, the Swift language
itself will continue to evolve, and the binary interface will also
change. To be safe, all components of your app should be built with
the same version of Xcode and the Swift compiler to ensure that they
work together.
This means that frameworks need to be managed carefully. For instance,
if your project uses frameworks to share code with an embedded
extension, you will want to build the frameworks, app, and extensions
together. It would be dangerous to rely upon binary frameworks that
use Swift — especially from third parties. As Swift changes, those
frameworks will be incompatible with the rest of your app. When the
binary interface stabilizes in a year or two, the Swift runtime will
become part of the host OS and this limitation will no longer exist.
Until the Swift ABI (application binary interface) stabilises (I'm guessing another year or two) the only way to distribute libraries that will work across different Xcode versions is to distribute the source code. Cocoa pods and Carthage are both good tools for making library distribution easier but for Swift code they will still rely on source code being available.
It might be possible to have an Cocoapod that detects the version of Xcode it is run with and then downloads and provides the correct build of your library but you will still need to build the libraries for all Xcode versions that you want to support and recompile every time Apple release a new Xcode but at least the user wouldn't need to download a new version manually.

Does the Swift toolchain eliminate code that is never called?

If I create an Xcode project with the iOS Single View Application template and choose Swift for the language, will the compiler exclude from the release build (binary) functions that never get called?
I'm wondering because I want to include a third-party library that has a lot of superfluous classes & functions, and I want to keep my app small & fast.
While I agree with comments, it is unlikely to impact performance in any significant way even if it was included...
Xcode 6 uses Apple LLVM Compiler Version 6.1, depending on how closely related it is to LLVM Developer Group's version the optimization feature is available http://llvm.org/docs/Passes.html with options such as -dce: Dead Code Elimination, -adce: Aggressive Dead Code Elimination.
One way to know for sure what is included is checking the assembly output using -emit-assembly option in the swift compiler and review the output, or opening the binary in a disassembler such as Hopper ( http://www.hopperapp.com/download.html )

Is there a way to detect VFP/NEON/Thumb/... on iOS at runtime?

So it's fairly easy to figure out what kind of CPU an iOS device runs by querying sysctlbyname("hw.cpusubtype", ...), but there seems to be no obvious way to figure out what features the CPU actually has (think VFP, NEON, Thumb, ...). Can someone think of a way to do this?
Basically, what I need is something similar to getauxval(AT_HWCAP) on Linux/Android, which returns a bit mask of features supported by the CPU.
A few things to note:
The information must be retrieved at runtime from the OS. No preprocessor defines.
Fat binaries is not a solution. I really do need to know this stuff in an ARM v6 binary.
Thanks in advance!
sysctlbyname has “hw.optional.neon”. I do not see a name for VFP, except “hw.optional.vfp_shortvector”, which is a deprecated feature.
Do a matrix float multiplaction via accelerate.framework and measure the execution time. The difference will be huge enough between Neon and VFP driven math, you simply cannot miss.
Thumb is always there, and the presence of NEON means armv7= Thumb2.
First, consider carefully whether or not you really need to support armv6 binaries for iOS. According to published version share statistics, something like 98.5% of iOS devices are running iOS 5.0 or later, which does not support armv6 devices (armv6 binaries will still run on current iOS versions, obviously, but all new apps should really be targeting armv7; there’s basically zero reason for your customers to be shipping armv6 binaries for iOS today).
Similarly, your concerns about code size are misplaced. If you provide a fat library, and your customer builds an armv6 binary against it, only the armv6 bits of your library will be built into their application. Furthermore, code size is usually a nearly trivial fraction of application bundle size; most of the size of an application comes from other resources.
Ok. All that aside, if you really want to pursue this: VFP and thumb are supported on all iOS devices, so there’s no need to check for support. You can check for NEON and thumb-2 using the method that Eric Postpischil suggested (all armv7 iOS devices have NEON support, so availability of NEON coincides exactly with availability of thumb-2).

Resources