diff --git a/compiler/compiler.go b/compiler/compiler.go index ff592798..de7e8d7c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -500,6 +500,16 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) { members[i] = member } return c.ctx.StructType(members, false), nil + case *types.Tuple: + members := make([]llvm.Type, typ.Len()) + for i := 0; i < typ.Len(); i++ { + member, err := c.getLLVMType(typ.At(i).Type()) + if err != nil { + return llvm.Type{}, err + } + members[i] = member + } + return c.ctx.StructType(members, false), nil default: return llvm.Type{}, errors.New("todo: unknown type: " + goType.String()) } @@ -1821,6 +1831,29 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } c.builder.CreateStore(zero, it) return it, nil + case *ssa.Select: + if len(expr.States) == 0 { + // Shortcuts for some simple selects. + llvmType, err := c.getLLVMType(expr.Type()) + if err != nil { + return llvm.Value{}, err + } + if expr.Blocking { + // Blocks forever: + // select {} + c.createRuntimeCall("deadlockStub", nil, "") + return llvm.Undef(llvmType), nil + } else { + // No-op: + // select { + // default: + // } + retval := llvm.Undef(llvmType) + retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "") + return retval, nil // {-1, false} + } + } + return llvm.Value{}, c.makeError(expr.Pos(), "unimplemented: "+expr.String()) case *ssa.Slice: if expr.Max != nil { return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String()) diff --git a/compiler/goroutine-lowering.go b/compiler/goroutine-lowering.go index 13f0d962..fa498344 100644 --- a/compiler/goroutine-lowering.go +++ b/compiler/goroutine-lowering.go @@ -170,6 +170,10 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { if !sleep.IsNil() { worklist = append(worklist, sleep) } + deadlockStub := c.mod.NamedFunction("runtime.deadlockStub") + if !deadlockStub.IsNil() { + worklist = append(worklist, deadlockStub) + } chanSendStub := c.mod.NamedFunction("runtime.chanSendStub") if !chanSendStub.IsNil() { worklist = append(worklist, chanSendStub) @@ -288,7 +292,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { // Transform all async functions into coroutines. for _, f := range asyncList { - if f == sleep || f == chanSendStub || f == chanRecvStub { + if f == sleep || f == deadlockStub || f == chanSendStub || f == chanRecvStub { continue } @@ -305,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { if !inst.IsACallInst().IsNil() { callee := inst.CalledValue() - if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == chanSendStub || callee == chanRecvStub { + if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSendStub || callee == chanRecvStub { continue } asyncCalls = append(asyncCalls, inst) @@ -439,6 +443,26 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { sleepCall.EraseFromParentAsInstruction() } + // Transform calls to runtime.deadlockStub into coroutine suspends (without + // resume). + for _, deadlockCall := range getUses(deadlockStub) { + // deadlockCall must be a call instruction. + frame := asyncFuncs[deadlockCall.InstructionParent().Parent()] + + // Exit coroutine. + c.builder.SetInsertPointBefore(deadlockCall) + continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ + llvm.ConstNull(c.ctx.TokenType()), + llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend + }, "") + c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead") + c.builder.SetInsertPointBefore(deadlockCall) + sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) + sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock) + sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) + deadlockCall.EraseFromParentAsInstruction() + } + // Transform calls to runtime.chanSendStub into channel send operations. for _, sendOp := range getUses(chanSendStub) { // sendOp must be a call instruction. diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 940861fb..fae7c5c8 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -41,6 +41,7 @@ const ( func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr) +func deadlockStub() // chanSend sends a single value over the channel. If this operation can // complete immediately (there is a goroutine waiting for a value), it sends the diff --git a/testdata/channel.go b/testdata/channel.go index 3777b27a..84025591 100644 --- a/testdata/channel.go +++ b/testdata/channel.go @@ -43,6 +43,10 @@ func main() { } println("sum(100):", sum) + // Test select + go selectDeadlock() + go selectNoOp() + // Allow goroutines to exit. time.Sleep(time.Microsecond) } @@ -93,3 +97,17 @@ func iterator(ch chan int, top int) { } close(ch) } + +func selectDeadlock() { + println("deadlocking") + select {} + println("unreachable") +} + +func selectNoOp() { + println("select no-op") + select { + default: + } + println("after no-op") +} diff --git a/testdata/channel.txt b/testdata/channel.txt index eaffd733..4c26c2f5 100644 --- a/testdata/channel.txt +++ b/testdata/channel.txt @@ -19,3 +19,6 @@ sum: 25 sum: 29 sum: 33 sum(100): 4950 +deadlocking +select no-op +after no-op