Dead functions
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.
Dead type metadata
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.