github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/compiler/llvm.go (about)

     1  package compiler
     2  
     3  import (
     4  	"fmt"
     5  	"go/token"
     6  	"go/types"
     7  	"math/big"
     8  	"strings"
     9  
    10  	"github.com/tinygo-org/tinygo/compiler/llvmutil"
    11  	"tinygo.org/x/go-llvm"
    12  )
    13  
    14  // This file contains helper functions for LLVM that are not exposed in the Go
    15  // bindings.
    16  
    17  // createTemporaryAlloca creates a new alloca in the entry block and adds
    18  // lifetime start information in the IR signalling that the alloca won't be used
    19  // before this point.
    20  //
    21  // This is useful for creating temporary allocas for intrinsics. Don't forget to
    22  // end the lifetime using emitLifetimeEnd after you're done with it.
    23  func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, size llvm.Value) {
    24  	return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
    25  }
    26  
    27  // insertBasicBlock inserts a new basic block after the current basic block.
    28  // This is useful when inserting new basic blocks while converting a
    29  // *ssa.BasicBlock to a llvm.BasicBlock and the LLVM basic block needs some
    30  // extra blocks.
    31  // It does not update b.blockExits, this must be done by the caller.
    32  func (b *builder) insertBasicBlock(name string) llvm.BasicBlock {
    33  	currentBB := b.Builder.GetInsertBlock()
    34  	nextBB := llvm.NextBasicBlock(currentBB)
    35  	if nextBB.IsNil() {
    36  		// Last basic block in the function, so add one to the end.
    37  		return b.ctx.AddBasicBlock(b.llvmFn, name)
    38  	}
    39  	// Insert a basic block before the next basic block - that is, at the
    40  	// current insert location.
    41  	return b.ctx.InsertBasicBlock(nextBB, name)
    42  }
    43  
    44  // emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
    45  // llvm.lifetime.end intrinsic. It is commonly used together with
    46  // createTemporaryAlloca.
    47  func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
    48  	llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size)
    49  }
    50  
    51  // emitPointerPack packs the list of values into a single pointer value using
    52  // bitcasts, or else allocates a value on the heap if it cannot be packed in the
    53  // pointer value directly. It returns the pointer with the packed data.
    54  // If the values are all constants, they are be stored in a constant global and
    55  // deduplicated.
    56  func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
    57  	valueTypes := make([]llvm.Type, len(values))
    58  	for i, value := range values {
    59  		valueTypes[i] = value.Type()
    60  	}
    61  	packedType := b.ctx.StructType(valueTypes, false)
    62  
    63  	// Allocate memory for the packed data.
    64  	size := b.targetData.TypeAllocSize(packedType)
    65  	if size == 0 {
    66  		return llvm.ConstPointerNull(b.dataPtrType)
    67  	} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
    68  		return values[0]
    69  	} else if size <= b.targetData.TypeAllocSize(b.dataPtrType) {
    70  		// Packed data fits in a pointer, so store it directly inside the
    71  		// pointer.
    72  		if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
    73  			// Try to keep this cast in SSA form.
    74  			return b.CreateIntToPtr(values[0], b.dataPtrType, "pack.int")
    75  		}
    76  
    77  		// Because packedType is a struct and we have to cast it to a *i8, store
    78  		// it in a *i8 alloca first and load the *i8 value from there. This is
    79  		// effectively a bitcast.
    80  		packedAlloc, _ := b.createTemporaryAlloca(b.dataPtrType, "")
    81  
    82  		if size < b.targetData.TypeAllocSize(b.dataPtrType) {
    83  			// The alloca is bigger than the value that will be stored in it.
    84  			// To avoid having some bits undefined, zero the alloca first.
    85  			// Hopefully this will get optimized away.
    86  			b.CreateStore(llvm.ConstNull(b.dataPtrType), packedAlloc)
    87  		}
    88  
    89  		// Store all values in the alloca.
    90  		for i, value := range values {
    91  			indices := []llvm.Value{
    92  				llvm.ConstInt(b.ctx.Int32Type(), 0, false),
    93  				llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
    94  			}
    95  			gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
    96  			b.CreateStore(value, gep)
    97  		}
    98  
    99  		// Load value (the *i8) from the alloca.
   100  		result := b.CreateLoad(b.dataPtrType, packedAlloc, "")
   101  
   102  		// End the lifetime of the alloca, to help the optimizer.
   103  		packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false)
   104  		b.emitLifetimeEnd(packedAlloc, packedSize)
   105  
   106  		return result
   107  	} else {
   108  		// Check if the values are all constants.
   109  		constant := true
   110  		for _, v := range values {
   111  			if !v.IsConstant() {
   112  				constant = false
   113  				break
   114  			}
   115  		}
   116  
   117  		if constant {
   118  			// The data is known at compile time, so store it in a constant global.
   119  			// The global address is marked as unnamed, which allows LLVM to merge duplicates.
   120  			global := llvm.AddGlobal(b.mod, packedType, b.pkg.Path()+"$pack")
   121  			global.SetInitializer(b.ctx.ConstStruct(values, false))
   122  			global.SetGlobalConstant(true)
   123  			global.SetUnnamedAddr(true)
   124  			global.SetLinkage(llvm.InternalLinkage)
   125  			return global
   126  		}
   127  
   128  		// Packed data is bigger than a pointer, so allocate it on the heap.
   129  		sizeValue := llvm.ConstInt(b.uintptrType, size, false)
   130  		alloc := b.mod.NamedFunction("runtime.alloc")
   131  		packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{
   132  			sizeValue,
   133  			llvm.ConstNull(b.dataPtrType),
   134  			llvm.Undef(b.dataPtrType), // unused context parameter
   135  		}, "")
   136  		if b.NeedsStackObjects {
   137  			b.trackPointer(packedAlloc)
   138  		}
   139  
   140  		// Store all values in the heap pointer.
   141  		for i, value := range values {
   142  			indices := []llvm.Value{
   143  				llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   144  				llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
   145  			}
   146  			gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
   147  			b.CreateStore(value, gep)
   148  		}
   149  
   150  		// Return the original heap allocation pointer, which already is an *i8.
   151  		return packedAlloc
   152  	}
   153  }
   154  
   155  // emitPointerUnpack extracts a list of values packed using emitPointerPack.
   156  func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
   157  	packedType := b.ctx.StructType(valueTypes, false)
   158  
   159  	// Get a correctly-typed pointer to the packed data.
   160  	var packedAlloc llvm.Value
   161  	needsLifetimeEnd := false
   162  	size := b.targetData.TypeAllocSize(packedType)
   163  	if size == 0 {
   164  		// No data to unpack.
   165  	} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
   166  		// A single pointer is always stored directly.
   167  		return []llvm.Value{ptr}
   168  	} else if size <= b.targetData.TypeAllocSize(b.dataPtrType) {
   169  		// Packed data stored directly in pointer.
   170  		if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
   171  			// Keep this cast in SSA form.
   172  			return []llvm.Value{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
   173  		}
   174  		// Fallback: load it using an alloca.
   175  		packedAlloc, _ = b.createTemporaryAlloca(b.dataPtrType, "unpack.raw.alloc")
   176  		b.CreateStore(ptr, packedAlloc)
   177  		needsLifetimeEnd = true
   178  	} else {
   179  		// Packed data stored on the heap.
   180  		packedAlloc = ptr
   181  	}
   182  	// Load each value from the packed data.
   183  	values := make([]llvm.Value, len(valueTypes))
   184  	for i, valueType := range valueTypes {
   185  		if b.targetData.TypeAllocSize(valueType) == 0 {
   186  			// This value has length zero, so there's nothing to load.
   187  			values[i] = llvm.ConstNull(valueType)
   188  			continue
   189  		}
   190  		indices := []llvm.Value{
   191  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   192  			llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
   193  		}
   194  		gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
   195  		values[i] = b.CreateLoad(valueType, gep, "")
   196  	}
   197  	if needsLifetimeEnd {
   198  		allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false)
   199  		b.emitLifetimeEnd(packedAlloc, allocSize)
   200  	}
   201  	return values
   202  }
   203  
   204  // makeGlobalArray creates a new LLVM global with the given name and integers as
   205  // contents, and returns the global and initializer type.
   206  // Note that it is left with the default linkage etc., you should set
   207  // linkage/constant/etc properties yourself.
   208  func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) (llvm.Type, llvm.Value) {
   209  	globalType := llvm.ArrayType(elementType, len(buf))
   210  	global := llvm.AddGlobal(c.mod, globalType, name)
   211  	value := llvm.Undef(globalType)
   212  	for i := 0; i < len(buf); i++ {
   213  		ch := uint64(buf[i])
   214  		value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "")
   215  	}
   216  	global.SetInitializer(value)
   217  	return globalType, global
   218  }
   219  
   220  // createObjectLayout returns a LLVM value (of type i8*) that describes where
   221  // there are pointers in the type t. If all the data fits in a word, it is
   222  // returned as a word. Otherwise it will store the data in a global.
   223  //
   224  // The value contains two pieces of information: the length of the object and
   225  // which words contain a pointer (indicated by setting the given bit to 1). For
   226  // arrays, only the element is stored. This works because the GC knows the
   227  // object size and can therefore know how this value is repeated in the object.
   228  //
   229  // For details on what's in this value, see src/runtime/gc_precise.go.
   230  func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value {
   231  	// Use the element type for arrays. This works even for nested arrays.
   232  	for {
   233  		kind := t.TypeKind()
   234  		if kind == llvm.ArrayTypeKind {
   235  			t = t.ElementType()
   236  			continue
   237  		}
   238  		if kind == llvm.StructTypeKind {
   239  			fields := t.StructElementTypes()
   240  			if len(fields) == 1 {
   241  				t = fields[0]
   242  				continue
   243  			}
   244  		}
   245  		break
   246  	}
   247  
   248  	// Do a few checks to see whether we need to generate any object layout
   249  	// information at all.
   250  	objectSizeBytes := c.targetData.TypeAllocSize(t)
   251  	pointerSize := c.targetData.TypeAllocSize(c.dataPtrType)
   252  	pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType)
   253  	if objectSizeBytes < pointerSize {
   254  		// Too small to contain a pointer.
   255  		layout := (uint64(1) << 1) | 1
   256  		return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
   257  	}
   258  	bitmap := c.getPointerBitmap(t, pos)
   259  	if bitmap.BitLen() == 0 {
   260  		// There are no pointers in this type, so we can simplify the layout.
   261  		// TODO: this can be done in many other cases, e.g. when allocating an
   262  		// array (like [4][]byte, which repeats a slice 4 times).
   263  		layout := (uint64(1) << 1) | 1
   264  		return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
   265  	}
   266  	if objectSizeBytes%uint64(pointerAlignment) != 0 {
   267  		// This shouldn't happen except for packed structs, which aren't
   268  		// currently used.
   269  		c.addError(pos, "internal error: unexpected object size for object with pointer field")
   270  		return llvm.ConstNull(c.dataPtrType)
   271  	}
   272  	objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
   273  
   274  	pointerBits := pointerSize * 8
   275  	var sizeFieldBits uint64
   276  	switch pointerBits {
   277  	case 16:
   278  		sizeFieldBits = 4
   279  	case 32:
   280  		sizeFieldBits = 5
   281  	case 64:
   282  		sizeFieldBits = 6
   283  	default:
   284  		panic("unknown pointer size")
   285  	}
   286  	layoutFieldBits := pointerBits - 1 - sizeFieldBits
   287  
   288  	// Try to emit the value as an inline integer. This is possible in most
   289  	// cases.
   290  	if objectSizeWords < layoutFieldBits {
   291  		// If it can be stored directly in the pointer value, do so.
   292  		// The runtime knows that if the least significant bit of the pointer is
   293  		// set, the pointer contains the value itself.
   294  		layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
   295  		return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
   296  	}
   297  
   298  	// Unfortunately, the object layout is too big to fit in a pointer-sized
   299  	// integer. Store it in a global instead.
   300  
   301  	// Try first whether the global already exists. All objects with a
   302  	// particular name have the same type, so this is possible.
   303  	globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
   304  	global := c.mod.NamedGlobal(globalName)
   305  	if !global.IsNil() {
   306  		return global
   307  	}
   308  
   309  	// Create the global initializer.
   310  	bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
   311  	bitmap.FillBytes(bitmapBytes)
   312  	reverseBytes(bitmapBytes) // big-endian to little-endian
   313  	var bitmapByteValues []llvm.Value
   314  	for _, b := range bitmapBytes {
   315  		bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false))
   316  	}
   317  	initializer := c.ctx.ConstStruct([]llvm.Value{
   318  		llvm.ConstInt(c.uintptrType, objectSizeWords, false),
   319  		llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues),
   320  	}, false)
   321  
   322  	global = llvm.AddGlobal(c.mod, initializer.Type(), globalName)
   323  	global.SetInitializer(initializer)
   324  	global.SetUnnamedAddr(true)
   325  	global.SetGlobalConstant(true)
   326  	global.SetLinkage(llvm.LinkOnceODRLinkage)
   327  	if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 {
   328  		// AVR doesn't have alignment by default.
   329  		global.SetAlignment(2)
   330  	}
   331  	if c.Debug && pos != token.NoPos {
   332  		// Creating a fake global so that the value can be inspected in GDB.
   333  		// For example, the layout for strings.stringFinder (as of Go version
   334  		// 1.15) has the following type according to GDB:
   335  		//   type = struct {
   336  		//       uintptr numBits;
   337  		//       uint8 data[33];
   338  		//   }
   339  		// ...that's sort of a mixed C/Go type, but it is readable. More
   340  		// importantly, these object layout globals can be read and printed by
   341  		// GDB which may be useful for debugging.
   342  		position := c.program.Fset.Position(pos)
   343  		diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[position.Filename], llvm.DIGlobalVariableExpression{
   344  			Name: globalName,
   345  			File: c.getDIFile(position.Filename),
   346  			Line: position.Line,
   347  			Type: c.getDIType(types.NewStruct([]*types.Var{
   348  				types.NewVar(pos, nil, "numBits", types.Typ[types.Uintptr]),
   349  				types.NewVar(pos, nil, "data", types.NewArray(types.Typ[types.Byte], int64(len(bitmapByteValues)))),
   350  			}, nil)),
   351  			LocalToUnit: false,
   352  			Expr:        c.dibuilder.CreateExpression(nil),
   353  		})
   354  		global.AddMetadata(0, diglobal)
   355  	}
   356  
   357  	return global
   358  }
   359  
   360  // getPointerBitmap scans the given LLVM type for pointers and sets bits in a
   361  // bigint at the word offset that contains a pointer. This scan is recursive.
   362  func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
   363  	alignment := c.targetData.PrefTypeAlignment(c.dataPtrType)
   364  	switch typ.TypeKind() {
   365  	case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
   366  		return big.NewInt(0)
   367  	case llvm.PointerTypeKind:
   368  		return big.NewInt(1)
   369  	case llvm.StructTypeKind:
   370  		ptrs := big.NewInt(0)
   371  		if typ.StructName() == "runtime.funcValue" {
   372  			// Hack: the type runtime.funcValue contains an 'id' field which is
   373  			// of type uintptr, but before the LowerFuncValues pass it actually
   374  			// contains a pointer (ptrtoint) to a global. This trips up the
   375  			// interp package. Therefore, make the id field a pointer for now.
   376  			typ = c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false)
   377  		}
   378  		for i, subtyp := range typ.StructElementTypes() {
   379  			subptrs := c.getPointerBitmap(subtyp, pos)
   380  			if subptrs.BitLen() == 0 {
   381  				continue
   382  			}
   383  			offset := c.targetData.ElementOffset(typ, i)
   384  			if offset%uint64(alignment) != 0 {
   385  				// This error will let the compilation fail, but by continuing
   386  				// the error can still easily be shown.
   387  				c.addError(pos, "internal error: allocated struct contains unaligned pointer")
   388  				continue
   389  			}
   390  			subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
   391  			ptrs.Or(ptrs, subptrs)
   392  		}
   393  		return ptrs
   394  	case llvm.ArrayTypeKind:
   395  		subtyp := typ.ElementType()
   396  		subptrs := c.getPointerBitmap(subtyp, pos)
   397  		ptrs := big.NewInt(0)
   398  		if subptrs.BitLen() == 0 {
   399  			return ptrs
   400  		}
   401  		elementSize := c.targetData.TypeAllocSize(subtyp)
   402  		if elementSize%uint64(alignment) != 0 {
   403  			// This error will let the compilation fail (but continues so that
   404  			// other errors can be shown).
   405  			c.addError(pos, "internal error: allocated array contains unaligned pointer")
   406  			return ptrs
   407  		}
   408  		for i := 0; i < typ.ArrayLength(); i++ {
   409  			ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
   410  			ptrs.Or(ptrs, subptrs)
   411  		}
   412  		return ptrs
   413  	default:
   414  		// Should not happen.
   415  		panic("unknown LLVM type")
   416  	}
   417  }
   418  
   419  // archFamily returns the archtecture from the LLVM triple but with some
   420  // architecture names ("armv6", "thumbv7m", etc) merged into a single
   421  // architecture name ("arm").
   422  func (c *compilerContext) archFamily() string {
   423  	arch := strings.Split(c.Triple, "-")[0]
   424  	if strings.HasPrefix(arch, "arm64") {
   425  		return "aarch64"
   426  	}
   427  	if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
   428  		return "arm"
   429  	}
   430  	return arch
   431  }
   432  
   433  // isThumb returns whether we're in ARM or in Thumb mode. It panics if the
   434  // features string is not one for an ARM architecture.
   435  func (c *compilerContext) isThumb() bool {
   436  	var isThumb, isNotThumb bool
   437  	for _, feature := range strings.Split(c.Features, ",") {
   438  		if feature == "+thumb-mode" {
   439  			isThumb = true
   440  		}
   441  		if feature == "-thumb-mode" {
   442  			isNotThumb = true
   443  		}
   444  	}
   445  	if isThumb == isNotThumb {
   446  		panic("unexpected feature flags")
   447  	}
   448  	return isThumb
   449  }
   450  
   451  // readStackPointer emits a LLVM intrinsic call that returns the current stack
   452  // pointer as an *i8.
   453  func (b *builder) readStackPointer() llvm.Value {
   454  	stacksave := b.mod.NamedFunction("llvm.stacksave")
   455  	if stacksave.IsNil() {
   456  		fnType := llvm.FunctionType(b.dataPtrType, nil, false)
   457  		stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType)
   458  	}
   459  	return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "")
   460  }
   461  
   462  // createZExtOrTrunc lets the input value fit in the output type bits, by zero
   463  // extending or truncating the integer.
   464  func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value {
   465  	valueBits := value.Type().IntTypeWidth()
   466  	resultBits := t.IntTypeWidth()
   467  	if valueBits > resultBits {
   468  		value = b.CreateTrunc(value, t, "")
   469  	} else if valueBits < resultBits {
   470  		value = b.CreateZExt(value, t, "")
   471  	}
   472  	return value
   473  }
   474  
   475  // Reverse a slice of bytes. From the wiki:
   476  // https://github.com/golang/go/wiki/SliceTricks#reversing
   477  func reverseBytes(buf []byte) {
   478  	for i := len(buf)/2 - 1; i >= 0; i-- {
   479  		opp := len(buf) - 1 - i
   480  		buf[i], buf[opp] = buf[opp], buf[i]
   481  	}
   482  }