Skip to content

Commit a7c4da1

Browse files
committed
Exception API wrappers
1 parent fdae6cd commit a7c4da1

File tree

5 files changed

+269
-3
lines changed

5 files changed

+269
-3
lines changed

Sources/LLVM/Clause.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#if !NO_SWIFTPM
2+
import cllvm
3+
#endif
4+
5+
/// Enumerates the supported kind of clauses.
6+
public enum LandingPadClause: IRValue {
7+
/// This clause means that the landingpad block should be entered if the
8+
/// exception being thrown matches or is a subtype of its type.
9+
///
10+
/// For C++, the type is a pointer to the `std::type_info` object
11+
/// (an RTTI object) representing the C++ exception type.
12+
///
13+
/// If the type is `null`, any exception matches, so the landing pad should
14+
/// always be entered. This is used for C++ catch-all blocks (`catch (...)`).
15+
///
16+
/// When this clause is matched, the selector value will be equal to the value
17+
/// returned by `@llvm.eh.typeid.for(i8* @ExcType)`. This will always be a
18+
/// positive value.
19+
case `catch`(IRGlobal)
20+
/// This clause means that the landing pad should be entered if the exception
21+
/// being thrown does not match any of the types in the list (which, for C++,
22+
/// are again specified as `std::type_info pointers`).
23+
///
24+
/// C++ front-ends use this to implement C++ exception specifications, such as
25+
/// `void foo() throw (ExcType1, ..., ExcTypeN) { ... }`.
26+
///
27+
/// When this clause is matched, the selector value will be negative.
28+
///
29+
/// The array argument to filter may be empty; for example, `[0 x i8**] undef`.
30+
/// This means that the landing pad should always be entered. (Note that such
31+
/// a filter would not be equivalent to `catch i8* null`, because filter and
32+
/// catch produce negative and positive selector values respectively.)
33+
case filter(IRType, [IRGlobal])
34+
35+
public func asLLVM() -> LLVMValueRef {
36+
switch self {
37+
case let .`catch`(val):
38+
return val.asLLVM()
39+
case let .filter(ty, arr):
40+
return ArrayType.constant(arr, type: ty).asLLVM()
41+
}
42+
}
43+
}

Sources/LLVM/IRBuilder.swift

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -935,15 +935,107 @@ public class IRBuilder {
935935
/// - parameter args: A list of arguments.
936936
/// - parameter name: The name for the newly inserted instruction.
937937
///
938-
/// - returns: A value representing `void`.
939-
@discardableResult
938+
/// - returns: A value representing the result of returning from the callee.
940939
public func buildCall(_ fn: IRValue, args: [IRValue], name: String = "") -> IRValue {
941940
var args = args.map { $0.asLLVM() as Optional }
942941
return args.withUnsafeMutableBufferPointer { buf in
943942
return LLVMBuildCall(llvm, fn.asLLVM(), buf.baseAddress!, UInt32(buf.count), name)
944943
}
945944
}
946945

946+
// MARK: Exception Handling Instructions
947+
948+
/// Build a call to the given function with the given arguments with the
949+
/// possibility of control transfering to either the `next` basic block or
950+
/// the `catch` basic block if an exception occurs.
951+
///
952+
/// If the callee function returns with the `ret` instruction, control flow
953+
/// will return to the `next` label. If the callee (or any indirect callees)
954+
/// returns via the `resume` instruction or other exception handling
955+
/// mechanism, control is interrupted and continued at the dynamically nearest
956+
/// `exception` label.
957+
///
958+
///
959+
/// - parameter fn: The function to invoke.
960+
/// - parameter args: A list of arguments.
961+
/// - parameter next: The destination block if the invoke succeeds without exceptions.
962+
/// - parameter catch: The destination block if the invoke encounters an exception.
963+
/// - parameter name: The name for the newly inserted instruction.
964+
///
965+
/// - returns: A value representing the result of returning from the callee
966+
/// under normal circumstances. Under exceptional circumstances, the value
967+
/// represents the value of any `resume` instruction in the `catch` block.
968+
public func buildInvoke(_ fn: IRValue, args: [IRValue], next: BasicBlock, catch: BasicBlock, name: String = "") -> IRValue {
969+
precondition(`catch`.firstInstruction!.opCode == .landingPad, "First instruction of catch block must be a landing pad")
970+
971+
var args = args.map { $0.asLLVM() as Optional }
972+
return args.withUnsafeMutableBufferPointer { buf in
973+
return LLVMBuildInvoke(llvm, fn.asLLVM(), buf.baseAddress!, UInt32(buf.count), next.llvm, `catch`.llvm, name)
974+
}
975+
}
976+
977+
/// Build a landing pad to specify that a basic block is where an exception
978+
/// lands, and corresponds to the code found in the `catch` portion of a
979+
/// `try/catch` sequence.
980+
///
981+
/// The clauses are applied in order from top to bottom. If two landing pad
982+
/// instructions are merged together through inlining, the clauses from the
983+
/// calling function are appended to the list of clauses. When the call stack
984+
/// is being unwound due to an exception being thrown, the exception is
985+
/// compared against each clause in turn. If it doesn’t match any of the
986+
/// clauses, and the cleanup flag is not set, then unwinding continues further
987+
/// up the call stack.
988+
///
989+
/// The landingpad instruction has several restrictions:
990+
///
991+
/// - A landing pad block is a basic block which is the unwind destination of
992+
/// an `invoke` instruction.
993+
/// - A landing pad block must have a `landingpad` instruction as its first
994+
/// non-PHI instruction.
995+
/// - There can be only one `landingpad` instruction within the landing pad
996+
/// block.
997+
/// - A basic block that is not a landing pad block may not include a
998+
/// `landingpad` instruction.
999+
///
1000+
/// - parameter type: The type of the resulting value from the landing pad.
1001+
/// - parameter personalityFn: The personality function.
1002+
/// - parameter clauses: A list of `catch` and `filter` clauses. This list
1003+
/// must either be non-empty or the landing pad must be marked as a cleanup
1004+
/// instruction.
1005+
/// - parameter cleanup: A flag indicating whether the landing pad is a
1006+
/// cleanup.
1007+
/// - parameter name: The name for the newly inserted instruction.
1008+
///
1009+
/// - returns: A value of the given type representing the result of matching
1010+
/// a clause during unwinding.
1011+
public func buildLandingPad(returning type: IRType, personalityFn: Function? = nil, clauses: [LandingPadClause], cleanup: Bool = false, name: String = "") -> IRValue {
1012+
precondition(cleanup || !clauses.isEmpty, "Landing pad must be created with clauses or as cleanup")
1013+
1014+
let lp : IRValue = LLVMBuildLandingPad(llvm, type.asLLVM(), personalityFn?.asLLVM(), UInt32(clauses.count), name)
1015+
for clause in clauses {
1016+
LLVMAddClause(lp.asLLVM(), clause.asLLVM())
1017+
}
1018+
LLVMSetCleanup(lp.asLLVM(), cleanup.llvm)
1019+
return lp
1020+
}
1021+
1022+
/// Build a resume instruction to resume propagation of an existing
1023+
/// (in-flight) exception whose unwinding was interrupted with a
1024+
/// `landingpad` instruction.
1025+
///
1026+
/// When all cleanups are finished, if an exception is not handled by the
1027+
/// current function, unwinding resumes by calling the resume instruction,
1028+
/// passing in the result of the `landingpad` instruction for the original
1029+
/// landing pad.
1030+
///
1031+
/// - parameter: A value representing the result of the original landing pad.
1032+
///
1033+
/// - returns: A value representing `void`.
1034+
@discardableResult
1035+
public func buildResume(_ val: IRValue) -> IRValue {
1036+
return LLVMBuildResume(llvm, val.asLLVM())
1037+
}
1038+
9471039
// MARK: Memory Access Instructions
9481040

9491041
/// Build an `alloca` to allocate stack memory to hold a value of the given

Tests/LLVMTests/ConstantSpec.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Foundation
44

55
class ConstantSpec : XCTestCase {
66
func testConstants() {
7-
87
XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["SIGNEDCONST"]) {
98
// SIGNEDCONST: ; ModuleID = '[[ModuleName:ConstantTest]]'
109
// SIGNEDCONST-NEXT: source_filename = "[[ModuleName]]"
@@ -91,4 +90,10 @@ class ConstantSpec : XCTestCase {
9190
module.dump()
9291
})
9392
}
93+
94+
#if !os(macOS)
95+
static var allTests = testCase([
96+
("testConstants", testConstants),
97+
])
98+
#endif
9499
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import LLVM
2+
import XCTest
3+
import Foundation
4+
5+
class IRExceptionSpec : XCTestCase {
6+
private let exceptType = StructType(elementTypes: [
7+
PointerType(pointee: IntType.int8),
8+
IntType.int32,
9+
])
10+
11+
func testExceptions() {
12+
XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["IRRESUME"]) {
13+
// IRRESUME: ; ModuleID = '[[ModuleName:IRBuilderTest]]'
14+
// IRRESUME-NEXT: source_filename = "[[ModuleName]]"
15+
let module = Module(name: "IRBuilderTest")
16+
let builder = IRBuilder(module: module)
17+
// IRRESUME: define void @main() {
18+
let main = builder.addFunction("main",
19+
type: FunctionType(argTypes: [],
20+
returnType: VoidType()))
21+
// IRRESUME-NEXT: entry:
22+
let entry = main.appendBasicBlock(named: "entry")
23+
builder.positionAtEnd(of: entry)
24+
25+
// IRRESUME-NEXT: resume i32 5
26+
builder.buildResume(IntType.int32.constant(5))
27+
28+
// IRRESUME-NEXT: ret void
29+
builder.buildRetVoid()
30+
// IRRESUME-NEXT: }
31+
module.dump()
32+
})
33+
34+
XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["IRCLEANUP"]) {
35+
// IRCLEANUP: ; ModuleID = '[[ModuleName:IRBuilderTest]]'
36+
// IRCLEANUP-NEXT: source_filename = "[[ModuleName]]"
37+
let module = Module(name: "IRBuilderTest")
38+
let builder = IRBuilder(module: module)
39+
// IRCLEANUP: define void @main() {
40+
let main = builder.addFunction("main",
41+
type: FunctionType(argTypes: [],
42+
returnType: VoidType()))
43+
// IRCLEANUP-NEXT: entry:
44+
let entry = main.appendBasicBlock(named: "entry")
45+
builder.positionAtEnd(of: entry)
46+
47+
// IRCLEANUP-NEXT: %0 = landingpad { i8*, i32 }
48+
// IRCLEANUP-NEXT: cleanup
49+
_ = builder.buildLandingPad(returning: exceptType, clauses: [], cleanup: true)
50+
51+
// IRCLEANUP-NEXT: ret void
52+
builder.buildRetVoid()
53+
// IRCLEANUP-NEXT: }
54+
module.dump()
55+
})
56+
57+
XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["IRCATCH"]) {
58+
// IRCATCH: ; ModuleID = '[[ModuleName:IRBuilderTest]]'
59+
// IRCATCH-NEXT: source_filename = "[[ModuleName]]"
60+
let module = Module(name: "IRBuilderTest")
61+
let builder = IRBuilder(module: module)
62+
63+
let except1 = builder.addGlobal("except1", type: PointerType(pointee: IntType.int8))
64+
let except2 = builder.addGlobal("except2", type: PointerType(pointee: IntType.int8))
65+
66+
// IRCATCH: define void @main() {
67+
let main = builder.addFunction("main",
68+
type: FunctionType(argTypes: [],
69+
returnType: VoidType()))
70+
// IRCATCH-NEXT: entry:
71+
let entry = main.appendBasicBlock(named: "entry")
72+
builder.positionAtEnd(of: entry)
73+
74+
// IRCATCH-NEXT: %0 = landingpad { i8*, i32 }
75+
// IRCATCH-NEXT: catch i8** @except1
76+
// IRCATCH-NEXT: catch i8** @except2
77+
_ = builder.buildLandingPad(returning: exceptType, clauses: [ .`catch`(except1), .`catch`(except2) ], cleanup: false)
78+
79+
// IRCATCH-NEXT: ret void
80+
builder.buildRetVoid()
81+
// IRCATCH-NEXT: }
82+
module.dump()
83+
})
84+
85+
XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["IRCATCHFILTER"]) {
86+
// IRCATCHFILTER: ; ModuleID = '[[ModuleName:IRBuilderTest]]'
87+
// IRCATCHFILTER-NEXT: source_filename = "[[ModuleName]]"
88+
let module = Module(name: "IRBuilderTest")
89+
let builder = IRBuilder(module: module)
90+
91+
let except1 = builder.addGlobal("except1", type: PointerType(pointee: IntType.int8))
92+
let except2 = builder.addGlobal("except2", type: PointerType(pointee: IntType.int8))
93+
let except3 = builder.addGlobal("except3", type: PointerType(pointee: IntType.int8))
94+
95+
// IRCATCHFILTER: define void @main() {
96+
let main = builder.addFunction("main",
97+
type: FunctionType(argTypes: [],
98+
returnType: VoidType()))
99+
// IRCATCHFILTER-NEXT: entry:
100+
let entry = main.appendBasicBlock(named: "entry")
101+
builder.positionAtEnd(of: entry)
102+
103+
// IRCATCHFILTER-NEXT: %0 = landingpad { i8*, i32 }
104+
// IRCATCHFILTER-NEXT: catch i8** @except1
105+
// IRCATCHFILTER-NEXT: filter [2 x i8**] [i8** @except2, i8** @except3]
106+
_ = builder.buildLandingPad(
107+
returning: exceptType,
108+
clauses: [ .`catch`(except1), .filter(except1.type, [ except2, except3 ]) ],
109+
cleanup: false
110+
)
111+
112+
// IRCATCHFILTER-NEXT: ret void
113+
builder.buildRetVoid()
114+
// IRCATCHFILTER-NEXT: }
115+
module.dump()
116+
})
117+
}
118+
119+
#if !os(macOS)
120+
static var allTests = testCase([
121+
("testExceptions", testExceptions),
122+
])
123+
#endif
124+
}

Tests/LinuxMain.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ import XCTest
55
#if !os(macOS)
66
XCTMain([
77
IRBuilderSpec.allTests,
8+
ConstantSpec.allTests,
9+
IRExceptionSpec.allTests,
810
])
911
#endif

0 commit comments

Comments
 (0)