We have a lot of code which is usable in any iOS application we write. Things such as:
For reasons unrelated to this question, we cannot use static or dynamic libraries. These must be included in the project as actual source files.
There are several hundred of these 'core' files so what I've been doing is adding all the files to the project (referencing a shared location on disk), but only adding them to specific targets as they are used/needed.
The problem is this becomes quite tedious to do, especially when there are sets of related files which all reference others. Tracking them down one by one is a real pain!
What I'm wondering is if I can simply include everything in the target, then count on the compiler to remove all the unused code.
For instance, I have several extension methods on UIView. If I don't use them in a particular target, will the compiler exclude that code from the compiled binary, or will it be compiled in, unreachable, bloating the code size for no reason?
So the linker is able to remove each individual function because it is in its own section. So enabling this for your library will allow the linker to remove unused functions from the library.
Those who are looking for a way to remove unused code from Swift project, it can be done easily using Periphery. Periphery is a tool to remove unused code from Swift projects. It can detect unused function, struct, class, protocols etc.
xcutility is a tool to find and delete unused files from Xcode projects. It recursively searches through a path to find all of the path's Xcode projects and files, and will tell you which files are not referenced or built in any of your Xcode projects.
The compiler's SILOptimizer has a dead function elimination pass, which eliminates functions and methods that are known not to be called (along with any associated vtable/witness table entries).
To fully take advantage of this, you'll want to be using whole module optimisation (-wmo
) in order to ensure that the compiler can analyse whether internal
functions are called or not from within the same module.
You can quite easily test this out for yourself, for example using the following code:
class C {}
extension C {
@inline(never) func foo() {}
@inline(never) func bar() {}
}
@inline(never) func foo() {}
@inline(never) func bar() {}
bar()
let c = C()
c.bar()
(I'm using the unofficial @inline(never)
here to ensure the functions aren't optimised away by inlining)
If we run xcrun swiftc -emit-sil -O -wmo main.swift | xcrun swift-demangle
, we can see the generated SIL:
sil_stage canonical
import Builtin
import Swift
import SwiftShims
class C {
init()
deinit
}
extension C {
@inline(never) func foo()
@inline(never) func bar()
}
@inline(never) func foo()
@inline(never) func bar()
let c: C
// c
sil_global hidden [let] @main.c : main.C : $C
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
// function_ref bar()
%2 = function_ref @main.bar() -> () : $@convention(thin) () -> () // user: %3
%3 = apply %2() : $@convention(thin) () -> ()
alloc_global @main.c : main.C // id: %4
%5 = global_addr @main.c : main.C : $*C // user: %8
%6 = alloc_ref $C // users: %8, %7
debug_value %6 : $C, let, name "self", argno 1 // id: %7
store %6 to %5 : $*C // id: %8
// function_ref specialized C.bar()
%9 = function_ref @function signature specialization <Arg[0] = Dead> of main.C.bar() -> () : $@convention(thin) () -> () // user: %10
%10 = apply %9() : $@convention(thin) () -> ()
%11 = integer_literal $Builtin.Int32, 0 // user: %12
%12 = struct $Int32 (%11 : $Builtin.Int32) // user: %13
return %12 : $Int32 // id: %13
} // end sil function 'main'
// C.__deallocating_deinit
sil hidden @main.C.__deallocating_deinit : $@convention(method) (@owned C) -> () {
// %0 // users: %3, %2, %1
bb0(%0 : $C):
debug_value %0 : $C, let, name "self", argno 1 // id: %1
debug_value %0 : $C, let, name "self", argno 1 // id: %2
dealloc_ref %0 : $C // id: %3
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function 'main.C.__deallocating_deinit'
// bar()
sil hidden [noinline] @main.bar() -> () : $@convention(thin) () -> () {
bb0:
%0 = tuple () // user: %1
return %0 : $() // id: %1
} // end sil function 'main.bar() -> ()'
// specialized C.bar()
sil shared [noinline] @function signature specialization <Arg[0] = Dead> of main.C.bar() -> () : $@convention(thin) () -> () {
bb0:
%0 = tuple () // user: %1
return %0 : $() // id: %1
} // end sil function 'function signature specialization <Arg[0] = Dead> of main.C.bar() -> ()'
sil_vtable C {
#C.deinit!deallocator: @main.C.__deallocating_deinit // C.__deallocating_deinit
}
You'll note that only the bar
function and method have had their bodies emitted (near the bottom). While there are still definitions for foo
at the top of the SIL, they get removed as the SIL is lowered to LLVM IR.
I was wondering if you could mark-up/attribute a method saying 'Don't remove this!' like you can in other languages
There isn't currently an official attribute for this, but there is an underscored @_optimize(none)
attribute which tells the optimiser not to touch something:
@_optimize(none) func foo() {}
Though given the attribute is underscored, use at your own risk.
Unfortunately, the compiler currently (as of Swift 4.2) doesn't appear to have an optimisation pass that eliminates the associated metadata for types that are known to not be used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With