Swift & LLVM

visitor
3 min readMay 7, 2020

What is LLVM

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. Despite its name, LLVM has little to do with traditional virtual machines. The name “LLVM” itself is not an acronym; it is the full name of the project.

How to compile LLVM for Swift

Checkout Source Code

It’s better to checkout the LLVM source code forked by Apple.

git clone https://github.com/apple/llvm-project

CMake

I recommend that you use Ninja to build in place of Xcode. Building through Ninja is faster.

cd llvm-project mkdir build cd build cmake -G -DCMAKE_OSX_ARCHITECTURES= \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_BUILD_RUNTIME=Off \ -DLLVM_INCLUDE_TESTS=Off \ -DLLVM_INCLUDE_EXAMPLES=Off \ -DLLVM_TARGETS_TO_BUILD= \ -DLLVM_BUILD_LLVM_DYLIB=true \ ../llvm # for iOS device cmake -G -DCMAKE_OSX_ARCHITECTURES= \ -DCMAKE_TOOLCHAIN_FILE=../llvm/cmake/platforms/iOS.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_BUILD_RUNTIME=Off \ -DLLVM_INCLUDE_TESTS=Off \ -DLLVM_INCLUDE_EXAMPLES=Off \ -DLLVM_ENABLE_BACKTRACES=Off \ -DLLVM_TARGETS_TO_BUILD= \ -DLLVM_BUILD_LLVM_DYLIB=true \ ../llvm

Build

It’s easy to build libLLVM.dylib, just one line. After the Ninja build success, we can find libLLVM.dylib in lib/libLLVM.dylib path.

ninja libLLVM.dylib

How to use LLVM API in Swift

Here we create a new project named HelloLLVM.

Import LLVM Header & libLLVM.dylib

First, we need to create a header for import LLVM API.

cd HelloLLVM/HelloLLVM
mkdir LLVM && cd LLVM && mkdir Header && mkdir Frameworks
cp -rf YOUR_LLVM_PROJECT_PATH/_llvm_project/llvm/include/llvm-c Header
cp -rf YOUR_LLVM_PROJECT_PATH/llvm_project/build/include/llvm Header
cp YOUR_LLVM_PROJECT_PATH/llvm_project/build/lib/libLLVM.dylib Frameworks

Add the previous Header Path to Header Search Paths.

Add the libLLVM.dylib to the project.

If using libLLVM.dylib in the iOS platform, we need to close bitcode before building HelloLLVM. Otherwise, we will face the link problem.

ld: 'LLVM/Frameworks/libLLVM.dylib' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. file 'LLVM/Frameworks/libLLVM.dylib' for architecture arm64

Finally, click the build button. The project will run successfully if the previous operation is correct.

Run a simple demo

import Foundation
import LLVM_C

let module = LLVMModuleCreateWithName("Hello LLVM")
defer { LLVMDisposeModule(module) }

let int32 = LLVMInt32Type()
let returnType = int32
let paramTypes = UnsafeMutablePointer<LLVMTypeRef?>.allocate(capacity: 2)
paramTypes.initialize(from: [int32, int32], count: 2)
defer { paramTypes.deallocate() }
let functionType = LLVMFunctionType(returnType, paramTypes, 2, 0)

let sumFunction = LLVMAddFunction(module, "sum", functionType)
let entryBlock = LLVMAppendBasicBlock(sumFunction, "entry")

let builder = LLVMCreateBuilder()
LLVMPositionBuilderAtEnd(builder, entryBlock)

let a = LLVMGetParam(sumFunction, 0)
let b = LLVMGetParam(sumFunction, 1)
let temp = LLVMBuildAdd(builder, a, b, "temp")
LLVMBuildRet(builder, temp)

var executionEngine: LLVMExecutionEngineRef?
var outputMsg: UnsafeMutablePointer<Int8>?

LLVMLinkInInterpreter()
LLVMCreateInterpreterForModule(&executionEngine, module, &outputMsg)

if let outputMsg = outputMsg {
print("can't initialize engine: \(String(cString: outputMsg))")
exit(1)
}

let x: UInt64 = 10
let y: UInt64 = 24

let parameters = UnsafeMutablePointer<LLVMGenericValueRef?>.allocate(capacity: 2)
parameters.initialize(from: [LLVMCreateGenericValueOfInt(int32, x, 0), LLVMCreateGenericValueOfInt(int32, y, 1)], count: 2)
defer { parameters.deallocate() }

let result = LLVMRunFunction(executionEngine, sumFunction, 2, parameters)

LLVMDumpModule(module)
print("===============================")
print("\(x) + \(y) = \(LLVMGenericValueToInt(result, 0))")

Yep! That’s all! (Above code just converted from the below article 😎)

Reference

--

--