aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--compiler/goroutine-lowering.go2
-rw-r--r--compiler/llvm.go72
-rw-r--r--compiler/llvmutil/llvm.go72
-rw-r--r--compiler/optimizer.go8
-rw-r--r--transform/func-lowering.go (renamed from compiler/func-lowering.go)92
-rw-r--r--transform/func-lowering_test.go10
-rw-r--r--transform/testdata/func-lowering.ll83
-rw-r--r--transform/testdata/func-lowering.out.ll121
8 files changed, 346 insertions, 114 deletions
diff --git a/compiler/goroutine-lowering.go b/compiler/goroutine-lowering.go
index 7ebb194fe..56da7e1f4 100644
--- a/compiler/goroutine-lowering.go
+++ b/compiler/goroutine-lowering.go
@@ -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)
diff --git a/compiler/llvm.go b/compiler/llvm.go
index 078956657..53d52bbae 100644
--- a/compiler/llvm.go
+++ b/compiler/llvm.go
@@ -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
diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go
index ad2d28717..21bc6c67f 100644
--- a/compiler/llvmutil/llvm.go
+++ b/compiler/llvmutil/llvm.go
@@ -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
+}
diff --git a/compiler/optimizer.go b/compiler/optimizer.go
index 601c6eef1..70084c984 100644
--- a/compiler/optimizer.go
+++ b/compiler/optimizer.go
@@ -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
diff --git a/compiler/func-lowering.go b/transform/func-lowering.go
index a3123b59d..fa6eb0e41 100644
--- a/compiler/func-lowering.go
+++ b/transform/func-lowering.go
@@ -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)
}
diff --git a/transform/func-lowering_test.go b/transform/func-lowering_test.go
new file mode 100644
index 000000000..ee3d019dd
--- /dev/null
+++ b/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)
+}
diff --git a/transform/testdata/func-lowering.ll b/transform/testdata/func-lowering.ll
new file mode 100644
index 000000000..8bcc0a406
--- /dev/null
+++ b/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
+}
+
diff --git a/transform/testdata/func-lowering.out.ll b/transform/testdata/func-lowering.out.ll
new file mode 100644
index 000000000..01d668359
--- /dev/null
+++ b/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
+}