LLVMSwift

LLVMSwift is a pure Swift interface to the LLVM API and its associated libraries. It provides native, easy-to-use components to make compiler development fun.
Introduction
LLVM IR
The root unit of organization of an LLVM IR program is a Module
let module = Module(name: "main")
LLVM IR construction is handled by IRBuilder
objects. An IRBuilder
is a cursor pointed inside a context, and as such has ways of extending that context and moving around inside of it.
Defining a function and moving the cursor to a point where we can begin inserting instructions is done like so:
let builder = IRBuilder(module: module)
let main = builder.addFunction("main",
type: FunctionType([], IntType.int64))
let entry = main.appendBasicBlock(named: "entry")
builder.positionAtEnd(of: entry)
Inserting instructions creates native IRValue
placeholder objects that allow us to structure LLVM IR programs just like Swift programs:
let constant = IntType.int64.constant(21)
let sum = builder.buildAdd(constant, constant)
builder.buildRet(sum)
This simple program generates the following IR:
// module.dump()
define i64 @main() {
entry:
ret i64 42
}
Types
LLVM IR is a strong, statically typed language. As such, values and functions
are tagged with their types, and conversions between them must be explicit (see
Conversion Operators).
LLVMSwift represents this with values conforming to the IRType
protocol and defines
the following types:
Type |
Represents |
VoidType |
Nothing; Has no size |
IntType |
Integer and Boolean values (i1 ) |
FloatType |
Floating-point values |
FunctionType |
Function values |
LabelType |
Code labels |
TokenType |
Values paired with instructions |
MetadataType |
Embedded metadata |
X86MMXType |
X86 MMX values |
PointerType |
Pointer values |
VectorType |
SIMD data |
ArrayType |
Homogeneous values |
Structure Type |
Heterogeneous values |
Control Flow
Control flow is changed through the unconditional and conditional br
instruction.
LLVM is also famous for a control-flow specific IR construct called a PHI node. Because all instructions in LLVM IR are in SSA (Single Static Assignment) form, a PHI node is necessary when the value of a variable assignment depends on the path the flow of control takes through the program. For example, let’s try to build the following Swift program in IR:
func calculateFibs(_ backward : Bool) -> Double {
let retVal : Double
if !backward {
// the fibonacci series (sort of)
retVal = 1/89
} else {
// the fibonacci series (sort of) backwards
retVal = 1/109
}
return retVal
}
Notice that the value of retVal
depends on the path the flow of control takes through this program, so we must emit a PHI node to properly initialize it:
let function = builder.addFunction("calculateFibs",
type: FunctionType([IntType.int1],
FloatType.double))
let entryBB = function.appendBasicBlock(named: "entry")
builder.positionAtEnd(of: entryBB)
// allocate space for a local value
let local = builder.buildAlloca(type: FloatType.double, name: "local")
// Compare to the condition
let test = builder.buildICmp(function.parameters[0], IntType.int1.zero(), .equal)
// Create basic blocks for "then", "else", and "merge"
let thenBB = function.appendBasicBlock(named: "then")
let elseBB = function.appendBasicBlock(named: "else")
let mergeBB = function.appendBasicBlock(named: "merge")
builder.buildCondBr(condition: test, then: thenBB, else: elseBB)
// MARK: Then Block
builder.positionAtEnd(of: thenBB)
// local = 1/89, the fibonacci series (sort of)
let thenVal = FloatType.double.constant(1/89)
// Branch to the merge block
builder.buildBr(mergeBB)
// MARK: Else Block
builder.positionAtEnd(of: elseBB)
// local = 1/109, the fibonacci series (sort of) backwards
let elseVal = FloatType.double.constant(1/109)
// Branch to the merge block
builder.buildBr(mergeBB)
// MARK: Merge Block
builder.positionAtEnd(of: mergeBB)
let phi = builder.buildPhi(FloatType.double, name: "phi_example")
phi.addIncoming([
(thenVal, thenBB),
(elseVal, elseBB),
])
builder.buildStore(phi, to: local)
let ret = builder.buildLoad(local, type: FloatType.double, name: "ret")
builder.buildRet(ret)
This program generates the following IR:
define double @calculateFibs(i1) {
entry:
%local = alloca double
%1 = icmp ne i1 %0, false
br i1 %1, label %then, label %else
then: ; preds = %entry
br label %merge
else: ; preds = %entry
br label %merge
merge: ; preds = %else, %then
%phi_example = phi double [ 0x3F8702E05C0B8170, %then ], [ 0x3F82C9FB4D812CA0, %else ]
store double %phi_example, double* %local
%ret = load double, double* %local
ret double %ret
}
JIT
LLVMSwift provides a JIT abstraction to make executing code in LLVM modules quick and easy. Let’s execute the PHI node example from before:
// Setup the JIT
let jit = try JIT(machine: TargetMachine())
typealias FnPtr = @convention(c) (Bool) -> Double
_ = try jit.addEagerlyCompiledIR(module) { (name) -> JIT.TargetAddress in
return JIT.TargetAddress()
}
// Retrieve a handle to the function we're going to invoke
let addr = try jit.address(of: "calculateFibs")
let fn = unsafeBitCast(addr, to: FnPtr.self)
// Call the function!
print(fn(true)) // 0.00917431192660551...
print(fn(false)) // 0.0112359550561798...
Installation
There are a couple annoying steps you need to accomplish before building
LLVMSwift:
- Install LLVM 11.0+ using your favorite package manager. For example:
- Ensure
llvm-config
is in your PATH
- That will reside in the
/bin
folder wherever your package manager
installed LLVM.
- Create a pkg-config file for your specific LLVM installation.
- We have a utility for this:
swift utils/make-pkgconfig.swift
Once you do that, you can add LLVMSwift as a dependency for your own Swift
compiler projects!
Installation with Swift Package Manager
.package(url: "https://github.com/llvm-swift/LLVMSwift.git", from: "0.8.0")
Installation without Swift Package Manager
We really recommend using SwiftPM with LLVMSwift, but if your project is
structured in such a way that makes using SwiftPM impractical or impossible,
use the following instructions:
- Xcode:
- Add this repository as a git submodule
- Add the files in
Sources/
to your Xcode project.
- Under
Library Search Paths
add the output of llvm-config --libdir
- Under
Header Search Paths
add the output of llvm-config --includedir
- Under
Link Target with Libraries
drag in
/path/to/your/llvm/lib/libLLVM.dylib
This project is used by Trill for
all its code generation.
Authors
License
This project is released under the MIT license, a copy of which is available
in this repo.
LLVMSwift
LLVMSwift is a pure Swift interface to the LLVM API and its associated libraries. It provides native, easy-to-use components to make compiler development fun.
Introduction
LLVM IR
The root unit of organization of an LLVM IR program is a
Module
LLVM IR construction is handled by
IRBuilder
objects. AnIRBuilder
is a cursor pointed inside a context, and as such has ways of extending that context and moving around inside of it.Defining a function and moving the cursor to a point where we can begin inserting instructions is done like so:
Inserting instructions creates native
IRValue
placeholder objects that allow us to structure LLVM IR programs just like Swift programs:This simple program generates the following IR:
Types
LLVM IR is a strong, statically typed language. As such, values and functions are tagged with their types, and conversions between them must be explicit (see Conversion Operators). LLVMSwift represents this with values conforming to the
IRType
protocol and defines the following types:i1
)Control Flow
Control flow is changed through the unconditional and conditional
br
instruction.LLVM is also famous for a control-flow specific IR construct called a PHI node. Because all instructions in LLVM IR are in SSA (Single Static Assignment) form, a PHI node is necessary when the value of a variable assignment depends on the path the flow of control takes through the program. For example, let’s try to build the following Swift program in IR:
Notice that the value of
retVal
depends on the path the flow of control takes through this program, so we must emit a PHI node to properly initialize it:This program generates the following IR:
JIT
LLVMSwift provides a JIT abstraction to make executing code in LLVM modules quick and easy. Let’s execute the PHI node example from before:
Installation
There are a couple annoying steps you need to accomplish before building LLVMSwift:
brew install llvm@11
llvm-config
is in yourPATH
/bin
folder wherever your package manager installed LLVM.swift utils/make-pkgconfig.swift
Once you do that, you can add LLVMSwift as a dependency for your own Swift compiler projects!
Installation with Swift Package Manager
Installation without Swift Package Manager
We really recommend using SwiftPM with LLVMSwift, but if your project is structured in such a way that makes using SwiftPM impractical or impossible, use the following instructions:
Sources/
to your Xcode project.Library Search Paths
add the output ofllvm-config --libdir
Header Search Paths
add the output ofllvm-config --includedir
Link Target with Libraries
drag in/path/to/your/llvm/lib/libLLVM.dylib
This project is used by Trill for all its code generation.
Authors
License
This project is released under the MIT license, a copy of which is available in this repo.