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

     1  // Package llvmutil contains utility functions used across multiple compiler
     2  // packages. For example, they may be used by both the compiler pacakge and
     3  // transformation packages.
     4  //
     5  // Normally, utility packages are avoided. However, in this case, the utility
     6  // functions are non-trivial and hard to get right. Copying them to multiple
     7  // places would be a big risk if only one of them is updated.
     8  package llvmutil
     9  
    10  import (
    11  	"tinygo.org/x/go-llvm"
    12  )
    13  
    14  // CreateEntryBlockAlloca creates a new alloca in the entry block, even though
    15  // the IR builder is located elsewhere. It assumes that the insert point is
    16  // at the end of the current block.
    17  func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm.Value {
    18  	currentBlock := builder.GetInsertBlock()
    19  	entryBlock := currentBlock.Parent().EntryBasicBlock()
    20  	if entryBlock.FirstInstruction().IsNil() {
    21  		builder.SetInsertPointAtEnd(entryBlock)
    22  	} else {
    23  		builder.SetInsertPointBefore(entryBlock.FirstInstruction())
    24  	}
    25  	alloca := builder.CreateAlloca(t, name)
    26  	builder.SetInsertPointAtEnd(currentBlock)
    27  	return alloca
    28  }
    29  
    30  // CreateTemporaryAlloca creates a new alloca in the entry block and adds
    31  // lifetime start infromation in the IR signalling that the alloca won't be used
    32  // before this point.
    33  //
    34  // This is useful for creating temporary allocas for intrinsics. Don't forget to
    35  // end the lifetime using emitLifetimeEnd after you're done with it.
    36  func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, size llvm.Value) {
    37  	ctx := t.Context()
    38  	targetData := llvm.NewTargetData(mod.DataLayout())
    39  	defer targetData.Dispose()
    40  	alloca = CreateEntryBlockAlloca(builder, t, name)
    41  	size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
    42  	fnType, fn := getLifetimeStartFunc(mod)
    43  	builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
    44  	return
    45  }
    46  
    47  // CreateInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
    48  func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, inst llvm.Value, name string) llvm.Value {
    49  	ctx := mod.Context()
    50  	targetData := llvm.NewTargetData(mod.DataLayout())
    51  	defer targetData.Dispose()
    52  
    53  	alloca := CreateEntryBlockAlloca(builder, t, name)
    54  	builder.SetInsertPointBefore(inst)
    55  	size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
    56  	fnType, fn := getLifetimeStartFunc(mod)
    57  	builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
    58  	if next := llvm.NextInstruction(inst); !next.IsNil() {
    59  		builder.SetInsertPointBefore(next)
    60  	} else {
    61  		builder.SetInsertPointAtEnd(inst.InstructionParent())
    62  	}
    63  	fnType, fn = getLifetimeEndFunc(mod)
    64  	builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
    65  	return alloca
    66  }
    67  
    68  // EmitLifetimeEnd signals the end of an (alloca) lifetime by calling the
    69  // llvm.lifetime.end intrinsic. It is commonly used together with
    70  // createTemporaryAlloca.
    71  func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
    72  	fnType, fn := getLifetimeEndFunc(mod)
    73  	builder.CreateCall(fnType, fn, []llvm.Value{size, ptr}, "")
    74  }
    75  
    76  // getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
    77  // first if it doesn't exist yet.
    78  func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
    79  	fnName := "llvm.lifetime.start.p0"
    80  	fn := mod.NamedFunction(fnName)
    81  	ctx := mod.Context()
    82  	ptrType := llvm.PointerType(ctx.Int8Type(), 0)
    83  	fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
    84  	if fn.IsNil() {
    85  		fn = llvm.AddFunction(mod, fnName, fnType)
    86  	}
    87  	return fnType, fn
    88  }
    89  
    90  // getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
    91  // first if it doesn't exist yet.
    92  func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
    93  	fnName := "llvm.lifetime.end.p0"
    94  	fn := mod.NamedFunction(fnName)
    95  	ctx := mod.Context()
    96  	ptrType := llvm.PointerType(ctx.Int8Type(), 0)
    97  	fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
    98  	if fn.IsNil() {
    99  		fn = llvm.AddFunction(mod, fnName, fnType)
   100  	}
   101  	return fnType, fn
   102  }
   103  
   104  // SplitBasicBlock splits a LLVM basic block into two parts. All instructions
   105  // after afterInst are moved into a new basic block (created right after the
   106  // current one) with the given name.
   107  func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
   108  	oldBlock := afterInst.InstructionParent()
   109  	newBlock := afterInst.Type().Context().InsertBasicBlock(insertAfter, name)
   110  	var nextInstructions []llvm.Value // values to move
   111  
   112  	// Collect to-be-moved instructions.
   113  	inst := afterInst
   114  	for {
   115  		inst = llvm.NextInstruction(inst)
   116  		if inst.IsNil() {
   117  			break
   118  		}
   119  		nextInstructions = append(nextInstructions, inst)
   120  	}
   121  
   122  	// Move instructions.
   123  	builder.SetInsertPointAtEnd(newBlock)
   124  	for _, inst := range nextInstructions {
   125  		inst.RemoveFromParentAsInstruction()
   126  		builder.Insert(inst)
   127  	}
   128  
   129  	// Find PHI nodes to update.
   130  	var phiNodes []llvm.Value // PHI nodes to update
   131  	for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
   132  		for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
   133  			if inst.IsAPHINode().IsNil() {
   134  				continue
   135  			}
   136  			needsUpdate := false
   137  			incomingCount := inst.IncomingCount()
   138  			for i := 0; i < incomingCount; i++ {
   139  				if inst.IncomingBlock(i) == oldBlock {
   140  					needsUpdate = true
   141  					break
   142  				}
   143  			}
   144  			if !needsUpdate {
   145  				// PHI node has no incoming edge from the old block.
   146  				continue
   147  			}
   148  			phiNodes = append(phiNodes, inst)
   149  		}
   150  	}
   151  
   152  	// Update PHI nodes.
   153  	for _, phi := range phiNodes {
   154  		builder.SetInsertPointBefore(phi)
   155  		newPhi := builder.CreatePHI(phi.Type(), "")
   156  		incomingCount := phi.IncomingCount()
   157  		incomingVals := make([]llvm.Value, incomingCount)
   158  		incomingBlocks := make([]llvm.BasicBlock, incomingCount)
   159  		for i := 0; i < incomingCount; i++ {
   160  			value := phi.IncomingValue(i)
   161  			block := phi.IncomingBlock(i)
   162  			if block == oldBlock {
   163  				block = newBlock
   164  			}
   165  			incomingVals[i] = value
   166  			incomingBlocks[i] = block
   167  		}
   168  		newPhi.AddIncoming(incomingVals, incomingBlocks)
   169  		phi.ReplaceAllUsesWith(newPhi)
   170  		phi.EraseFromParentAsInstruction()
   171  	}
   172  
   173  	return newBlock
   174  }
   175  
   176  // Append the given values to a global array like llvm.used. The global might
   177  // not exist yet. The values can be any pointer type, they will be cast to i8*.
   178  func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) {
   179  	// Read the existing values in the llvm.used array (if it exists).
   180  	var usedValues []llvm.Value
   181  	if used := mod.NamedGlobal(globalName); !used.IsNil() {
   182  		builder := mod.Context().NewBuilder()
   183  		defer builder.Dispose()
   184  		usedInitializer := used.Initializer()
   185  		num := usedInitializer.Type().ArrayLength()
   186  		for i := 0; i < num; i++ {
   187  			usedValues = append(usedValues, builder.CreateExtractValue(usedInitializer, i, ""))
   188  		}
   189  		used.EraseFromParentAsGlobal()
   190  	}
   191  
   192  	// Add the new values.
   193  	ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
   194  	for _, value := range values {
   195  		// Note: the bitcast is necessary to cast AVR function pointers to
   196  		// address space 0 pointer types.
   197  		usedValues = append(usedValues, llvm.ConstPointerCast(value, ptrType))
   198  	}
   199  
   200  	// Create a new array (with the old and new values).
   201  	usedInitializer := llvm.ConstArray(ptrType, usedValues)
   202  	used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName)
   203  	used.SetInitializer(usedInitializer)
   204  	used.SetLinkage(llvm.AppendingLinkage)
   205  }