招聘

假如你看完觉得这篇文章对你有协助,想和咱们一起同事,欢迎参加字节跳动国际化短视频产品研制团队,团队与岗位介绍在文末。

正文开端

笔者之前一直在探究Objective-C言语的底层原理,有天忽然想到既然iOS未来的编程言语是Swift(其实国外现在已是,国内因大型app的历史包袱过重),我为什么不去研讨Swift言语的底层原理,而在一个注定被扔掉的言语上继续发掘呢。加上现在地点团队也在开端对Swift言语进行尝试,所以探究Swift言语的底层原理显着各方面的价值更大。

说干就干,开端阅读一份项目源码的第一步,是先让这个项目能够通过你自己编译通过而且顺畅跑起来,这一点特别重要。而Swift源码工程比较复杂,搭建起一个调试环境并不容易。因而能自己本地将工程编译运转起来而且可调试关于咱们了解和学习Swift底层的原理能够说是不能绕过的条件。笔者通过2个月周末时间的探索踩了N多坑后总结这篇文章,目的便是协助读者尽快到达本地可调试的状态。

网上材料现状

现在咱们在网上能搜到的这方面的材料十分少,而且几乎都存在以下几个问题

  • Swift版别非最新版别:关于咱们来说继续研讨老版别的Swift代码必定没有去研讨最新版的Swift代码有价值。
  • 各种编译问题:有一些材料依照其过程会遇到各种编译问题导致无法正常运转,可是并没有全面的处理计划。
  • 无法调试:部分材料能调试SwiftRuntime,但却无法调试SwiftCore,而这两个关于研讨Swift底层相同重要。
  • 官方计划跑不起来:官方文档的编译计划笔者尝试了很多次都没有办法正常的Run起来,假如有同学处理了请辅导我。

明确方针

关于现在的咱们,最有价值的内容应该是咱们工作中经常用到的内容,比方Foundation中的Array的底层完结,比方Swift中Runtime的底层完结,而像LLVM虚拟机或许Swift编译器的底层完结,关于咱们来说还不用太着急.

因而咱们的方针很简单

  • Swift 现在最新的安稳版别(5.8.1)本地编译运转起来,而且能够到达SwiftRuntime和SwiftCore本地可调试

盘它

  1. Disk Space Prepare

整个Swift工程以及其编译后的DerivedData缓存差不多需求64G的磁盘空间,主张在开端前清理本地磁盘,空出至少70G的空间以便寄存工程相关文件。

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境
手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

  1. Dependency Install

Swift的本地部署过程中,会依靠cmake,ninja,sccache,因而咱们需求先把这些依靠安装好。

$ brew install cmake ninja sccache
  1. Clone&Checkout

首先,咱们在咱们预备寄存咱们Swift源码的地方创立一个文件夹,然后cd到咱们的目录:

$ mkdir swift-project
$ cd swift-project

然后,咱们在方才新建的目录中履行如下指令拉取对应的 Swift 源码,并cd到源码目录:

$ git clone --branch swift-5.8.1-RELEASE git@github.com:apple/swift.git
$ cd swift

最后,拉取源码后还须拉取依靠:

$ ./utils/update-checkout --tag swift-5.8.1-RELEASE --clone

这一步会比较耗时,主要是拉取llvm-project十分耗时,它有将近4G的巨细,能够耐性等待下。当咱们看到下面这样的输出时就阐明成功把代码盘下来了。

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

  • 扩展阅读

    • 假如你想跑其他的Swift版别,或许想跑和你Xcode对应版别的Swift,那么能够通过如下指令来检查
    • zixun@zixundeMBP swift % xcrun swift -version
      swift-driver version: 1.75.2 Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100)
      Target: arm64-apple-macosx13.0
      
  1. Edit Code

这个时分假如咱们直接去履行Swift的build-script脚本会报如下过错:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

从报错来看问题出在cxxshim-OSX-ARM64的脚本里,原因便是cxxshim试图创立模块目录导致cxxshim,咱们只需求将 swift/stdlib/public/Cxx/cxxshim/CMakeLists.txt 此文件中的如下指令删去即可。

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

  1. Build Swift

源码修正结束后咱们开端编译,这儿咱们需求运用Swift的官方脚本,也便是在util目录下的build-script脚本。以下是我运用的指令:

$ utils/build-script \
--release-debuginfo --debug-swift-stdlib \
--xcode --skip-ios --skip-watchos --skip-tvos \
--skip-early-swiftsyntax --skip-build-benchmarks \
--swift-darwin-supported-archs="$(uname -m)"

这个脚本也会比较耗时,由于有这么多子工程需求编译,大家能够一边跑笔者一边给大家解说下各个参数的意思:

  • --xcode: 运用CMake的Xcode生成器,编译完结后会生成一个Swift.xcodeproj的工程。
  • --release-debuginfo: 编译出带有 RelWithDebInfo 环境变量的工程,相似于 DebugRelease 形式,RelWithDebInfo 会优化一部分,但一起保留调试信息。(比较debug编译速度快十分多)
  • --debug-swift-stdlib: 编译带有调试信息的 Swift标准库,假如想调试 Swift编译器,能够运用 --debug-swift
  • --skip-build-benchmarks: 表明在构建过程中越过编译和构建基准测验的过程。
  • --skip-early-swiftsyntax : 表示越过earlyswiftsyntax, 这个不加会编译犯错
  • --swift-darwin-supported-archs "$(uname -m)": 编译需求的架构$(uname -m) 指令用于获取机器架构环境,例如笔者机器获取的结果为arm64
  • --sccache: 编译缓存东西,能够进步下一次编译的速度
  • -skip-ios --skip-tvos --skip-watchos: 越过相应渠道,这儿只编译 macOS渠道。
  • --bootstrapping=off: 越过迭代构建过程,直接用现有的编译器构建方针编译器,从而节约编译时间。

或许你会遇到error: using unsupported Xcode version的过错,处理办法很简单,输入如下指令即可

export SKIP_XCODE_VERSION_CHECK=1

这样build就不会检测xcode版别了,然后咱们继续履行上面build-script指令。

当咱们看到如下界面的时分阐明咱们编译成功了:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

编译成功之后,咱们会在目录 swift-project下得到一个build文件夹,里边便是咱们 build 的产品:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

  1. Run Swift

    接下来,在上面生成的build目录中咱们找到swift-macos-arm64文件夹下的Swift工程,双击翻开Swift.xcodeproj来翻开咱们的Swift源码工程。翻开后,咱们能够看到如下弹框。这儿网上很多文章都叫你点击Automatically Create Schemes来自动生成Schemes,笔者主张是点击Manually Manage Schemes

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

由于Automatically Create Schemes 会生成一堆scheme,Swift工程自身就现已很大了,再生成这么多scheme会十分卡,咱们只要创立咱们自己需求的就好。点击Manually Manage Schemes后会弹出如下弹框:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

没关系,直接Close即可。接下来,咱们需求创立一个用来运转验证代码的Target,在 Swift.xcodeproj 工程里边,咱们点击 TARGETS 下面的 + 新建一个调试的 target,咱们挑选 macOS 的Command Line Tool来创立

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

Product Name 按自己喜好取个喜欢的名字就行(笔者的名字叫Swiftabc)。接下来咱们为 target 引进依靠,如下图引进ALL_BUILD:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

然后承认一下Build SettingHardened Runtime是否现已关闭:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

Hardened Runtime是Apple的一种安全维护策略,具体能够查阅Hardened Runtime | Apple Developer Documentation。

接下来咱们如下图点击New Scheme...来创立咱们需求编译的Scheme:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

在如下弹框中挑选咱们创立的Target(Swiftabc),然后点击OK:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

然后咱们点击如下图的Edit Scheme...来修改咱们需求变更的内容:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

在如下图的弹框中咱们将 Target SchemeBuild Configuration 修正为ReWithDebInfo

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

完结了配置,接下去就让咱们Run一下看看,不出意外的话这个时分就要出意外了,咱们会遇到如下的过错:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

这儿其时纠结了笔者好久,苦苦寻找得不到解法,直到发现这个Options.h的文件在llvm的工程里,抱着试一试编译llvm工程的心态编译了一把llvm处理了这个问题(而且发现Swift工程依靠很多llvm编译产品,必须先编译llvm),llvm工程在如下目录:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

双击翻开后和Swift工程一样,咱们挑选Manually Manage Schemes

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

然后单击弹框中的Close按钮:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

接下来通过New Scheme...新建咱们需求的Scheme:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

这儿咱们挑选All_BUILDScheme,这个Scheme能够把LLVM工程的库全部编译一遍

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

记住和Swift工程一样,把Build Configuration改成RelWithDebInfo:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

挑选好之后,咱们开端Build LLVM的工程,LLVM的工程十分大,编译比较久,主张打一把王者后回来看看是否编译完结。当你打完王者回来后会发现不出意外的话这个时分又要出意外了:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

一开端笔者想了各种办法去把这个被年代扔掉的i386架构干掉,可是发现怎样干都干不掉,假如有同学知道怎样干掉请辅导我。回到这个问题自身,其实不需求处理,由于在编译i386架构之前,我发现咱们需求的arm64的架构现已编译出来了,所以不处理也没关系。

因而,咱们能够回到Swift工程继续编译大计,继续等待咱们的编译效果,不出意外的话又又又要出意外了,咱们会得到如下的报错:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

这是由于工程找不到cmark,这个问题很好处理,还记住咱们通过脚本生成的工程吗,有一个便是cmark:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

翻开cmark-gfm.xcodeproj工程,挑选All_BUILD:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

相同的,通过Edit Scheme...Build Configuration改成RelWithDebInfo然后Build一把

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

不出意外的话这次没有出意外,顺畅Build Success。继续回到Swift工程继续编译大计。这一波,贼稳,不出意外的话美丽的Build Success会弹在咱们的XCode界面上。那怎样验证咱们现在是否能够debug Swift的源码呢?

咱们先打一波断点,搜索HeapObject.cpp文件,然后在文件的第141行打一个断点然后从头Run一把:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

完美,此时咱们现已具备了Swift Runtime的debug环境。接下来咱们在main.swift文件中添加咱们想测验的代码。

import Foundation
var arr = Array(1...10)
var arr2 = arr.filter { a in
    a > 5
}
print(arr2)

然后咱们在ArrayType.swift文件中找到filter函数,然后打上断点

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

可是当咱们Run起来的时分会发现,这个断点没有进来,怎样肥四!这儿笔者纠结了好久,也尝试了网上的办法将build-script脚本中的--debug-swift-stdlib参数替换成--debug-swift,依旧不行。而且--debug-swift会把包括编译器,LLVM等所有的host tool的符号全部编译链接进来,慢的一批,编译一次能够打3到4把王者荣耀。除非哪天咱们要去看编译器的源码,不然珍爱生命,远离--debug-swift

咱们回到XCode,发现控制台有如下输出:

warning: Swiftabc was compiled with optimization - stepping may behave oddly; variables may not be available.

大概意思便是咱们试图在运用编译器优化后的代码进行调试,这会让咱们的调试变得很古怪。有了这个头绪,咱们去看看Sequence.swift文件中filter的源码:

  @inlinable
  public __consuming func filter(
    _ isIncluded: (Element) throws -> Bool
  ) rethrows -> [Element] {
    return try _filter(isIncluded)
  }

办法的正上方有个注解@inlinable,学过C++的同学应该知道了,这不便是内敛函数么,Swift的@inlinable和C++相似,内敛会把咱们的办法体仿制在调用的时分在调用函数上打开。咱们要调试源码就要把这个给关掉。

  • 在 Xcode 的 “Build Settings” -> “Swift Compiler – Code Generation” -> “Optimization Level” 设置成 “No Optimization”
  • 在 Xcode 的 “Build Settings” -> “Apple Compiler – Code Generation” -> “Optimization Level” 设置成 “None”

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

然后咱们从头Run一把,能够发现咱们完美的进入了filter函数:

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

这个时分,咱们一般都会想看看_filter函数里的内容,咱们点击step into,可是却发现进来的却是ContiguousArrayinit办法

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

又怎样肥四!咱们先找到_filter函数

  @_transparent
  public func _filter(
    _ isIncluded: (Element) throws -> Bool
  ) rethrows -> [Element] {
    var result = ContiguousArray<Element>()
    var iterator = self.makeIterator()
    while let element = iterator.next() {
      if try isIncluded(element) {
        result.append(element)
      }
    }
    return Array(result)
  }

发现第一行var result = ContiguousArray<Element>() 好像便是初始化ContiguousArray,莫非这个函数又被内敛了。看注解并没有@inlinable,而是@_transparent

咱们在工程中大局搜@_transparent,会搜到如下文档内容:

Semantically, `@_transparent` means something like "treat this operation as
if it were a primitive operation". The name is meant to imply that both the
compiler and the compiled program will "see through" the operation to its
implementation.
This has several consequences:
-   **Any calls to a function marked** **`@_transparent`** **MUST be inlined prior to**
**doing dataflow-related diagnostics, even under** **`-Onone`**. This may be
necessary to *catch* dataflow errors.
-   Because of this, a `@_transparent` function is implicitly inlinable, in
that changing its implementation most likely will not affect callers in
existing compiled binaries.
-   Because of this, a public or `@usableFromInline` `@_transparent` function
MUST only reference public symbols, and MUST not be optimized based on
knowledge of the module it's in. [The former is caught by checks in Sema.]
-   **Debug info SHOULD skip over the inlined operations when single-stepping**
**through the calling function**.
This is all that `@_transparent` means.

大概意思便是@_transparent也是内敛,而且不会受咱们前面设置的Optimization Level影响.这儿我现在找到的唯一办法便是把@_transparent注释掉。

//  @_transparent
  public func _filter(
    _ isIncluded: (Element) throws -> Bool
  ) rethrows -> [Element] {
    var result = ContiguousArray<Element>()
    var iterator = self.makeIterator()
    while let element = iterator.next() {
      if try isIncluded(element) {
        result.append(element)
      }
    }
    return Array(result)
  }

注释后,咱们就能够完美的进入到_filter函数啦

手把手搭建Swift语言源码(最新v5.8.1)本地调试环境

现在,咱们完美的把当时最新的Swift版别5.8.1本地跑了起来,而且关于咱们最有价值的SwiftRuntimeSwiftCore都能够调试。

One More Thing

  1. 当我要切换swift版别从头跑一遍需求怎样做?

  • 清空缓存

    • command + shift + k : 清空内存缓存

    • 删去DerivedData

      • 手把手搭建Swift语言源码(最新v5.8.1)本地调试环境
  • 删去build

    • 删去swift-project/build目录下所有内容
    • 手把手搭建Swift语言源码(最新v5.8.1)本地调试环境
  • 切换版别

    • 用脚本切换到你需求的tag,千万不要用git checkout,由于swift有很多子库房需求同步切换
    • utils/update-checkout --tag swift-XXX-RELEASE
      
  • Build&Run

    • 依照这篇文档从头Build和Run

  1. 当我遇到问题处理不了上哪里搜/问?

  • Github issue: github.com/apple/swift…
  • Swift Forums: forums.swift.org/
  1. 参考材料
  • /post/721956…
  • github.com/apple/swift…

招聘

假如你看完觉得这篇文章对你有协助,想和咱们一起同事,欢迎参加字节跳动国际化短视频产品研制团队

团队介绍

国际化短视频产品研制团队,旨在完结字节跳动国际化短视频事务的研制工作,搭建及维护业界领先的产品。

参加咱们,你能接触到包括用户增长、社交 直播、内容发明、内容消费等中心事务场景,支撑产品在全球赛道上高速开展;也能接触到包括服务架构、根底技能等方向上的技能应战,保障事务继续高质量、高效率、且安全地为用户服务;一起还能为不同事务场景提供全面的技能处理计划,优化各项产品指标及用户体会。

在这儿,有大牛带队与大家一起不断探究前沿,打破想象空间。在这儿,你的每一行代码都将服务亿万用户。在这儿,团队专业且朴实,合作气氛相等且轻松。

以下岗位可base上海、杭州、北京。

iOS 高档研制工程师

岗位描绘

  1. 负责国际化短视频产品内容发现方向的iOS研制、功用完结和产品迭代; 2. 与产品规划配合,深度参加需求评审,功用定义,体会优化等要害讨论; 3. 规划杰出的技能架构,推进并优化代码的健壮性、可维护性,并编写明晰的技能文档。

岗位要求

  1. 本科及以上计算机相关专业结业,2年以上相关工作经验;
  2. 有较强的学习才能,将新技能应用到事务实践场景中,推进技能迭代;
  3. 有杰出的编程习惯,代码结构明晰,命名标准;
  4. 熟练掌握 Objective-C 或 Swift 言语,熟悉App开发的主流结构和开发形式;
  5. 对软件产品有强烈的责任心,具备杰出的交流才能和优异的团队协作才能。