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 }