Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ matrix:
osx_image: xcode8
before_install:
- export PATH=/usr/local/opt/llvm/bin:"${PATH}"
- brew update
- brew install llvm
- sudo swift utils/make-pkgconfig.swift
script:
Expand Down
43 changes: 43 additions & 0 deletions Sources/LLVM/Clause.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#if !NO_SWIFTPM
import cllvm
#endif

/// Enumerates the supported kind of clauses.
public enum LandingPadClause: IRValue {
/// This clause means that the landingpad block should be entered if the
/// exception being thrown matches or is a subtype of its type.
///
/// For C++, the type is a pointer to the `std::type_info` object
/// (an RTTI object) representing the C++ exception type.
///
/// If the type is `null`, any exception matches, so the landing pad should
/// always be entered. This is used for C++ catch-all blocks (`catch (...)`).
///
/// When this clause is matched, the selector value will be equal to the value
/// returned by `@llvm.eh.typeid.for(i8* @ExcType)`. This will always be a
/// positive value.
case `catch`(IRGlobal)
/// This clause means that the landing pad should be entered if the exception
/// being thrown does not match any of the types in the list (which, for C++,
/// are again specified as `std::type_info pointers`).
///
/// C++ front-ends use this to implement C++ exception specifications, such as
/// `void foo() throw (ExcType1, ..., ExcTypeN) { ... }`.
///
/// When this clause is matched, the selector value will be negative.
///
/// The array argument to filter may be empty; for example, `[0 x i8**] undef`.
/// This means that the landing pad should always be entered. (Note that such
/// a filter would not be equivalent to `catch i8* null`, because filter and
/// catch produce negative and positive selector values respectively.)
case filter(IRType, [IRGlobal])

public func asLLVM() -> LLVMValueRef {
switch self {
case let .`catch`(val):
return val.asLLVM()
case let .filter(ty, arr):
return ArrayType.constant(arr, type: ty).asLLVM()
}
}
}
96 changes: 94 additions & 2 deletions Sources/LLVM/IRBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -935,15 +935,107 @@ public class IRBuilder {
/// - parameter args: A list of arguments.
/// - parameter name: The name for the newly inserted instruction.
///
/// - returns: A value representing `void`.
@discardableResult
/// - returns: A value representing the result of returning from the callee.
public func buildCall(_ fn: IRValue, args: [IRValue], name: String = "") -> IRValue {
var args = args.map { $0.asLLVM() as Optional }
return args.withUnsafeMutableBufferPointer { buf in
return LLVMBuildCall(llvm, fn.asLLVM(), buf.baseAddress!, UInt32(buf.count), name)
}
}

// MARK: Exception Handling Instructions

/// Build a call to the given function with the given arguments with the
/// possibility of control transfering to either the `next` basic block or
/// the `catch` basic block if an exception occurs.
///
/// If the callee function returns with the `ret` instruction, control flow
/// will return to the `next` label. If the callee (or any indirect callees)
/// returns via the `resume` instruction or other exception handling
/// mechanism, control is interrupted and continued at the dynamically nearest
/// `exception` label.
///
///
/// - parameter fn: The function to invoke.
/// - parameter args: A list of arguments.
/// - parameter next: The destination block if the invoke succeeds without exceptions.
/// - parameter catch: The destination block if the invoke encounters an exception.
/// - parameter name: The name for the newly inserted instruction.
///
/// - returns: A value representing the result of returning from the callee
/// under normal circumstances. Under exceptional circumstances, the value
/// represents the value of any `resume` instruction in the `catch` block.
public func buildInvoke(_ fn: IRValue, args: [IRValue], next: BasicBlock, catch: BasicBlock, name: String = "") -> IRValue {
precondition(`catch`.firstInstruction!.opCode == .landingPad, "First instruction of catch block must be a landing pad")

var args = args.map { $0.asLLVM() as Optional }
return args.withUnsafeMutableBufferPointer { buf in
return LLVMBuildInvoke(llvm, fn.asLLVM(), buf.baseAddress!, UInt32(buf.count), next.llvm, `catch`.llvm, name)
}
}

/// Build a landing pad to specify that a basic block is where an exception
/// lands, and corresponds to the code found in the `catch` portion of a
/// `try/catch` sequence.
///
/// The clauses are applied in order from top to bottom. If two landing pad
/// instructions are merged together through inlining, the clauses from the
/// calling function are appended to the list of clauses. When the call stack
/// is being unwound due to an exception being thrown, the exception is
/// compared against each clause in turn. If it doesn’t match any of the
/// clauses, and the cleanup flag is not set, then unwinding continues further
/// up the call stack.
///
/// The landingpad instruction has several restrictions:
///
/// - A landing pad block is a basic block which is the unwind destination of
/// an `invoke` instruction.
/// - A landing pad block must have a `landingpad` instruction as its first
/// non-PHI instruction.
/// - There can be only one `landingpad` instruction within the landing pad
/// block.
/// - A basic block that is not a landing pad block may not include a
/// `landingpad` instruction.
///
/// - parameter type: The type of the resulting value from the landing pad.
/// - parameter personalityFn: The personality function.
/// - parameter clauses: A list of `catch` and `filter` clauses. This list
/// must either be non-empty or the landing pad must be marked as a cleanup
/// instruction.
/// - parameter cleanup: A flag indicating whether the landing pad is a
/// cleanup.
/// - parameter name: The name for the newly inserted instruction.
///
/// - returns: A value of the given type representing the result of matching
/// a clause during unwinding.
public func buildLandingPad(returning type: IRType, personalityFn: Function? = nil, clauses: [LandingPadClause], cleanup: Bool = false, name: String = "") -> IRValue {
precondition(cleanup || !clauses.isEmpty, "Landing pad must be created with clauses or as cleanup")

let lp : IRValue = LLVMBuildLandingPad(llvm, type.asLLVM(), personalityFn?.asLLVM(), UInt32(clauses.count), name)
for clause in clauses {
LLVMAddClause(lp.asLLVM(), clause.asLLVM())
}
LLVMSetCleanup(lp.asLLVM(), cleanup.llvm)
return lp
}

/// Build a resume instruction to resume propagation of an existing
/// (in-flight) exception whose unwinding was interrupted with a
/// `landingpad` instruction.
///
/// When all cleanups are finished, if an exception is not handled by the
/// current function, unwinding resumes by calling the resume instruction,
/// passing in the result of the `landingpad` instruction for the original
/// landing pad.
///
/// - parameter: A value representing the result of the original landing pad.
///
/// - returns: A value representing `void`.
@discardableResult
public func buildResume(_ val: IRValue) -> IRValue {
return LLVMBuildResume(llvm, val.asLLVM())
}

// MARK: Memory Access Instructions

/// Build an `alloca` to allocate stack memory to hold a value of the given
Expand Down
7 changes: 6 additions & 1 deletion Tests/LLVMTests/ConstantSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Foundation

class ConstantSpec : XCTestCase {
func testConstants() {

XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["SIGNEDCONST"]) {
// SIGNEDCONST: ; ModuleID = '[[ModuleName:ConstantTest]]'
// SIGNEDCONST-NEXT: source_filename = "[[ModuleName]]"
Expand Down Expand Up @@ -91,4 +90,10 @@ class ConstantSpec : XCTestCase {
module.dump()
})
}

#if !os(macOS)
static var allTests = testCase([
("testConstants", testConstants),
])
#endif
}
124 changes: 124 additions & 0 deletions Tests/LLVMTests/IRExceptionSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import LLVM
import XCTest
import Foundation

class IRExceptionSpec : XCTestCase {
private let exceptType = StructType(elementTypes: [
PointerType(pointee: IntType.int8),
IntType.int32,
])

func testExceptions() {
XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["IRRESUME"]) {
// IRRESUME: ; ModuleID = '[[ModuleName:IRBuilderTest]]'
// IRRESUME-NEXT: source_filename = "[[ModuleName]]"
let module = Module(name: "IRBuilderTest")
let builder = IRBuilder(module: module)
// IRRESUME: define void @main() {
let main = builder.addFunction("main",
type: FunctionType(argTypes: [],
returnType: VoidType()))
// IRRESUME-NEXT: entry:
let entry = main.appendBasicBlock(named: "entry")
builder.positionAtEnd(of: entry)

// IRRESUME-NEXT: resume i32 5
builder.buildResume(IntType.int32.constant(5))

// IRRESUME-NEXT: ret void
builder.buildRetVoid()
// IRRESUME-NEXT: }
module.dump()
})

XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["IRCLEANUP"]) {
// IRCLEANUP: ; ModuleID = '[[ModuleName:IRBuilderTest]]'
// IRCLEANUP-NEXT: source_filename = "[[ModuleName]]"
let module = Module(name: "IRBuilderTest")
let builder = IRBuilder(module: module)
// IRCLEANUP: define void @main() {
let main = builder.addFunction("main",
type: FunctionType(argTypes: [],
returnType: VoidType()))
// IRCLEANUP-NEXT: entry:
let entry = main.appendBasicBlock(named: "entry")
builder.positionAtEnd(of: entry)

// IRCLEANUP-NEXT: %0 = landingpad { i8*, i32 }
// IRCLEANUP-NEXT: cleanup
_ = builder.buildLandingPad(returning: exceptType, clauses: [], cleanup: true)

// IRCLEANUP-NEXT: ret void
builder.buildRetVoid()
// IRCLEANUP-NEXT: }
module.dump()
})

XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["IRCATCH"]) {
// IRCATCH: ; ModuleID = '[[ModuleName:IRBuilderTest]]'
// IRCATCH-NEXT: source_filename = "[[ModuleName]]"
let module = Module(name: "IRBuilderTest")
let builder = IRBuilder(module: module)

let except1 = builder.addGlobal("except1", type: PointerType(pointee: IntType.int8))
let except2 = builder.addGlobal("except2", type: PointerType(pointee: IntType.int8))

// IRCATCH: define void @main() {
let main = builder.addFunction("main",
type: FunctionType(argTypes: [],
returnType: VoidType()))
// IRCATCH-NEXT: entry:
let entry = main.appendBasicBlock(named: "entry")
builder.positionAtEnd(of: entry)

// IRCATCH-NEXT: %0 = landingpad { i8*, i32 }
// IRCATCH-NEXT: catch i8** @except1
// IRCATCH-NEXT: catch i8** @except2
_ = builder.buildLandingPad(returning: exceptType, clauses: [ .`catch`(except1), .`catch`(except2) ], cleanup: false)

// IRCATCH-NEXT: ret void
builder.buildRetVoid()
// IRCATCH-NEXT: }
module.dump()
})

XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["IRCATCHFILTER"]) {
// IRCATCHFILTER: ; ModuleID = '[[ModuleName:IRBuilderTest]]'
// IRCATCHFILTER-NEXT: source_filename = "[[ModuleName]]"
let module = Module(name: "IRBuilderTest")
let builder = IRBuilder(module: module)

let except1 = builder.addGlobal("except1", type: PointerType(pointee: IntType.int8))
let except2 = builder.addGlobal("except2", type: PointerType(pointee: IntType.int8))
let except3 = builder.addGlobal("except3", type: PointerType(pointee: IntType.int8))

// IRCATCHFILTER: define void @main() {
let main = builder.addFunction("main",
type: FunctionType(argTypes: [],
returnType: VoidType()))
// IRCATCHFILTER-NEXT: entry:
let entry = main.appendBasicBlock(named: "entry")
builder.positionAtEnd(of: entry)

// IRCATCHFILTER-NEXT: %0 = landingpad { i8*, i32 }
// IRCATCHFILTER-NEXT: catch i8** @except1
// IRCATCHFILTER-NEXT: filter [2 x i8**] [i8** @except2, i8** @except3]
_ = builder.buildLandingPad(
returning: exceptType,
clauses: [ .`catch`(except1), .filter(except1.type, [ except2, except3 ]) ],
cleanup: false
)

// IRCATCHFILTER-NEXT: ret void
builder.buildRetVoid()
// IRCATCHFILTER-NEXT: }
module.dump()
})
}

#if !os(macOS)
static var allTests = testCase([
("testExceptions", testExceptions),
])
#endif
}
2 changes: 2 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import XCTest
#if !os(macOS)
XCTMain([
IRBuilderSpec.allTests,
ConstantSpec.allTests,
IRExceptionSpec.allTests,
])
#endif