Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/llvm-swift/LLVMSwift

A Swift wrapper for the LLVM C API (version 11.0)
https://github.com/llvm-swift/LLVMSwift

compiler llvm swift swiftpm

Last synced: 3 months ago
JSON representation

A Swift wrapper for the LLVM C API (version 11.0)

Awesome Lists containing this project

README

        

# LLVMSwift
[![Build Status](https://travis-ci.org/llvm-swift/LLVMSwift.svg?branch=master)](https://travis-ci.org/llvm-swift/LLVMSwift) [![Documentation](https://cdn.rawgit.com/llvm-swift/LLVMSwift/master/docs/badge.svg)](https://llvm-swift.github.io/LLVMSwift) [![Slack Invite](https://llvmswift-slack.herokuapp.com/badge.svg)](https://llvmswift-slack.herokuapp.com)

LLVMSwift is a pure Swift interface to the [LLVM](http://llvm.org) 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`

```swift
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:

```swift
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:

```swift
let constant = IntType.int64.constant(21)
let sum = builder.buildAdd(constant, constant)
builder.buildRet(sum)
```

This simple program generates the following IR:

```llvm
// 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](http://llvm.org/docs/LangRef.html#conversion-operations)).
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](http://llvm.org/docs/LangRef.html#phi-instruction). 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:

```swift
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:

```swift
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:

```llvm
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:

```swift
// 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:
- `brew install llvm@11`
- 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

```swift
.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](https://github.com/harlanhaskins/trill) for
all its code generation.

## Authors

- Harlan Haskins ([@harlanhaskins](https://github.com/harlanhaskins))
- Robert Widmann ([@CodaFi](https://github.com/CodaFi))

## License

This project is released under the MIT license, a copy of which is available
in this repo.