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  }