Swift编译器/链接器是否会自动删除未使用的方法/类/扩展名等? [英] Does the swift compiler/linker automatically remove unused methods/classes/extensions, etc.?

查看:96
本文介绍了Swift编译器/链接器是否会自动删除未使用的方法/类/扩展名等?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有很多代码可在我们编写的任何iOS应用程序中使用.诸如:

We have a lot of code which is usable in any iOS application we write. Things such as:

  • 自定义/通用控件
  • UIView,UIImage和UIViewController等常见对象的扩展
  • 全局实用程序功能
  • 全局常量
  • 构成常见功能"的相关文件集,例如可以用于枚举的任何东西的选择器屏幕.

由于与该问题无关的原因,我们不能使用静态或动态库.这些必须作为实际的源文件包含在项目中.

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.

例如,我在UIView上有几种扩展方法.如果我不在特定的目标中使用它们,编译器会从编译的二进制文件中排除该代码,还是将其编译到无法访问的位置,从而无缘无故膨胀代码大小?

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?

推荐答案

死函数

编译器的SILOptimizer 具有无效的函数消除传递,它消除了已知不被调用的功能和方法(以及任何相关的vtable/见证表条目).

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).

要充分利用此优势,您需要使用整个模块优化(-wmo),以确保编译器可以分析是否在同一模块内调用了internal函数.

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()

(我在这里使用非官方的@inline(never)来确保未通过内联优化功能)

(I'm using the unofficial @inline(never) here to ensure the functions aren't optimised away by inlining)

如果运行xcrun swiftc -emit-sil -O -wmo main.swift | xcrun swift-demangle,则可以看到生成的SIL:

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
}

您会注意到,只有bar函数和方法发出了它们的主体(靠近底部).尽管在SIL的顶部仍然有foo的定义,但是随着SIL降低到LLVM IR,它们将被删除.

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

目前尚无官方属性,但有一个强调的@_optimize(none)属性,该属性告诉优化器不要触摸某些东西:

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.

不幸的是,当前(从Swift 4.2版本开始)编译器似乎没有出现,没有通过优化遍历消除了已知不使用的类型的关联元数据.

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.

这篇关于Swift编译器/链接器是否会自动删除未使用的方法/类/扩展名等?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆