|
| 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 | +} |
0 commit comments