Does the swift compiler/linker automatically remove unused methods/classes/extensions, etc.?

梦想与她 提交于 2019-12-05 14:28:41

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!