Hello, SIL

LLVM Developers’ Meeting 2015 features a talk entitled Swift’s High-Level IR: A Case Study of Complementing LLVM IR with Language-Specific Optimization. The abstract explains:

The Swift programming language is built on LLVM and uses LLVM IR and the LLVM backend for code generation, but it also contains a new high-level IR called SIL to model the semantics of the language (and perform optimizations) at a higher level.

Ars Technica provides a fantastic introduction to Swift Intermediate Language (SIL). In this post, we’ll:

  • Emit “raw” SIL for a simple Swift program, demangle its symbols, and examine its structure.
  • Emit “canonical” SIL for that same program, then compare it to the raw SIL.
  • Modify the canonical SIL directly, then compile and run it, in order to take advantage of private functions in the Swift runtime.

All the code that follows was produced using Xcode 7.1 (7B91b).

Raw SIL with swiftc -emit-silgen

Let’s begin with an extremely simple Swift program, banana.swift:

We can emit the raw SIL for this program using xcrun swiftc -emit-silgen banana.swift. SIL contains mangled Swift code, but we can demangle it using xcrun swift-demangle.

The following is the raw SIL. My annotations are in multi-line comment blocks (/** ... */). Single-line comments are added by the Swift compiler.

Canonical SIL with swiftc -emit-sil

What’s the difference between “raw” and “canonical” SIL? Let’s take a look. We can emit canonical SIL using xcrun swiftc -emit-sil banana.swift. Once again, I’ve run the result through xcrun swift-demangle and added annotations below.

It appears that canonical SIL has “optimized” the code such that Swift._didEnterMain is never called, and instead an empty tuple is inlined in its place. The function still appears below the main function, though:

Editing and Compiling SIL: Executing Code in Swift._didEnterMain

The Swift compiler doesn’t just compile Swift code. You can invoke swiftc on canonical SIL to generate a program:

SIL has access to Builtin types that we cannot access in normal Swift code. By editing SIL directly, we can compile a program in which we have free access to these private types.

Let’s begin by executing code during Swift._didEnterMain. In order to do so, we must emit canonical, mangled SIL. Here’s what the Swift._didEnterMain function looks like without running xcrun swift-demangle on it:

Run xcrun swiftc banana.sil -o banana, and you’ll see that the compiler successfully creates a program executable named banana.

Now let’s modify the SIL such that Swift._didEnterMain is actually called from within main.

The Swift._didEnterMain doesn’t actually do anything yet. Let’s modify it to print an additional statement. First, we add a function body.

Then, we add code to print an additional string.

Compile and run the SIL, and you’ll see a new string printed before main is executed:

And there you have it! We’ve hooked into an internal Swift runtime event, Swift._didEnterMain, to execute arbitrary code.

In future posts, we’ll explore using Builtin, as well as optimizations SIL performs before transforming a program into LLVM IR. Follow @swiftcio for updates.