all: implement trivial select statements

Implement two trivial uses of the select statement.

Always blocking:
    select {}

No-op:
    select {
    default:
    }

Go 1.12 added a `select {}` instruction to syscall/js, so this is needed
for Go 1.12 support. More complete support for select will be added in
the future.
Этот коммит содержится в:
Ayke van Laethem 2019-02-28 14:11:46 +01:00 коммит произвёл Ron Evans
родитель 4d82f42d61
коммит ad7297a539
5 изменённых файлов: 81 добавлений и 2 удалений

Просмотреть файл

@ -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())

Просмотреть файл

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

Просмотреть файл

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

18
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")
}

3
testdata/channel.txt предоставленный
Просмотреть файл

@ -19,3 +19,6 @@ sum: 25
sum: 29
sum: 33
sum(100): 4950
deadlocking
select no-op
after no-op