diff options
-rw-r--r-- | compiler/goroutine-lowering.go | 2 | ||||
-rw-r--r-- | compiler/llvm.go | 72 | ||||
-rw-r--r-- | compiler/llvmutil/llvm.go | 72 | ||||
-rw-r--r-- | compiler/optimizer.go | 8 | ||||
-rw-r--r-- | transform/func-lowering.go (renamed from compiler/func-lowering.go) | 92 | ||||
-rw-r--r-- | transform/func-lowering_test.go | 10 | ||||
-rw-r--r-- | transform/testdata/func-lowering.ll | 83 | ||||
-rw-r--r-- | transform/testdata/func-lowering.out.ll | 121 |
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 +} |