github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/compiler/intrinsics.go (about) 1 package compiler 2 3 // This file contains helper functions to create calls to LLVM intrinsics. 4 5 import ( 6 "go/token" 7 "strconv" 8 "strings" 9 10 "tinygo.org/x/go-llvm" 11 ) 12 13 // Define unimplemented intrinsic functions. 14 // 15 // Some functions are either normally implemented in Go assembly (like 16 // sync/atomic functions) or intentionally left undefined to be implemented 17 // directly in the compiler (like runtime/volatile functions). Either way, look 18 // for these and implement them if this is the case. 19 func (b *builder) defineIntrinsicFunction() { 20 name := b.fn.RelString(nil) 21 switch { 22 case name == "runtime.memcpy" || name == "runtime.memmove": 23 b.createMemoryCopyImpl() 24 case name == "runtime.memzero": 25 b.createMemoryZeroImpl() 26 case name == "runtime.KeepAlive": 27 b.createKeepAliveImpl() 28 case strings.HasPrefix(name, "runtime/volatile.Load"): 29 b.createVolatileLoad() 30 case strings.HasPrefix(name, "runtime/volatile.Store"): 31 b.createVolatileStore() 32 case strings.HasPrefix(name, "sync/atomic.") && token.IsExported(b.fn.Name()): 33 b.createFunctionStart(true) 34 returnValue := b.createAtomicOp(b.fn.Name()) 35 if !returnValue.IsNil() { 36 b.CreateRet(returnValue) 37 } else { 38 b.CreateRetVoid() 39 } 40 } 41 } 42 43 // createMemoryCopyImpl creates a call to a builtin LLVM memcpy or memmove 44 // function, declaring this function if needed. These calls are treated 45 // specially by optimization passes possibly resulting in better generated code, 46 // and will otherwise be lowered to regular libc memcpy/memmove calls. 47 func (b *builder) createMemoryCopyImpl() { 48 b.createFunctionStart(true) 49 fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) 50 llvmFn := b.mod.NamedFunction(fnName) 51 if llvmFn.IsNil() { 52 fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false) 53 llvmFn = llvm.AddFunction(b.mod, fnName, fnType) 54 } 55 var params []llvm.Value 56 for _, param := range b.fn.Params { 57 params = append(params, b.getValue(param, getPos(b.fn))) 58 } 59 params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false)) 60 b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "") 61 b.CreateRetVoid() 62 } 63 64 // createMemoryZeroImpl creates calls to llvm.memset.* to zero a block of 65 // memory, declaring the function if needed. These calls will be lowered to 66 // regular libc memset calls if they aren't optimized out in a different way. 67 func (b *builder) createMemoryZeroImpl() { 68 b.createFunctionStart(true) 69 llvmFn := b.getMemsetFunc() 70 params := []llvm.Value{ 71 b.getValue(b.fn.Params[0], getPos(b.fn)), 72 llvm.ConstInt(b.ctx.Int8Type(), 0, false), 73 b.getValue(b.fn.Params[1], getPos(b.fn)), 74 llvm.ConstInt(b.ctx.Int1Type(), 0, false), 75 } 76 b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "") 77 b.CreateRetVoid() 78 } 79 80 // Return the llvm.memset.p0.i8 function declaration. 81 func (c *compilerContext) getMemsetFunc() llvm.Value { 82 fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) 83 llvmFn := c.mod.NamedFunction(fnName) 84 if llvmFn.IsNil() { 85 fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) 86 llvmFn = llvm.AddFunction(c.mod, fnName, fnType) 87 } 88 return llvmFn 89 } 90 91 // createKeepAlive creates the runtime.KeepAlive function. It is implemented 92 // using inline assembly. 93 func (b *builder) createKeepAliveImpl() { 94 b.createFunctionStart(true) 95 96 // Get the underlying value of the interface value. 97 interfaceValue := b.getValue(b.fn.Params[0], getPos(b.fn)) 98 pointerValue := b.CreateExtractValue(interfaceValue, 1, "") 99 100 // Create an equivalent of the following C code, which is basically just a 101 // nop but ensures the pointerValue is kept alive: 102 // 103 // __asm__ __volatile__("" : : "r"(pointerValue)) 104 // 105 // It should be portable to basically everything as the "r" register type 106 // exists basically everywhere. 107 asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false) 108 asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false) 109 b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "") 110 111 b.CreateRetVoid() 112 } 113 114 var mathToLLVMMapping = map[string]string{ 115 "math.Ceil": "llvm.ceil.f64", 116 "math.Exp": "llvm.exp.f64", 117 "math.Exp2": "llvm.exp2.f64", 118 "math.Floor": "llvm.floor.f64", 119 "math.Log": "llvm.log.f64", 120 "math.Sqrt": "llvm.sqrt.f64", 121 "math.Trunc": "llvm.trunc.f64", 122 } 123 124 // defineMathOp defines a math function body as a call to a LLVM intrinsic, 125 // instead of the regular Go implementation. This allows LLVM to reason about 126 // the math operation and (depending on the architecture) allows it to lower the 127 // operation to very fast floating point instructions. If this is not possible, 128 // LLVM will emit a call to a libm function that implements the same operation. 129 // 130 // One example of an optimization that LLVM can do is to convert 131 // float32(math.Sqrt(float64(v))) to a 32-bit floating point operation, which is 132 // beneficial on architectures where 64-bit floating point operations are (much) 133 // more expensive than 32-bit ones. 134 func (b *builder) defineMathOp() { 135 b.createFunctionStart(true) 136 llvmName := mathToLLVMMapping[b.fn.RelString(nil)] 137 if llvmName == "" { 138 panic("unreachable: unknown math operation") // sanity check 139 } 140 llvmFn := b.mod.NamedFunction(llvmName) 141 if llvmFn.IsNil() { 142 // The intrinsic doesn't exist yet, so declare it. 143 // At the moment, all supported intrinsics have the form "double 144 // foo(double %x)" so we can hardcode the signature here. 145 llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false) 146 llvmFn = llvm.AddFunction(b.mod, llvmName, llvmType) 147 } 148 // Create a call to the intrinsic. 149 args := make([]llvm.Value, len(b.fn.Params)) 150 for i, param := range b.fn.Params { 151 args[i] = b.getValue(param, getPos(b.fn)) 152 } 153 result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "") 154 b.CreateRet(result) 155 } 156 157 // Implement most math/bits functions. 158 // 159 // This implements all the functions that operate on bits. It does not yet 160 // implement the arithmetic functions (like bits.Add), which also have LLVM 161 // intrinsics. 162 func (b *builder) defineMathBitsIntrinsic() bool { 163 if b.fn.Pkg.Pkg.Path() != "math/bits" { 164 return false 165 } 166 name := b.fn.Name() 167 switch name { 168 case "LeadingZeros", "LeadingZeros8", "LeadingZeros16", "LeadingZeros32", "LeadingZeros64", 169 "TrailingZeros", "TrailingZeros8", "TrailingZeros16", "TrailingZeros32", "TrailingZeros64": 170 b.createFunctionStart(true) 171 param := b.getValue(b.fn.Params[0], b.fn.Pos()) 172 valueType := param.Type() 173 var intrinsicName string 174 if strings.HasPrefix(name, "Leading") { // LeadingZeros 175 intrinsicName = "llvm.ctlz.i" + strconv.Itoa(valueType.IntTypeWidth()) 176 } else { // TrailingZeros 177 intrinsicName = "llvm.cttz.i" + strconv.Itoa(valueType.IntTypeWidth()) 178 } 179 llvmFn := b.mod.NamedFunction(intrinsicName) 180 llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, b.ctx.Int1Type()}, false) 181 if llvmFn.IsNil() { 182 llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) 183 } 184 result := b.createCall(llvmFnType, llvmFn, []llvm.Value{ 185 param, 186 llvm.ConstInt(b.ctx.Int1Type(), 0, false), 187 }, "") 188 result = b.createZExtOrTrunc(result, b.intType) 189 b.CreateRet(result) 190 return true 191 case "Len", "Len8", "Len16", "Len32", "Len64": 192 // bits.Len can be implemented as: 193 // (unsafe.Sizeof(v) * 8) - bits.LeadingZeros(n) 194 // Not sure why this isn't already done in the standard library, as it 195 // is much simpler than a lookup table. 196 b.createFunctionStart(true) 197 param := b.getValue(b.fn.Params[0], b.fn.Pos()) 198 valueType := param.Type() 199 valueBits := valueType.IntTypeWidth() 200 intrinsicName := "llvm.ctlz.i" + strconv.Itoa(valueBits) 201 llvmFn := b.mod.NamedFunction(intrinsicName) 202 llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, b.ctx.Int1Type()}, false) 203 if llvmFn.IsNil() { 204 llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) 205 } 206 result := b.createCall(llvmFnType, llvmFn, []llvm.Value{ 207 param, 208 llvm.ConstInt(b.ctx.Int1Type(), 0, false), 209 }, "") 210 result = b.createZExtOrTrunc(result, b.intType) 211 maxLen := llvm.ConstInt(b.intType, uint64(valueBits), false) // number of bits in the value 212 result = b.CreateSub(maxLen, result, "") 213 b.CreateRet(result) 214 return true 215 case "OnesCount", "OnesCount8", "OnesCount16", "OnesCount32", "OnesCount64": 216 b.createFunctionStart(true) 217 param := b.getValue(b.fn.Params[0], b.fn.Pos()) 218 valueType := param.Type() 219 intrinsicName := "llvm.ctpop.i" + strconv.Itoa(valueType.IntTypeWidth()) 220 llvmFn := b.mod.NamedFunction(intrinsicName) 221 llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType}, false) 222 if llvmFn.IsNil() { 223 llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) 224 } 225 result := b.createCall(llvmFnType, llvmFn, []llvm.Value{param}, "") 226 result = b.createZExtOrTrunc(result, b.intType) 227 b.CreateRet(result) 228 return true 229 case "Reverse", "Reverse8", "Reverse16", "Reverse32", "Reverse64", 230 "ReverseBytes", "ReverseBytes16", "ReverseBytes32", "ReverseBytes64": 231 b.createFunctionStart(true) 232 param := b.getValue(b.fn.Params[0], b.fn.Pos()) 233 valueType := param.Type() 234 var intrinsicName string 235 if strings.HasPrefix(name, "ReverseBytes") { 236 intrinsicName = "llvm.bswap.i" + strconv.Itoa(valueType.IntTypeWidth()) 237 } else { // Reverse 238 intrinsicName = "llvm.bitreverse.i" + strconv.Itoa(valueType.IntTypeWidth()) 239 } 240 llvmFn := b.mod.NamedFunction(intrinsicName) 241 llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType}, false) 242 if llvmFn.IsNil() { 243 llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) 244 } 245 result := b.createCall(llvmFnType, llvmFn, []llvm.Value{param}, "") 246 b.CreateRet(result) 247 return true 248 case "RotateLeft", "RotateLeft8", "RotateLeft16", "RotateLeft32", "RotateLeft64": 249 // Warning: the documentation says these functions must be constant time. 250 // I do not think LLVM guarantees this, but there's a good chance LLVM 251 // already recognized the rotate instruction so it probably won't get 252 // any _worse_ by implementing these rotate functions. 253 b.createFunctionStart(true) 254 x := b.getValue(b.fn.Params[0], b.fn.Pos()) 255 k := b.getValue(b.fn.Params[1], b.fn.Pos()) 256 valueType := x.Type() 257 intrinsicName := "llvm.fshl.i" + strconv.Itoa(valueType.IntTypeWidth()) 258 llvmFn := b.mod.NamedFunction(intrinsicName) 259 llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, valueType, valueType}, false) 260 if llvmFn.IsNil() { 261 llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) 262 } 263 k = b.createZExtOrTrunc(k, valueType) 264 result := b.createCall(llvmFnType, llvmFn, []llvm.Value{x, x, k}, "") 265 b.CreateRet(result) 266 return true 267 default: 268 return false 269 } 270 }