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.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)
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// contents, and returns the global.
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.OptimizeAllocs(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
|
||||
// interprocedural optimizations. To get them to work, function
|
||||
|
@ -90,7 +92,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||
} else {
|
||||
// Must be run at any optimization level.
|
||||
transform.LowerInterfaces(c.mod)
|
||||
c.LowerFuncValues()
|
||||
if c.funcImplementation() == funcValueSwitch {
|
||||
transform.LowerFuncValues(c.mod)
|
||||
}
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package compiler
|
||||
package transform
|
||||
|
||||
// This file lowers func values into their final form. This is necessary for
|
||||
// funcValueSwitch, which needs full program analysis.
|
||||
|
@ -7,6 +7,7 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
|
@ -43,17 +44,17 @@ func (l funcWithUsesList) Swap(i, j int) {
|
|||
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.
|
||||
func (c *Compiler) LowerFuncValues() {
|
||||
if c.funcImplementation() != funcValueSwitch {
|
||||
return
|
||||
}
|
||||
func LowerFuncValues(mod llvm.Module) {
|
||||
ctx := mod.Context()
|
||||
builder := ctx.NewBuilder()
|
||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||
|
||||
// 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{}
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
|
@ -107,7 +108,7 @@ func (c *Compiler) LowerFuncValues() {
|
|||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.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
|
||||
// used in a func value. The func value itself can be either nil
|
||||
// or this one function.
|
||||
c.builder.SetInsertPointBefore(getFuncPtrCall)
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
||||
builder.SetInsertPointBefore(getFuncPtrCall)
|
||||
zero := llvm.ConstInt(uintptrType, 0, false)
|
||||
isnil := builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
||||
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) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
|
@ -181,15 +182,23 @@ func (c *Compiler) LowerFuncValues() {
|
|||
// to replace.
|
||||
for _, callIntPtr := range getUses(getFuncPtrCall) {
|
||||
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
|
||||
// Special case for runtime.makeGoroutine.
|
||||
for _, inttoptr := range getUses(callIntPtr) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected a 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.
|
||||
// Strip the parent handle off here because it is irrelevant to goroutine starts.
|
||||
return c.emitStartGoroutine(funcPtr, params[:len(params)-1])
|
||||
// Set the parent handle to null here because it is irrelevant to goroutine starts.
|
||||
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)
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
@ -209,15 +218,15 @@ func (c *Compiler) LowerFuncValues() {
|
|||
}
|
||||
switch bitcastUse.CalledValue().Name() {
|
||||
case "runtime.isnil":
|
||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(ctx.Int1Type(), 0, false))
|
||||
bitcastUse.EraseFromParentAsInstruction()
|
||||
default:
|
||||
panic("expected a call to runtime.isnil")
|
||||
}
|
||||
}
|
||||
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
|
||||
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
return c.builder.CreateCall(funcPtr, params, "")
|
||||
addFuncLoweringSwitch(mod, builder, funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
return builder.CreateCall(funcPtr, params, "")
|
||||
}, functions)
|
||||
} else {
|
||||
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,
|
||||
// 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
|
||||
// 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.
|
||||
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
|
||||
// optimizer).
|
||||
c.builder.SetInsertPointBefore(call)
|
||||
defaultBlock := c.ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
|
||||
c.builder.SetInsertPointAtEnd(defaultBlock)
|
||||
c.builder.CreateUnreachable()
|
||||
builder.SetInsertPointBefore(call)
|
||||
defaultBlock := ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
|
||||
builder.SetInsertPointAtEnd(defaultBlock)
|
||||
builder.CreateUnreachable()
|
||||
|
||||
// Create the switch.
|
||||
c.builder.SetInsertPointBefore(call)
|
||||
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||
builder.SetInsertPointBefore(call)
|
||||
sw := builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||
|
||||
// Split right after the switch. We will need to insert a few basic blocks
|
||||
// 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.
|
||||
nilBlock := c.ctx.InsertBasicBlock(nextBlock, "func.nil")
|
||||
c.builder.SetInsertPointAtEnd(nilBlock)
|
||||
c.createRuntimeCall("nilPanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
|
||||
nilBlock := ctx.InsertBasicBlock(nextBlock, "func.nil")
|
||||
builder.SetInsertPointAtEnd(nilBlock)
|
||||
nilPanic := mod.NamedFunction("runtime.nilPanic")
|
||||
builder.CreateCall(nilPanic, []llvm.Value{llvm.Undef(i8ptrType), llvm.ConstNull(i8ptrType)}, "")
|
||||
builder.CreateUnreachable()
|
||||
sw.AddCase(llvm.ConstInt(uintptrType, 0, false), nilBlock)
|
||||
|
||||
// Gather the list of parameters for every call we're going to make.
|
||||
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))
|
||||
for i, fn := range functions {
|
||||
// Insert a switch case.
|
||||
bb := c.ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||
c.builder.SetInsertPointAtEnd(bb)
|
||||
bb := ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||
builder.SetInsertPointAtEnd(bb)
|
||||
result := createCall(fn.funcPtr, callParams)
|
||||
c.builder.CreateBr(nextBlock)
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
|
||||
builder.CreateBr(nextBlock)
|
||||
sw.AddCase(llvm.ConstInt(uintptrType, uint64(fn.id), false), bb)
|
||||
phiBlocks[i] = bb
|
||||
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
|
||||
// call produced a value.
|
||||
if call.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||
phi := c.builder.CreatePHI(call.Type(), "")
|
||||
builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||
phi := builder.CreatePHI(call.Type(), "")
|
||||
phi.AddIncoming(phiValues, phiBlocks)
|
||||
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
|
||||
}
|
Загрузка…
Создание таблицы
Сослаться в новой задаче