compiler: refactor func lowering to the transform package
This commit makes a number of changes: * It avoids a dependency on Compiler.emitStartGoroutine. * It moves the func-lowering pass to the transform package. * It adds testing to func lowering. No functionality should have changed with this commit.
Этот коммит содержится в:
родитель
024a0827ea
коммит
374349cfa5
8 изменённых файлов: 346 добавлений и 114 удалений
|
@ -805,7 +805,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||||
llvm.ConstNull(c.ctx.TokenType()),
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||||
}, "")
|
}, "")
|
||||||
wakeup := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
wakeup := llvmutil.SplitBasicBlock(c.builder, inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
||||||
c.builder.SetInsertPointBefore(inst)
|
c.builder.SetInsertPointBefore(inst)
|
||||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||||
|
|
|
@ -52,78 +52,6 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
|
||||||
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
|
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeGlobalArray creates a new LLVM global with the given name and integers as
|
// makeGlobalArray creates a new LLVM global with the given name and integers as
|
||||||
// contents, and returns the global.
|
// contents, and returns the global.
|
||||||
// Note that it is left with the default linkage etc., you should set
|
// Note that it is left with the default linkage etc., you should set
|
||||||
|
|
|
@ -94,3 +94,75 @@ func getLifetimeEndFunc(mod llvm.Module) llvm.Value {
|
||||||
}
|
}
|
||||||
return fn
|
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 SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
||||||
|
oldBlock := afterInst.InstructionParent()
|
||||||
|
newBlock := afterInst.Type().Context().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.
|
||||||
|
builder.SetInsertPointAtEnd(newBlock)
|
||||||
|
for _, inst := range nextInstructions {
|
||||||
|
inst.RemoveFromParentAsInstruction()
|
||||||
|
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 {
|
||||||
|
builder.SetInsertPointBefore(phi)
|
||||||
|
newPhi := 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
|
||||||
|
}
|
||||||
|
|
|
@ -56,7 +56,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||||
transform.OptimizeStringToBytes(c.mod)
|
transform.OptimizeStringToBytes(c.mod)
|
||||||
transform.OptimizeAllocs(c.mod)
|
transform.OptimizeAllocs(c.mod)
|
||||||
transform.LowerInterfaces(c.mod)
|
transform.LowerInterfaces(c.mod)
|
||||||
c.LowerFuncValues()
|
if c.funcImplementation() == funcValueSwitch {
|
||||||
|
transform.LowerFuncValues(c.mod)
|
||||||
|
}
|
||||||
|
|
||||||
// After interfaces are lowered, there are many more opportunities for
|
// After interfaces are lowered, there are many more opportunities for
|
||||||
// interprocedural optimizations. To get them to work, function
|
// interprocedural optimizations. To get them to work, function
|
||||||
|
@ -90,7 +92,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||||
} else {
|
} else {
|
||||||
// Must be run at any optimization level.
|
// Must be run at any optimization level.
|
||||||
transform.LowerInterfaces(c.mod)
|
transform.LowerInterfaces(c.mod)
|
||||||
c.LowerFuncValues()
|
if c.funcImplementation() == funcValueSwitch {
|
||||||
|
transform.LowerFuncValues(c.mod)
|
||||||
|
}
|
||||||
err := c.LowerGoroutines()
|
err := c.LowerGoroutines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package compiler
|
package transform
|
||||||
|
|
||||||
// This file lowers func values into their final form. This is necessary for
|
// This file lowers func values into their final form. This is necessary for
|
||||||
// funcValueSwitch, which needs full program analysis.
|
// funcValueSwitch, which needs full program analysis.
|
||||||
|
@ -7,6 +7,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,17 +44,17 @@ func (l funcWithUsesList) Swap(i, j int) {
|
||||||
l[i], l[j] = l[j], l[i]
|
l[i], l[j] = l[j], l[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// LowerFuncValue lowers the runtime.funcValueWithSignature type and
|
// LowerFuncValues lowers the runtime.funcValueWithSignature type and
|
||||||
// runtime.getFuncPtr function to their final form.
|
// runtime.getFuncPtr function to their final form.
|
||||||
func (c *Compiler) LowerFuncValues() {
|
func LowerFuncValues(mod llvm.Module) {
|
||||||
if c.funcImplementation() != funcValueSwitch {
|
ctx := mod.Context()
|
||||||
return
|
builder := ctx.NewBuilder()
|
||||||
}
|
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||||
|
|
||||||
// Find all func values used in the program with their signatures.
|
// Find all func values used in the program with their signatures.
|
||||||
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0)
|
funcValueWithSignaturePtr := llvm.PointerType(mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
|
||||||
signatures := map[string]*funcSignatureInfo{}
|
signatures := map[string]*funcSignatureInfo{}
|
||||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
if global.Type() != funcValueWithSignaturePtr {
|
if global.Type() != funcValueWithSignaturePtr {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -107,7 +108,7 @@ func (c *Compiler) LowerFuncValues() {
|
||||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||||
panic("expected const ptrtoint")
|
panic("expected const ptrtoint")
|
||||||
}
|
}
|
||||||
use.ReplaceAllUsesWith(llvm.ConstInt(c.uintptrType, uint64(fn.id), false))
|
use.ReplaceAllUsesWith(llvm.ConstInt(uintptrType, uint64(fn.id), false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,11 +140,11 @@ func (c *Compiler) LowerFuncValues() {
|
||||||
// There is exactly one function with this signature that is
|
// There is exactly one function with this signature that is
|
||||||
// used in a func value. The func value itself can be either nil
|
// used in a func value. The func value itself can be either nil
|
||||||
// or this one function.
|
// or this one function.
|
||||||
c.builder.SetInsertPointBefore(getFuncPtrCall)
|
builder.SetInsertPointBefore(getFuncPtrCall)
|
||||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
zero := llvm.ConstInt(uintptrType, 0, false)
|
||||||
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
isnil := builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
||||||
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
|
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
|
||||||
funcPtr := c.builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
|
funcPtr := builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
|
||||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||||
panic("expected inttoptr")
|
panic("expected inttoptr")
|
||||||
|
@ -181,15 +182,23 @@ func (c *Compiler) LowerFuncValues() {
|
||||||
// to replace.
|
// to replace.
|
||||||
for _, callIntPtr := range getUses(getFuncPtrCall) {
|
for _, callIntPtr := range getUses(getFuncPtrCall) {
|
||||||
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
|
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
|
||||||
|
// Special case for runtime.makeGoroutine.
|
||||||
for _, inttoptr := range getUses(callIntPtr) {
|
for _, inttoptr := range getUses(callIntPtr) {
|
||||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||||
panic("expected a inttoptr")
|
panic("expected a inttoptr")
|
||||||
}
|
}
|
||||||
for _, use := range getUses(inttoptr) {
|
for _, use := range getUses(inttoptr) {
|
||||||
c.addFuncLoweringSwitch(funcID, use, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
addFuncLoweringSwitch(mod, builder, funcID, use, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||||
// The function lowering switch code passes in a parent handle value.
|
// The function lowering switch code passes in a parent handle value.
|
||||||
// Strip the parent handle off here because it is irrelevant to goroutine starts.
|
// Set the parent handle to null here because it is irrelevant to goroutine starts.
|
||||||
return c.emitStartGoroutine(funcPtr, params[:len(params)-1])
|
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
params[len(params)-1] = llvm.ConstPointerNull(i8ptrType)
|
||||||
|
calleeValue := builder.CreatePtrToInt(funcPtr, uintptrType, "")
|
||||||
|
makeGoroutine := mod.NamedFunction("runtime.makeGoroutine")
|
||||||
|
calleeValue = builder.CreateCall(makeGoroutine, []llvm.Value{calleeValue, llvm.Undef(i8ptrType), llvm.ConstNull(i8ptrType)}, "")
|
||||||
|
calleeValue = builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
|
||||||
|
builder.CreateCall(calleeValue, params, "")
|
||||||
|
return llvm.Value{} // void so no return value
|
||||||
}, functions)
|
}, functions)
|
||||||
use.EraseFromParentAsInstruction()
|
use.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
|
@ -209,15 +218,15 @@ func (c *Compiler) LowerFuncValues() {
|
||||||
}
|
}
|
||||||
switch bitcastUse.CalledValue().Name() {
|
switch bitcastUse.CalledValue().Name() {
|
||||||
case "runtime.isnil":
|
case "runtime.isnil":
|
||||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(ctx.Int1Type(), 0, false))
|
||||||
bitcastUse.EraseFromParentAsInstruction()
|
bitcastUse.EraseFromParentAsInstruction()
|
||||||
default:
|
default:
|
||||||
panic("expected a call to runtime.isnil")
|
panic("expected a call to runtime.isnil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
|
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
|
||||||
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
addFuncLoweringSwitch(mod, builder, funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||||
return c.builder.CreateCall(funcPtr, params, "")
|
return builder.CreateCall(funcPtr, params, "")
|
||||||
}, functions)
|
}, functions)
|
||||||
} else {
|
} else {
|
||||||
panic("unexpected getFuncPtrCall")
|
panic("unexpected getFuncPtrCall")
|
||||||
|
@ -236,30 +245,35 @@ func (c *Compiler) LowerFuncValues() {
|
||||||
// to the newly created direct calls. The funcID is the number to switch on,
|
// to the newly created direct calls. The funcID is the number to switch on,
|
||||||
// call is the call instruction to replace, and createCall is the callback that
|
// call is the call instruction to replace, and createCall is the callback that
|
||||||
// actually creates the new call. By changing createCall to something other than
|
// actually creates the new call. By changing createCall to something other than
|
||||||
// c.builder.CreateCall, instead of calling a function it can start a new
|
// builder.CreateCall, instead of calling a function it can start a new
|
||||||
// goroutine for example.
|
// goroutine for example.
|
||||||
func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
|
func addFuncLoweringSwitch(mod llvm.Module, builder llvm.Builder, funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
|
||||||
|
ctx := mod.Context()
|
||||||
|
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||||
|
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
|
||||||
// The block that cannot be reached with correct funcValues (to help the
|
// The block that cannot be reached with correct funcValues (to help the
|
||||||
// optimizer).
|
// optimizer).
|
||||||
c.builder.SetInsertPointBefore(call)
|
builder.SetInsertPointBefore(call)
|
||||||
defaultBlock := c.ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
|
defaultBlock := ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
|
||||||
c.builder.SetInsertPointAtEnd(defaultBlock)
|
builder.SetInsertPointAtEnd(defaultBlock)
|
||||||
c.builder.CreateUnreachable()
|
builder.CreateUnreachable()
|
||||||
|
|
||||||
// Create the switch.
|
// Create the switch.
|
||||||
c.builder.SetInsertPointBefore(call)
|
builder.SetInsertPointBefore(call)
|
||||||
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
sw := builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||||
|
|
||||||
// Split right after the switch. We will need to insert a few basic blocks
|
// Split right after the switch. We will need to insert a few basic blocks
|
||||||
// in this gap.
|
// in this gap.
|
||||||
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
|
nextBlock := llvmutil.SplitBasicBlock(builder, sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
|
||||||
|
|
||||||
// The 0 case, which is actually a nil check.
|
// The 0 case, which is actually a nil check.
|
||||||
nilBlock := c.ctx.InsertBasicBlock(nextBlock, "func.nil")
|
nilBlock := ctx.InsertBasicBlock(nextBlock, "func.nil")
|
||||||
c.builder.SetInsertPointAtEnd(nilBlock)
|
builder.SetInsertPointAtEnd(nilBlock)
|
||||||
c.createRuntimeCall("nilPanic", nil, "")
|
nilPanic := mod.NamedFunction("runtime.nilPanic")
|
||||||
c.builder.CreateUnreachable()
|
builder.CreateCall(nilPanic, []llvm.Value{llvm.Undef(i8ptrType), llvm.ConstNull(i8ptrType)}, "")
|
||||||
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
|
builder.CreateUnreachable()
|
||||||
|
sw.AddCase(llvm.ConstInt(uintptrType, 0, false), nilBlock)
|
||||||
|
|
||||||
// Gather the list of parameters for every call we're going to make.
|
// Gather the list of parameters for every call we're going to make.
|
||||||
callParams := make([]llvm.Value, call.OperandsCount()-1)
|
callParams := make([]llvm.Value, call.OperandsCount()-1)
|
||||||
|
@ -273,11 +287,11 @@ func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall fun
|
||||||
phiValues := make([]llvm.Value, len(functions))
|
phiValues := make([]llvm.Value, len(functions))
|
||||||
for i, fn := range functions {
|
for i, fn := range functions {
|
||||||
// Insert a switch case.
|
// Insert a switch case.
|
||||||
bb := c.ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
bb := ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||||
c.builder.SetInsertPointAtEnd(bb)
|
builder.SetInsertPointAtEnd(bb)
|
||||||
result := createCall(fn.funcPtr, callParams)
|
result := createCall(fn.funcPtr, callParams)
|
||||||
c.builder.CreateBr(nextBlock)
|
builder.CreateBr(nextBlock)
|
||||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
|
sw.AddCase(llvm.ConstInt(uintptrType, uint64(fn.id), false), bb)
|
||||||
phiBlocks[i] = bb
|
phiBlocks[i] = bb
|
||||||
phiValues[i] = result
|
phiValues[i] = result
|
||||||
}
|
}
|
||||||
|
@ -285,8 +299,8 @@ func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall fun
|
||||||
// next block (after the split). This is only necessary when the
|
// next block (after the split). This is only necessary when the
|
||||||
// call produced a value.
|
// call produced a value.
|
||||||
if call.Type().TypeKind() != llvm.VoidTypeKind {
|
if call.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||||
phi := c.builder.CreatePHI(call.Type(), "")
|
phi := builder.CreatePHI(call.Type(), "")
|
||||||
phi.AddIncoming(phiValues, phiBlocks)
|
phi.AddIncoming(phiValues, phiBlocks)
|
||||||
call.ReplaceAllUsesWith(phi)
|
call.ReplaceAllUsesWith(phi)
|
||||||
}
|
}
|
10
transform/func-lowering_test.go
Обычный файл
10
transform/func-lowering_test.go
Обычный файл
|
@ -0,0 +1,10 @@
|
||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncLowering(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testTransform(t, "testdata/func-lowering", LowerFuncValues)
|
||||||
|
}
|
83
transform/testdata/func-lowering.ll
предоставленный
Обычный файл
83
transform/testdata/func-lowering.ll
предоставленный
Обычный файл
|
@ -0,0 +1,83 @@
|
||||||
|
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||||
|
target triple = "wasm32-unknown-unknown-wasm"
|
||||||
|
|
||||||
|
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
|
||||||
|
%runtime.funcValueWithSignature = type { i32, %runtime.typecodeID* }
|
||||||
|
|
||||||
|
@"reflect/types.type:func:{basic:int8}{}" = external constant %runtime.typecodeID
|
||||||
|
@"reflect/types.type:func:{basic:uint8}{}" = external constant %runtime.typecodeID
|
||||||
|
@"reflect/types.type:func:{basic:int}{}" = external constant %runtime.typecodeID
|
||||||
|
@"funcInt8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @funcInt8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}" }
|
||||||
|
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||||
|
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||||
|
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||||
|
@"main$2$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$2" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||||
|
|
||||||
|
declare i32 @runtime.getFuncPtr(i8*, i32, %runtime.typecodeID*, i8*, i8*)
|
||||||
|
|
||||||
|
declare i32 @runtime.makeGoroutine(i32, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @runtime.nilPanic(i8*, i8*)
|
||||||
|
|
||||||
|
declare i1 @runtime.isnil(i8*, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @"main$1"(i32, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @"main$2"(i32, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @funcInt8(i8, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @func1Uint8(i8, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @func2Uint8(i8, i8*, i8*)
|
||||||
|
|
||||||
|
; Call a function of which only one function with this signature is used as a
|
||||||
|
; function value. This means that lowering it to IR is trivial: simply check
|
||||||
|
; whether the func value is nil, and if not, call that one function directly.
|
||||||
|
define void @runFunc1(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||||
|
entry:
|
||||||
|
%3 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}", i8* undef, i8* null)
|
||||||
|
%4 = inttoptr i32 %3 to void (i8, i8*, i8*)*
|
||||||
|
%5 = bitcast void (i8, i8*, i8*)* %4 to i8*
|
||||||
|
%6 = call i1 @runtime.isnil(i8* %5, i8* undef, i8* null)
|
||||||
|
br i1 %6, label %fpcall.nil, label %fpcall.next
|
||||||
|
|
||||||
|
fpcall.nil:
|
||||||
|
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||||
|
unreachable
|
||||||
|
|
||||||
|
fpcall.next:
|
||||||
|
call void %4(i8 %2, i8* %0, i8* undef)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; There are two functions with this signature used in a func value. That means
|
||||||
|
; that we'll have to check at runtime which of the two it is (or whether the
|
||||||
|
; func value is nil). This call will thus be lowered to a switch statement.
|
||||||
|
define void @runFunc2(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||||
|
entry:
|
||||||
|
%3 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}", i8* undef, i8* null)
|
||||||
|
%4 = inttoptr i32 %3 to void (i8, i8*, i8*)*
|
||||||
|
%5 = bitcast void (i8, i8*, i8*)* %4 to i8*
|
||||||
|
%6 = call i1 @runtime.isnil(i8* %5, i8* undef, i8* null)
|
||||||
|
br i1 %6, label %fpcall.nil, label %fpcall.next
|
||||||
|
|
||||||
|
fpcall.nil:
|
||||||
|
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||||
|
unreachable
|
||||||
|
|
||||||
|
fpcall.next:
|
||||||
|
call void %4(i8 %2, i8* %0, i8* undef)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Special case for runtime.makeGoroutine.
|
||||||
|
define void @sleepFuncValue(i8*, i32, i8* nocapture readnone %context, i8* nocapture readnone %parentHandle) {
|
||||||
|
entry:
|
||||||
|
%2 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}", i8* undef, i8* null)
|
||||||
|
%3 = call i32 @runtime.makeGoroutine(i32 %2, i8* undef, i8* null)
|
||||||
|
%4 = inttoptr i32 %3 to void (i32, i8*, i8*)*
|
||||||
|
call void %4(i32 8, i8* %0, i8* null)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
121
transform/testdata/func-lowering.out.ll
предоставленный
Обычный файл
121
transform/testdata/func-lowering.out.ll
предоставленный
Обычный файл
|
@ -0,0 +1,121 @@
|
||||||
|
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||||
|
target triple = "wasm32-unknown-unknown-wasm"
|
||||||
|
|
||||||
|
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
|
||||||
|
%runtime.funcValueWithSignature = type { i32, %runtime.typecodeID* }
|
||||||
|
|
||||||
|
@"reflect/types.type:func:{basic:int8}{}" = external constant %runtime.typecodeID
|
||||||
|
@"reflect/types.type:func:{basic:uint8}{}" = external constant %runtime.typecodeID
|
||||||
|
@"reflect/types.type:func:{basic:int}{}" = external constant %runtime.typecodeID
|
||||||
|
@"funcInt8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @funcInt8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}" }
|
||||||
|
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||||
|
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||||
|
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||||
|
@"main$2$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$2" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||||
|
|
||||||
|
declare i32 @runtime.getFuncPtr(i8*, i32, %runtime.typecodeID*, i8*, i8*)
|
||||||
|
|
||||||
|
declare i32 @runtime.makeGoroutine(i32, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @runtime.nilPanic(i8*, i8*)
|
||||||
|
|
||||||
|
declare i1 @runtime.isnil(i8*, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @"main$1"(i32, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @"main$2"(i32, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @funcInt8(i8, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @func1Uint8(i8, i8*, i8*)
|
||||||
|
|
||||||
|
declare void @func2Uint8(i8, i8*, i8*)
|
||||||
|
|
||||||
|
; Call a function of which only one function with this signature is used as a
|
||||||
|
; function value. This means that lowering it to IR is trivial: simply check
|
||||||
|
; whether the func value is nil, and if not, call that one function directly.
|
||||||
|
define void @runFunc1(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||||
|
entry:
|
||||||
|
%3 = icmp eq i32 %1, 0
|
||||||
|
%4 = select i1 %3, void (i8, i8*, i8*)* null, void (i8, i8*, i8*)* @funcInt8
|
||||||
|
%5 = bitcast void (i8, i8*, i8*)* %4 to i8*
|
||||||
|
%6 = call i1 @runtime.isnil(i8* %5, i8* undef, i8* null)
|
||||||
|
br i1 %6, label %fpcall.nil, label %fpcall.next
|
||||||
|
|
||||||
|
fpcall.nil:
|
||||||
|
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||||
|
unreachable
|
||||||
|
|
||||||
|
fpcall.next:
|
||||||
|
call void %4(i8 %2, i8* %0, i8* undef)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; There are two functions with this signature used in a func value. That means
|
||||||
|
; that we'll have to check at runtime which of the two it is (or whether the
|
||||||
|
; func value is nil). This call will thus be lowered to a switch statement.
|
||||||
|
define void @runFunc2(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||||
|
entry:
|
||||||
|
br i1 false, label %fpcall.nil, label %fpcall.next
|
||||||
|
|
||||||
|
fpcall.nil:
|
||||||
|
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||||
|
unreachable
|
||||||
|
|
||||||
|
fpcall.next:
|
||||||
|
switch i32 %1, label %func.default [
|
||||||
|
i32 0, label %func.nil
|
||||||
|
i32 1, label %func.call1
|
||||||
|
i32 2, label %func.call2
|
||||||
|
]
|
||||||
|
|
||||||
|
func.nil:
|
||||||
|
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||||
|
unreachable
|
||||||
|
|
||||||
|
func.call1:
|
||||||
|
call void @func1Uint8(i8 %2, i8* %0, i8* undef)
|
||||||
|
br label %func.next
|
||||||
|
|
||||||
|
func.call2:
|
||||||
|
call void @func2Uint8(i8 %2, i8* %0, i8* undef)
|
||||||
|
br label %func.next
|
||||||
|
|
||||||
|
func.next:
|
||||||
|
ret void
|
||||||
|
|
||||||
|
func.default:
|
||||||
|
unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
; Special case for runtime.makeGoroutine.
|
||||||
|
define void @sleepFuncValue(i8*, i32, i8* nocapture readnone %context, i8* nocapture readnone %parentHandle) {
|
||||||
|
entry:
|
||||||
|
switch i32 %1, label %func.default [
|
||||||
|
i32 0, label %func.nil
|
||||||
|
i32 1, label %func.call1
|
||||||
|
i32 2, label %func.call2
|
||||||
|
]
|
||||||
|
|
||||||
|
func.nil:
|
||||||
|
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||||
|
unreachable
|
||||||
|
|
||||||
|
func.call1:
|
||||||
|
%2 = call i32 @runtime.makeGoroutine(i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), i8* undef, i8* null)
|
||||||
|
%3 = inttoptr i32 %2 to void (i32, i8*, i8*)*
|
||||||
|
call void %3(i32 8, i8* %0, i8* null)
|
||||||
|
br label %func.next
|
||||||
|
|
||||||
|
func.call2:
|
||||||
|
%4 = call i32 @runtime.makeGoroutine(i32 ptrtoint (void (i32, i8*, i8*)* @"main$2" to i32), i8* undef, i8* null)
|
||||||
|
%5 = inttoptr i32 %4 to void (i32, i8*, i8*)*
|
||||||
|
call void %5(i32 8, i8* %0, i8* null)
|
||||||
|
br label %func.next
|
||||||
|
|
||||||
|
func.next:
|
||||||
|
ret void
|
||||||
|
|
||||||
|
func.default:
|
||||||
|
unreachable
|
||||||
|
}
|
Загрузка…
Создание таблицы
Сослаться в новой задаче