
By moving all allocas used in hashmap operations to the entry block, the stack frame remains at a fixed size known at compile time. This avoids stack overflows when doing map operations in loops and in general improves code quality: the compiled size of testdata/map.go went from 3776 to 3632 in .text size.
136 строки
4,3 КиБ
Go
136 строки
4,3 КиБ
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
|
|
// after the last instruction in the current block. Also, it adds lifetime
|
|
// information to 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 after you're done with it.
|
|
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
|
currentBlock := c.builder.GetInsertBlock()
|
|
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
|
|
alloca = c.builder.CreateAlloca(t, name)
|
|
c.builder.SetInsertPointAtEnd(currentBlock)
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|