Xcode Build System : Know it better

Varun Tomar
6 min readMay 27, 2020

A program undergoes a lot of transformations before it can be run on a real device. Like any other language processing system, Xcode build system needs to runs a number of commands and passes various attributes to handles their order of execution, dependencies and more. Xcode build system has five phases as below:

  1. Preprocessing
  2. Compiler
  3. Assembler
  4. Linker
  5. Loader

Preprocessing

The purpose of preprocessing is to transform our program in a way that it can be absorbed by a compiler. It replace macros with their definitions, discovers dependencies and resolves preprocessor directives. If we consider that Swift compiler does not have a preprocessor then we are not allowed to define macros in our Swift projects. Xcode 8 onwards SWIFT_ACTIVE_COMPILATION_CONDITIONS build setting allows us to define our preprocessor flags. This matches the behaviour same as in Objective-C and Preprocessor Macros.

Compiler

Compilation is one of the important task in this whole process. Compiler is a program that basically translates source code from a high order language like Swift & Objective C to a low level language like object file. In iOS there are 2 types of compiler “CLang & swiftc”. High level diagram of this process is as below :

Note : Compiler consists of 2 main parts: front end and back end. Clang is a C/C++/Objective-C compiler, which uses its own frontend, and LLVM as the backend, same in case of swiftc. What is this mess, where this LLVM come from?

Don’t worry 😉, I am going to give a brief idea on this, although this needs to be a separate article in detail (I will write that for sure in my coming articles).

LLVM (Low Level Virtual Machine) is a backend compiler meant to build compilers on top of it. It deals with optimisation and production of code adapted to the target architecture(ARM, x86). CLang/Swiftc is a front end compiler which parses C, C++, Objective C and Swift code and translates it into a intermediate representation(IR) suitable for LLVM. You will understand it in a better way later in this article.

Lets understand swift program compilation granularity, please refer below diagram :

This is how Swift compiler works step by step :

  1. Swift code gets parsed into AST (Abstract Syntax Tree). AST is a tree representation of the abstract syntactic structure of source code. Each node in this tree denotes a construct. How it looks, let’s better understand it by an example. Blow is the Swift code file we are going to work upon :

Let’s generate above file AST by using command on terminal “xcrun swiftc -dump-ast <filename>.swift”.

AST gives interesting 🧐 results, It has huge content but i am showing a portion of that. If you just read the result you will learn a lot about what is happening behind the scene. Below code shows few interesting things:

(func_decl range=[Test.swift:12:3 - line:12:16] "fly()" interface type='(Bird) -> () -> ()' access=internal(parameter "self")

NOTE : As shown above when we create a function, swift will pass a parameter which is “self” that’s why we have access to other functions.

(class_decl range=[Test.swift:17:1 - line:20:1] "Sparrow" interface type='Sparrow.Type' access=internal non-resilient inherits: Bird

Above tree code shows how inheritance takes into account.

2. Now comes to semantic analysis that could be performed when the AST is constructed. Semantic analysis is responsible for transforming AST into a well-formed, fully-type-checked form of the AST, remove warnings or errors for semantic problems in the source code.

3. Next SIL generation and optimisation comes on the table. To get the SIL after this phase: use command “xcrun swiftc -emit-silgen <filename>.swift”

It’s really interesting 🤔 to see SIL generated part, If you see above terminal output, you might react like : “OMG 😮 . what the heck is @$s4Test4BirdC3flyyyF ?”. But it’s not as scary as you thought. It is Name mangling used to squash additional information of an entity into a single string. The encoded name could tell us its type (class/struct/enum), module, context… For example, in @$s4Test4BirdC3flyyyF, the letterC following Bird implies that Bird is a class, it can tell you more. We won’t dive into the detail of this technique.In case you are interested to explore it, add a comment in feedback section. I will write separately for it. Moreover we can trace a mangled string back to the originally readable text using swift-demangle .

Friendly SIL can be generated with command “xcrun swiftc -emit-silgen <filename>.swift | xcrun swift-demangle”. Let's create it for our sample class.

This time it is more readable. Let's walk with me🚶‍♂️ through above SIL :

  • A function starts with keyword sil.
  • The keyword hidden corresponds to internal in Swift code.
  • @main.Bird.fly() -> () is the de-mangled text of @$s4Test4BirdC3flyyyF, representing the function name.
  • $@convention(method) means a call to this function requires a context. For example, in self.fly(), self is the context of the function call.
  • $@convention(thin) says this is a free function. No context is needed to make an invocation.
  • If the argument is reference type, an annotation @owned is used.

Find it interesting 🧐? I do have more interesting findings..

4. After SIL generation and optimisation, here comes IR (Intermediate Representation). IR is the input for LLVM. Run command “xcrun swiftc -emit-ir <filename>.swift. You might scratch your head by seeing output as this is moving towards machine language🤯🤯

Assembler

Here onwards control goes to Assembler that convert input into relocatable machine code. It produces Mach-O files.

Mach-O file is used for object files, executables and libraries. It is a collection of bytes grouped in some meaningful chunks that will run on the ARM processor of an iOS device or the Intel processor on a Mac.

Linker & Loader

Linker is a computer program that merges various object files and libraries together in order to make a single Mach-O executable. Lastly, Loader which is a part of operating system, brings a program into memory and executes it.

Thanks for reading this🙏🙏. Any feedback in the comment section would be appreciated. If you enjoyed reading this post, please share and give some claps.

You can follow me for fresh articles.

--

--