Skip to content

Commit 431a5e6

Browse files
committed
Wrap LLVM Intrinsics
The LLVM Intrinsics API is large, complex, platform-dependent, and in a constant state of flux. LLVM has chosen to manage this complexity with a set of TableGen definition files. Therefore, we must parse and interpret these files to provide a robust API. This commit introduces a new tool, 'intrinsics-gen', to be invoked from the top-level of the repository. The tool will read in any number of platform-dependent TableGen files and interpret them as a Swift file of enums with selectors. Each of these selectors either represents an intrinsic or a family of intrinsics that the framework is capable of resolving to one of LLVM's "proper intrinsic" selectors. The idea is that we do not ever ship this generated Swift file. Instead, users that wish to use this framework with the intrinsics will invoke 'intrinsics-gen' with the appropriate td file and SwiftPM will handle the rest. The API that will be committed is just the infrastructure necessary to support this. See the test suite for a concrete example of how to use this.
1 parent ed611f2 commit 431a5e6

File tree

8 files changed

+2493
-0
lines changed

8 files changed

+2493
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ Package.resolved
55
Package.pins
66
/*.xcodeproj
77
/build
8+
/Sources/LLVMIntrinsics/IntrinsicsDef.swift

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ matrix:
1111
- brew update
1212
- brew install llvm
1313
- sudo swift utils/make-pkgconfig.swift
14+
- sudo swift utils/intrinsics-gen.swift Tests/Resources/Intrinsics.td
1415
script:
1516
- swift test -Xlinker -w
1617
- os: linux
@@ -37,6 +38,7 @@ matrix:
3738
- tar xzf swift-4.2-RELEASE-ubuntu14.04.tar.gz
3839
- export PATH=${PWD}/swift-4.2-RELEASE-ubuntu14.04/usr/bin:"${PATH}"
3940
- sudo ./swift-4.2-RELEASE-ubuntu14.04/usr/bin/swift utils/make-pkgconfig.swift
41+
- sudo ./swift-4.2-RELEASE-ubuntu14.04/usr/bin/swift utils/intrinsics-gen.swift Tests/Resources/Intrinsics.td
4042
script:
4143
- swift test
4244
notifications:

Package.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ let package = Package(
88
.library(
99
name: "LLVM",
1010
targets: ["LLVM"]),
11+
.library(
12+
name: "LLVMIntrinsics",
13+
targets: ["LLVMIntrinsics"]),
1114
],
1215
dependencies: [
1316
.package(url: "https://github.com/llvm-swift/FileCheck.git", from: "0.0.3"),
@@ -25,5 +28,11 @@ let package = Package(
2528
.testTarget(
2629
name: "LLVMTests",
2730
dependencies: ["LLVM", "FileCheck"]),
31+
.target(
32+
name: "LLVMIntrinsics",
33+
dependencies: ["LLVM"]),
34+
.testTarget(
35+
name: "LLVMIntrinsicsTests",
36+
dependencies: ["LLVMIntrinsics", "LLVM", "FileCheck"]),
2837
]
2938
)

Sources/LLVM/Intrinsics.swift

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#if !NO_SWIFTPM
2+
import cllvm
3+
#endif
4+
5+
/// The `LLVMIntrinsic` protocol represents types that act as selectors for
6+
/// intrinsic functions.
7+
///
8+
/// LLVM supports the notion of an “intrinsic function”. These functions have
9+
/// well known names and semantics and are required to follow certain
10+
/// restrictions. Overall, these intrinsics represent an extension mechanism for
11+
/// the LLVM language that does not require changing all of the transformations
12+
/// in LLVM when adding to the language.
13+
///
14+
/// Intrinsic function names all start with an "llvm." prefix. This prefix is
15+
/// reserved in LLVM for intrinsic names; thus, function names may not begin
16+
/// with this prefix. Intrinsic functions must always be external functions:
17+
/// you cannot define the body of intrinsic functions. Intrinsic functions may
18+
/// only be used in call or invoke instructions: it is illegal to take the
19+
/// address of an intrinsic function.
20+
public protocol LLVMIntrinsic {
21+
var signature: FunctionType { get }
22+
var llvmSelector: String { get }
23+
}
24+
25+
/// The `LLVMOverloadedIntrinsic` protocol represents types that act as
26+
/// selectors for a family of overloaded intrinsic functions.
27+
///
28+
/// Some intrinsic functions can be overloaded, i.e., the intrinsic represents a
29+
/// family of functions that perform the same operation but on different data
30+
/// types. Because LLVM can represent over 8 million different integer types,
31+
/// overloading is used commonly to allow an intrinsic function to operate on
32+
/// any integer type. One or more of the argument types or the result type can
33+
/// be overloaded to accept any integer type. Argument types may also be defined
34+
/// as exactly matching a previous argument’s type or the result type. This
35+
/// allows an intrinsic function which accepts multiple arguments, but needs all
36+
/// of them to be of the same type, to only be overloaded with respect to a
37+
/// single argument or the result.
38+
///
39+
/// Overloaded intrinsics will have the names of its overloaded argument types
40+
/// encoded into its function name, each preceded by a period. Only those types
41+
/// which are overloaded result in a name suffix. Arguments whose type is
42+
/// matched against another type do not. For example, the llvm.ctpop function
43+
/// can take an integer of any width and returns an integer of exactly the same
44+
/// integer width. This leads to a family of functions such as
45+
/// `i8 @llvm.ctpop.i8(i8 %val)` and i29 `@llvm.ctpop.i29(i29 %val)`. Only one
46+
/// type, the return type, is overloaded, and only one type suffix is required.
47+
/// Because the argument’s type is matched against the return type, it does not
48+
/// require its own name suffix.
49+
public protocol LLVMOverloadedIntrinsic: LLVMIntrinsic {
50+
static var overloadSet: [LLVMIntrinsic] { get }
51+
}
52+
53+
extension IRBuilder {
54+
/// Builds a call to an intrinsic function.
55+
///
56+
/// - note: In debug builds this function type checks its arguments to ensure
57+
/// calls are well-formed. In release builds no such checking will
58+
/// occur and all calls to intrinsics with mismatched arguments will
59+
/// result in undefined behavior.
60+
///
61+
/// - Parameters:
62+
/// - intr: The selector for the intrinsic to be invoked.
63+
/// - returnType: A suggested return type for overloaded intrinsics. By
64+
/// default, the framework will infer a type and attempt to match the
65+
/// right intrinsic function.
66+
/// - args: The arguments to the intrinsic function.
67+
/// - Returns: A value representing the result of calling the intrinsic
68+
/// function.
69+
public func buildIntrinsicCall<I: LLVMIntrinsic>(to intr: I, returnType: IRType? = nil, args: IRValue...) -> IRValue {
70+
assert(typeCheckArguments(to: intr, args: args))
71+
72+
return self.buildCall(self.buildIntrinsic(self.resolveIntrinsic(intr, args, returnType)), args: args)
73+
}
74+
75+
76+
/// Builds a call to one of a family of overloaded intrinsic functions.
77+
///
78+
/// - note: The type of the arguments ultimately determines the selector for
79+
/// the intrinsic that is called. Failure of the arguments to
80+
/// correspond to any of the overloads is a fatal condition.
81+
///
82+
/// - Parameters:
83+
/// - intr: The selector for the overloaded intrinsic to be resolved and
84+
/// invoked.
85+
/// - returnType: A suggested return type for overloaded intrinsics. By
86+
/// default, the framework will infer a type and attempt to match the
87+
/// right intrinsic function.
88+
/// - args: The arguments to the intrinsic function.
89+
/// - Returns: A value representing the result of calling the intrinsic
90+
/// function.
91+
public func buildIntrinsicCall<I: LLVMOverloadedIntrinsic>(to intr: I.Type, returnType: IRType? = nil, args: IRValue...) -> IRValue {
92+
guard let intr = resolveOverloadedArguments(to: intr, args: args) else {
93+
fatalError("Unable to resolve overload among \(I.overloadSet.map{$0.llvmSelector})")
94+
}
95+
return self.buildCall(self.buildIntrinsic(self.resolveIntrinsic(intr, args, returnType)), args: args)
96+
}
97+
98+
private func resolveIntrinsic(_ i: LLVMIntrinsic, _ args: [IRValue], _ returnTy: IRType?) -> LLVMIntrinsic {
99+
let fnArgTypes = i.signature.argTypes
100+
101+
func typeNameForType(_ type: IRType) -> String {
102+
if let iTy = type as? IntType {
103+
return "i\(iTy.width)"
104+
} else if let fTy = type as? FloatType {
105+
switch fTy.kind {
106+
case .half:
107+
return "f16"
108+
case .float:
109+
return "f32"
110+
case .double:
111+
return "f64"
112+
case .x86FP80:
113+
return "f80"
114+
case .fp128:
115+
return "f128"
116+
case .ppcFP128:
117+
return "ppcf128"
118+
}
119+
} else if let pTy = type as? PointerType {
120+
return "p0" + typeNameForType(pTy.pointee)
121+
} else if let vTy = type as? VectorType {
122+
return "v\(vTy.count)" + typeNameForType(vTy.elementType)
123+
}
124+
fatalError()
125+
}
126+
127+
var argTypes = [IRType]()
128+
var sigName = i.llvmSelector
129+
for t in zip(fnArgTypes, args) {
130+
guard t.0 is IntrinsicSubstitutionMarker else {
131+
argTypes.append(t.0)
132+
continue
133+
}
134+
argTypes.append(t.1.type)
135+
sigName += "." + typeNameForType(t.1.type)
136+
}
137+
138+
let retTy: IRType
139+
if i.signature.returnType is IntrinsicSubstitutionMarker {
140+
// HACK: Sometimes the return type is generic. This is nice for people that
141+
// are writing textual IR and know their types ahead of time, but we have
142+
// no such luck. All of the intrinsics seems to be predicated on the idea
143+
// that the first substitution we perform matches the desired return type.
144+
guard let potentialRetTy = returnTy ?? argTypes.first else {
145+
fatalError("Unable to disambiguate return type for overloaded intrinsic \(i.llvmSelector); provide one explicitly.")
146+
}
147+
retTy = potentialRetTy
148+
} else {
149+
retTy = i.signature.returnType
150+
}
151+
return ResolvedIntrinsic(signature: FunctionType(argTypes: argTypes, returnType: retTy), llvmSelector: sigName)
152+
}
153+
154+
private func buildIntrinsic(_ i: LLVMIntrinsic) -> Function {
155+
if let f = self.module.function(named: i.llvmSelector) {
156+
return f
157+
}
158+
var f = self.addFunction(i.llvmSelector, type: i.signature)
159+
f.linkage = .external
160+
return f
161+
}
162+
163+
private func typeCheckArguments<I: LLVMIntrinsic>(to fn: I, args: [IRValue]) -> Bool {
164+
let fnArgTypes = fn.signature.argTypes
165+
guard fnArgTypes.count == args.count else { return false }
166+
for t in zip(fnArgTypes, args) {
167+
// <SUB> => T
168+
if t.0 is IntrinsicSubstitutionMarker {
169+
continue
170+
} else if let vecTy = t.0 as? VectorType, vecTy.elementType is IntrinsicSubstitutionMarker {
171+
// Vector<n, <SUB>> => Vector<n, T>
172+
guard t.1 is VectorType else {
173+
return false
174+
}
175+
continue
176+
} else if let vecTy = t.0 as? VectorType, vecTy.count == -1 {
177+
// Vector<-1, T> => Vector<n, T>
178+
guard let vecTy2 = t.1 as? VectorType, vecTy.elementType.asLLVM() == vecTy2.elementType.asLLVM() else {
179+
return false
180+
}
181+
continue
182+
}
183+
184+
// Fall back to pointer equality
185+
guard t.0.asLLVM() == t.1.type.asLLVM() else { return false }
186+
}
187+
return true
188+
}
189+
190+
private func resolveOverloadedArguments<I: LLVMOverloadedIntrinsic>(to fns: I.Type, args: [IRValue]) -> LLVMIntrinsic? {
191+
return fns.overloadSet.filter({ (fn) -> Bool in
192+
let fnArgTypes = fn.signature.argTypes
193+
guard fnArgTypes.count == args.count else { return false }
194+
for t in zip(fnArgTypes, args) {
195+
guard t.0.asLLVM() == t.1.type.asLLVM() else { return false }
196+
}
197+
return true
198+
}).first
199+
}
200+
}
201+
202+
// A marker type generated by the 'intrinsics-gen' tool to indicate that a
203+
// substitution should be performed on an argument. They cannot be reified into
204+
// an LLVM Type and will trap if an attempt is made to do so.
205+
public struct IntrinsicSubstitutionMarker: IRType {
206+
public init() {}
207+
public func asLLVM() -> LLVMTypeRef {
208+
fatalError("Unhandled substitution marker in type")
209+
}
210+
}
211+
212+
/// A marker intrinsic for the candidate selected during overload resolution.
213+
private struct ResolvedIntrinsic: LLVMIntrinsic {
214+
let signature: FunctionType
215+
let llvmSelector: String
216+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*! THIS FILE INTENTIONALLY LEFT BLANK !*/
2+
///
3+
/// LLVMSwift does not ship with an intrinsics API by default. To enable it,
4+
/// you must run
5+
///
6+
/// $ swift ./utils/intrinsics-gen.swift /Path/To/Intrinsics.td
7+
///
8+
/// To run the tests for LLVMSwiftIntrinsics, you should at least run
9+
///
10+
/// $ swift ./utils/intrinsics-gen.swift Tests/Resources/Intrinsics.td

0 commit comments

Comments
 (0)