Stance on -cross-module-optimization

What is the latest stance on manually enabling -cross-module-optimization?

.unsafeFlags(
    ["-cross-module-optimization"],
    .when(configuration: .release)
)

I was reminded of existence of this flag after taking a look the new swift-homomorphic-encryption's README, which although it requires Swift 5.10, it also mentions that:

I couldn't find any good amount of info about this flag in Swift's source code.
Some comments here and there, but no documentation.

1 Like

@dimi mentioned this nice response from @Erik_Eckstein in another post to me:

So ... It appears the flag does still have some usage, and while CMO (cross-module-optimization) is enabled by default, explicitly enabling it will make it more aggressive?

And if code size doesn't matter much, one should enable that setting?

I just fully and cleanly recompiled our 200K LOC Server-Side Vapor app on swift-5.10-noble (amd64) and on release mode:

No CMO: 483.7 MB Docker image
With CMO: 482.94 MB Docker image

The CMO code had a "cleanup" commit in it, so I think I can just conclude that the size didn't really change at all with or without CMO explicitly enabled?

That's ... weird. I didn't expect the same code size with and without CMO explicitly enabled.

Any explanations?

I'd also like to learn more about what exactly the more aggressive optimization does ā€” is it primarily reliant on libraries marking their members with @inlinable or @inline(...)?

Generally it is supposed to not rely on that: @inlinable works in either case.

2 Likes

How much of your Docker image is actually the text segment of your Swift binary? 0.75 MB of code is actually quite a bit; the entirety of the Standard Library on Darwin only has 3.76MB of code.

i would be reluctant to do that, not the least because it affects all other dependencies of that module, and not just the bits imported from swift-homomorphic-encryption.

The same code:

Without CMO: 482.92
With CMO: 482.94

So ... still like only 20KB difference?
Is that expected?

The only difference between these codes is that in Package.swift one has:

extension [SwiftSetting] {
    static var forExecutables: Self {
        [
            .unsafeFlags(
                ["-cross-module-optimization"],
                .when(configuration: .release)
            )
        ]
    }
}

And the other:

extension [SwiftSetting] {
    static var forExecutables: Self {
        [
//            .unsafeFlags(
//                ["-cross-module-optimization"],
//                .when(configuration: .release)
//            )
        ]
    }
}

Ah fair enough. No idea if that's expected.

I'm just trying to have a general idea, we're not using swift-homomorphic-encryption at all anyway.
I know it affects all the dependencies, and that's fine by me if it optimizes anything.
A bit more code size or a bit more CI time is not my concern.

Not sure if it's worth noting, but these are static-std-lib builds.

EDIT: and also we use jemalloc.