Introduction
Swift was in limelight during last 2 years and was ranked #1 most loved programming language of 2015. It is a general purpose, multi-paradigm, statically typed language. Developed by team led by Chris Lattner at Apple.
While introducing Swift in WWDC14 Tim Cook described it using four words which are:
- Fast
- Modern
- Safe
- Interactive
In this blog, I will be explaining why and how swift achieves above mentioned qualities.
Discovering All the Underlying Swift Power
Swift is a strongly typed compiled programming language. This makes it a very safe programming language. Swift is very strict about types, and it verifies that all types are used correctly in the source code. It catches many issues at compile time.
Swift is also a static programming language. Its source code is compiled to the assembly code and the assembly code is compiled to the machine code using the LLVM tool. Running native machine code instructions is the fastest way of doing this. In comparison, Java and C# are compiled to a piece of intermediate code, and it needs a virtual machine to run it, or another tool that will translate it into machine instructions. Because Swift doesn’t do this at runtime, it has a very big performance gain.
By mixing strongly typed rules and compiling to assembly code, Swift can analyse code very well and perform very good assembly code optimization. Swift is also built to be nice to write, with a pleasant and clean syntax and modern features and types. This unique combination of syntax, powerful features, safety, and performance makes Swift a very amazing programming language.
The Compilation
The compilation of the Swift source code is quite an interesting process, and it involves several steps. The Swift compiler uses LLVM for optimisation and binary generation. To better understand the entire process, refer to this flow diagram:
First, the Swift source code is transformed into an AST (short for Abstract Syntax Tree). Then, it is transformed into SIL (short for Swift Intermediate Language), first into a raw SIL and then into a canonical SIL. After that, it is transformed into LLVM IR (short for Intermediate Representation). In this step, LLVM takes care of the rest. It takes IR, does an optimisation, and produces an assembly and, after that, an executable for a specific architecture.
The interesting part in preceding diagram is the steps for generating SIL. It’s a Swift-specific optimisation and it was created specifically for swift. Other programming languages, such as C, don’t do this optimisation before they generate LLVM IR, and they have one less optimisation step.
Swiftc
There are 2 different Swift compiler tools, swift and swiftc. If you see the help for both the tool using the -h option you will observe they both are swift compilers with almost similar options. The main difference being, the swift tool compiles and executes swift code. If you run it without any arguments, it will launch a REPL and give you the ability to evaluate the Swift code. While
The swiftc compiler compiles swift code and produces the result, but it doesn’t execute it. Depending on the option, you can get a binary file, an assembly, an LLVM IR representation, or something else.
With swiftc, it’s possible to generate the results for each of those steps. It’s incredibly useful for code optimisation analysis. To see all the available modes, just run xcrun swiftc -h. Now, let’s quickly take a look on them.
Swift AST
Swiftc has three different options for generating AST. Each of them generates AST with different levels of details. The AST code representation shows us how the Swift compiler sees and analyzes the code:
xcrun swiftc -dump-ast main.swift
xcrun swiftc -dump-parse main.swift
xcrun swiftc -print-ast main.swift
The output of -dump-ast contains the maximum details, and it could be hard to analyze. Let’s take a look at the -dump-parse example first:
(source_file
(var_decl “a” type='<null type>’ let storage_kind=stored)
(top_level_code_decl
(brace_stmt
(pattern_binding_decl
(pattern_named ‘a’)
(integer_literal_expr type='<null>’ value=10)))
This AST code represents the var a = 10 Swift code. Each instruction is parsed into a separate tree node and then put together in a tree representation. You can find more information about Clang’s AST at http://clang.llvm.org/docs/IntroductionToTheClangAST.html.
SIL
The Swift Intermediate Language (SIL) is one of the most useful tools for analyzing Swift code. It contains many details and is very readable and easy to analyze. For generating a SIL, xcrun has two modes; -emit-silgen generates raw SIL and -emit-sil generates canonical SIL:
xcrun swiftc -emit-silgen main.swift
xcrun swiftc -emit-sil main.swift
Raw SIL and canonical SIL are almost the same. Raw SIL is a bit simpler and it doesn’t include the details of private function implementations and some global objects. Let’s take a look at the generated raw SIL:
sil_stage raw
import Builtin
import Swift
import SwiftShims
// main.a : Swift.Int
sil_global [let] @_Tv4main1aSi : $Int
…
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<UnsafeMutablePointer<Int8>>) -> Int32 {
…
}
// main.bye () -> ()
sil hidden @_TF4main3byeFT_T_ : $@convention(thin) () -> () {…
}
// static Swift.+ infix (Swift.Int, Swift.Int) -> Swift.Int
sil [transparent] [fragile] @_TZFSsoi1pFTSiSi_Si : $@convention(thin) (Int, Int) -> Int
A really nice feature of SIL is that it contains comments that explain the generated code. The let a: Int statement would be translated into @_Tv4main1aSi : $Int and we can see this from the comment that stays above the generated SIL:
// main.a : Swift.Int sil_global @_Tv4main1aSi : $Int
The SIL represents Swift code in a mangled format. The names contain a lot of information about the type, the count of symbols in the name, and so on. Some mangled names can be very long and hard to read, such as _TZvOSs7Process11_unsafeArgvGVSs20UnsafeMutablePointerGS0_VSs4Int8__.
We can demangle a name back to its normal notation with the swift-demangle tool. Let’s try to demangle @_Tv4main1aSi and see whether it really translates into main.a : Swift.Int:
xcrun swift-demangle _Tv4main1aSi _Tv4main1aSi ---> main.a : Swift.Int
If you want to learn more about name mangling, you can read a great post about it written by Mike Ash at https://mikeash.com/pyblog/friday-qa-2014-08-15-swift-name-mangling.html.
LLVM IR
Intermediate Representation (IR) is a more low-level code representation. It is not as human-friendly and readable as SIL. This is because it has more information for the compiler than for humans. We can use IR to compare different programming languages. To get Swift’s IR, use the -emit-ir options, and to get IR for C, we can use clang -emit-llvm:
xcrun swiftc -emit-ir main.swift clang -S -emit-llvm main.c -o C-IR.txt
Other swiftc options
The swiftc compiler is very powerful and has many more modes and options. You can create an assembly, a binary, a linked library, and object files. You can also specify many options, such as an output file with the -o option, optimization -O, -Onone, and many others:
xcrun swiftc -emit-assembly main.swift -o assembly
Analysing executable files
It is very difficult to analyse the assembly code generated by the swiftc compiler. To make our lives easier, we will use a Hopper Disassembler tool to disassemble executable files, generating a piece of pseudocode and analysing it. You can download the free version of Hopper from http://www.hopperapp.com.
The Hopper Disassembler tool can work with binary, executable, and object files. The easiest way of using it is by generating an executable file with the swiftc main.swift command and opening it in Hopper. You can simply drag and drop the main executable file to open it in Hopper.
On the left-hand side, you can find all the labels for functions and variables and navigate to them. The search feature is very useful when you are analyzing a big project with many functions. In the center is an assembly code; you can press Alt + Enter to see the pseudocode for the current procedure. It is much easier to analyze high-level pseudocode.
We can also compile an application in Xcode and disassemble our SimpleApp.app in Hopper. This allows us to analyze very large and complex applications as well.
So, that’s all on compilation process for a swift, I hope you did get the reason behind Tim Cook calling Swift a Fast, Safe and Interactive language. I guess I missed Modern, because I reckon that’s a topic for another writing.