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.
Этот коммит содержится в:
Ayke van Laethem 2019-11-27 00:23:03 +01:00 коммит произвёл Ron Evans
родитель 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 Обычный файл
Просмотреть файл

@ -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 предоставленный Обычный файл
Просмотреть файл

@ -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 предоставленный Обычный файл
Просмотреть файл

@ -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
}