
Make sure all allocas are created in the entry block and are given the right lifetimes. This is good for code quality: * Moving allocas to the entry block makes sure they are always allocated statically (avoiding the need for a frame pointer) and do not grow the stack on each new alloca instruction. This is especially useful in loops where it could otherwise lead to a stack overflow even though there is no recursion. * Adding lifetime markers allows LLVM to reuse stack areas for different allocas as long as their lifetimes do not overlap. All in all, this reduces code size in all tested cases for the BBC micro:bit, and reduces code size for most cases for WebAssembly.
154 строки
5 КиБ
Go
154 строки
5 КиБ
Go
package compiler
|
|
|
|
import (
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// This file contains helper functions for LLVM that are not exposed in the Go
|
|
// bindings.
|
|
|
|
// Return a list of values (actually, instructions) where this value is used as
|
|
// an operand.
|
|
func getUses(value llvm.Value) []llvm.Value {
|
|
if value.IsNil() {
|
|
return nil
|
|
}
|
|
var uses []llvm.Value
|
|
use := value.FirstUse()
|
|
for !use.IsNil() {
|
|
uses = append(uses, use.User())
|
|
use = use.NextUse()
|
|
}
|
|
return uses
|
|
}
|
|
|
|
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
|
// the IR builder is located elsewhere. It assumes that the insert point is
|
|
// at the end of the current block.
|
|
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
|
|
currentBlock := c.builder.GetInsertBlock()
|
|
entryBlock := currentBlock.Parent().EntryBasicBlock()
|
|
if entryBlock.FirstInstruction().IsNil() {
|
|
c.builder.SetInsertPointAtEnd(entryBlock)
|
|
} else {
|
|
c.builder.SetInsertPointBefore(entryBlock.FirstInstruction())
|
|
}
|
|
alloca := c.builder.CreateAlloca(t, name)
|
|
c.builder.SetInsertPointAtEnd(currentBlock)
|
|
return alloca
|
|
}
|
|
|
|
// createTemporaryAlloca creates a new alloca in the entry block and adds
|
|
// lifetime start infromation in the IR signalling that the alloca won't be used
|
|
// before this point.
|
|
//
|
|
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
|
// end the lifetime using emitLifetimeEnd after you're done with it.
|
|
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
|
alloca = c.createEntryBlockAlloca(t, name)
|
|
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
|
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
|
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
|
return
|
|
}
|
|
|
|
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
|
// llvm.lifetime.end intrinsic. It is commonly used together with
|
|
// createTemporaryAlloca.
|
|
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
|
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "")
|
|
}
|
|
|
|
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
|
// first if it doesn't exist yet.
|
|
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
|
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
|
|
if fn.IsNil() {
|
|
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
|
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
|
|
}
|
|
return fn
|
|
}
|
|
|
|
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
|
|
// first if it doesn't exist yet.
|
|
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
|
|
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
|
|
if fn.IsNil() {
|
|
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
|
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
|
|
}
|
|
return fn
|
|
}
|
|
|
|
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
|
// after afterInst are moved into a new basic block (created right after the
|
|
// current one) with the given name.
|
|
func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
|
oldBlock := afterInst.InstructionParent()
|
|
newBlock := c.ctx.InsertBasicBlock(insertAfter, name)
|
|
var nextInstructions []llvm.Value // values to move
|
|
|
|
// Collect to-be-moved instructions.
|
|
inst := afterInst
|
|
for {
|
|
inst = llvm.NextInstruction(inst)
|
|
if inst.IsNil() {
|
|
break
|
|
}
|
|
nextInstructions = append(nextInstructions, inst)
|
|
}
|
|
|
|
// Move instructions.
|
|
c.builder.SetInsertPointAtEnd(newBlock)
|
|
for _, inst := range nextInstructions {
|
|
inst.RemoveFromParentAsInstruction()
|
|
c.builder.Insert(inst)
|
|
}
|
|
|
|
// Find PHI nodes to update.
|
|
var phiNodes []llvm.Value // PHI nodes to update
|
|
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
|
if inst.IsAPHINode().IsNil() {
|
|
continue
|
|
}
|
|
needsUpdate := false
|
|
incomingCount := inst.IncomingCount()
|
|
for i := 0; i < incomingCount; i++ {
|
|
if inst.IncomingBlock(i) == oldBlock {
|
|
needsUpdate = true
|
|
break
|
|
}
|
|
}
|
|
if !needsUpdate {
|
|
// PHI node has no incoming edge from the old block.
|
|
continue
|
|
}
|
|
phiNodes = append(phiNodes, inst)
|
|
}
|
|
}
|
|
|
|
// Update PHI nodes.
|
|
for _, phi := range phiNodes {
|
|
c.builder.SetInsertPointBefore(phi)
|
|
newPhi := c.builder.CreatePHI(phi.Type(), "")
|
|
incomingCount := phi.IncomingCount()
|
|
incomingVals := make([]llvm.Value, incomingCount)
|
|
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
|
|
for i := 0; i < incomingCount; i++ {
|
|
value := phi.IncomingValue(i)
|
|
block := phi.IncomingBlock(i)
|
|
if block == oldBlock {
|
|
block = newBlock
|
|
}
|
|
incomingVals[i] = value
|
|
incomingBlocks[i] = block
|
|
}
|
|
newPhi.AddIncoming(incomingVals, incomingBlocks)
|
|
phi.ReplaceAllUsesWith(newPhi)
|
|
phi.EraseFromParentAsInstruction()
|
|
}
|
|
|
|
return newBlock
|
|
}
|