From 9a54ee4241a1ec252368eb7e7576a5b41723beec Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 4 May 2019 21:32:00 +0200 Subject: [PATCH] compiler: allow larger-than-int values to be sent across a channel Instead of storing the value to send/receive in the coroutine promise, store only a pointer in the promise. This simplifies the code a lot and allows larger value sizes to be sent across a channel. Unfortunately, this new system has a code size impact. For example, compiling testdata/channel.go for the BBC micro:bit, there is an increase in code size from 4776 bytes to 4856 bytes. However, the improved flexibility and simplicity of the code should be worth it. If this becomes an issue, we can always refactor the code at a later time. --- compiler/channel.go | 38 ++++++++---- compiler/compiler.go | 6 +- compiler/goroutine-lowering.go | 109 ++++++++------------------------- compiler/llvm.go | 11 ++++ compiler/sizes.go | 3 + src/runtime/chan.go | 37 +++++------ src/runtime/scheduler.go | 28 ++++++--- testdata/channel.go | 9 +++ testdata/channel.txt | 1 + 9 files changed, 112 insertions(+), 130 deletions(-) diff --git a/compiler/channel.go b/compiler/channel.go index d9ab1b84..8f1a8603 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -12,13 +12,6 @@ import ( // emitMakeChan returns a new channel value for the given channel type. func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) { - valueType := c.getLLVMType(expr.Type().(*types.Chan).Elem()) - if c.targetData.TypeAllocSize(valueType) > c.targetData.TypeAllocSize(c.intType) { - // Values bigger than int overflow the data part of the coroutine. - // TODO: make the coroutine data part big enough to hold these bigger - // values. - return llvm.Value{}, c.makeError(expr.Pos(), "todo: channel with values bigger than int") - } chanType := c.mod.GetTypeByName("runtime.channel") size := c.targetData.TypeAllocSize(chanType) sizeValue := llvm.ConstInt(c.uintptrType, size, false) @@ -30,14 +23,27 @@ func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) { // emitChanSend emits a pseudo chan send operation. It is lowered to the actual // channel send operation during goroutine lowering. func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) { - valueType := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem()) + valueType := c.getLLVMType(instr.X.Type()) ch := c.getValue(frame, instr.Chan) chanValue := c.getValue(frame, instr.X) valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false) + coroutine := c.createRuntimeCall("getCoroutine", nil, "") + + // store value-to-send + c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction()) valueAlloca := c.builder.CreateAlloca(valueType, "chan.value") + c.builder.SetInsertPointBefore(coroutine) + c.builder.SetInsertPointAtEnd(coroutine.InstructionParent()) c.builder.CreateStore(chanValue, valueAlloca) valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr") - c.createRuntimeCall("chanSendStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueSize}, "") + + // Do the send. + c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "") + + // Make sure CoroSplit includes the alloca in the coroutine frame. + // This is a bit dirty, but it works (at least in LLVM 8). + valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false) + c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "") } // emitChanRecv emits a pseudo chan receive operation. It is lowered to the @@ -46,13 +52,21 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value { valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem()) valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false) ch := c.getValue(frame, unop.X) + coroutine := c.createRuntimeCall("getCoroutine", nil, "") + + // Allocate memory to receive into. + c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction()) valueAlloca := c.builder.CreateAlloca(valueType, "chan.value") + c.builder.SetInsertPointBefore(coroutine) + c.builder.SetInsertPointAtEnd(coroutine.InstructionParent()) valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr") - valueOk := c.builder.CreateAlloca(c.ctx.Int1Type(), "chan.comma-ok.alloca") - c.createRuntimeCall("chanRecvStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueOk, valueSize}, "") + + // Do the receive. + c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "") received := c.builder.CreateLoad(valueAlloca, "chan.received") if unop.CommaOk { - commaOk := c.builder.CreateLoad(valueOk, "chan.comma-ok") + commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide") + commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk") tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false)) tuple = c.builder.CreateInsertValue(tuple, received, 0, "") tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") diff --git a/compiler/compiler.go b/compiler/compiler.go index 044d0b8b..70951c7a 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -344,11 +344,9 @@ func (c *Compiler) Compile(mainPath string) []error { realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.free").SetLinkage(llvm.ExternalLinkage) - c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.ExternalLinkage) - c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.ExternalLinkage) - c.mod.NamedFunction("runtime.setTaskData").SetLinkage(llvm.ExternalLinkage) - c.mod.NamedFunction("runtime.getTaskData").SetLinkage(llvm.ExternalLinkage) + c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.ExternalLinkage) + c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage) diff --git a/compiler/goroutine-lowering.go b/compiler/goroutine-lowering.go index 3a03b85a..2a9276df 100644 --- a/compiler/goroutine-lowering.go +++ b/compiler/goroutine-lowering.go @@ -62,7 +62,7 @@ package compiler // llvm.suspend(hdl) // suspend point // println("some other operation") // var i *int // allocate space on the stack for the return value -// runtime.setTaskData(hdl, &i) // store return value alloca in our coroutine promise +// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise // bar(hdl) // await, pass a continuation (hdl) to bar // llvm.suspend(hdl) // suspend point, wait for the callee to re-activate // println("done", *i) @@ -146,11 +146,9 @@ func (c *Compiler) LowerGoroutines() error { realMain.SetLinkage(llvm.InternalLinkage) c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage) c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage) - c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage) - c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.InternalLinkage) c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage) - c.mod.NamedFunction("runtime.setTaskData").SetLinkage(llvm.InternalLinkage) - c.mod.NamedFunction("runtime.getTaskData").SetLinkage(llvm.InternalLinkage) + c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage) + c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage) c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage) return nil @@ -179,13 +177,13 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { if !deadlockStub.IsNil() { worklist = append(worklist, deadlockStub) } - chanSendStub := c.mod.NamedFunction("runtime.chanSendStub") - if !chanSendStub.IsNil() { - worklist = append(worklist, chanSendStub) + chanSend := c.mod.NamedFunction("runtime.chanSend") + if !chanSend.IsNil() { + worklist = append(worklist, chanSend) } - chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub") - if !chanRecvStub.IsNil() { - worklist = append(worklist, chanRecvStub) + chanRecv := c.mod.NamedFunction("runtime.chanRecv") + if !chanRecv.IsNil() { + worklist = append(worklist, chanRecv) } if len(worklist) == 0 { @@ -283,9 +281,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false) coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType) - coroPromiseType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.i8ptrType, c.ctx.Int32Type(), c.ctx.Int1Type()}, false) - coroPromiseFunc := llvm.AddFunction(c.mod, "llvm.coro.promise", coroPromiseType) - coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false) coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType) @@ -297,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 == deadlockStub || f == chanSendStub || f == chanRecvStub { + if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv { continue } @@ -314,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 == deadlockStub || callee == chanSendStub || callee == chanRecvStub { + if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv { continue } asyncCalls = append(asyncCalls, inst) @@ -359,7 +354,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca") c.builder.SetInsertPointBefore(inst) data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "") - c.createRuntimeCall("setTaskData", []llvm.Value{frame.taskHandle, data}, "") + c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "") } // Suspend. @@ -404,7 +399,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { // Return this value by writing to the pointer stored in the // parent handle. The parent coroutine has made an alloca that // we can write to to store our return value. - returnValuePtr := c.createRuntimeCall("getTaskData", []llvm.Value{parentHandle}, "coro.parentData") + returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData") alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca") c.builder.CreateStore(inst.Operand(0), alloca) default: @@ -452,6 +447,14 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { c.builder.CreateUnreachable() } + // Replace calls to runtime.getCoroutineCall with the coroutine of this + // frame. + for _, getCoroutineCall := range getUses(c.mod.NamedFunction("runtime.getCoroutine")) { + frame := asyncFuncs[getCoroutineCall.InstructionParent().Parent()] + getCoroutineCall.ReplaceAllUsesWith(frame.taskHandle) + getCoroutineCall.EraseFromParentAsInstruction() + } + // Transform calls to time.Sleep() into coroutine suspend points. for _, sleepCall := range getUses(sleep) { // sleepCall must be a call instruction. @@ -495,37 +498,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { deadlockCall.EraseFromParentAsInstruction() } - // Transform calls to runtime.chanSendStub into channel send operations. - for _, sendOp := range getUses(chanSendStub) { + // Transform calls to runtime.chanSend into channel send operations. + for _, sendOp := range getUses(chanSend) { // sendOp must be a call instruction. frame := asyncFuncs[sendOp.InstructionParent().Parent()] - // Send the value over the channel, or block. - sendOp.SetOperand(0, frame.taskHandle) - sendOp.SetOperand(sendOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanSend")) - - // Use taskState.data to store the value to send: - // *(*valueType)(&coroutine.promise().data) = valueToSend - // runtime.chanSend(coroutine, ch) - bitcast := sendOp.Operand(2) - valueAlloca := bitcast.Operand(0) - c.builder.SetInsertPointBefore(valueAlloca) - promiseType := c.mod.GetTypeByName("runtime.taskState") - promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{ - frame.taskHandle, - llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false), - llvm.ConstInt(c.ctx.Int1Type(), 0, false), - }, "task.promise.raw") - promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise") - dataPtr := c.builder.CreateInBoundsGEP(promise, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 2, false), - }, "task.promise.data") - sendOp.SetOperand(2, llvm.Undef(c.i8ptrType)) - valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), "")) - bitcast.EraseFromParentAsInstruction() - valueAlloca.EraseFromParentAsInstruction() - // Yield to scheduler. c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp)) continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ @@ -538,21 +515,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) } - // Transform calls to runtime.chanRecvStub into channel receive operations. - for _, recvOp := range getUses(chanRecvStub) { + // Transform calls to runtime.chanRecv into channel receive operations. + for _, recvOp := range getUses(chanRecv) { // recvOp must be a call instruction. frame := asyncFuncs[recvOp.InstructionParent().Parent()] - bitcast := recvOp.Operand(2) - commaOk := recvOp.Operand(3) - valueAlloca := bitcast.Operand(0) - - // Receive the value over the channel, or block. - recvOp.SetOperand(0, frame.taskHandle) - recvOp.SetOperand(recvOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanRecv")) - recvOp.SetOperand(2, llvm.Undef(c.i8ptrType)) - bitcast.EraseFromParentAsInstruction() - // Yield to scheduler. c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp)) continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ @@ -564,32 +531,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { c.builder.SetInsertPointAtEnd(recvOp.InstructionParent()) sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup) sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) - - // The value to receive is stored in taskState.data: - // runtime.chanRecv(coroutine, ch) - // promise := coroutine.promise() - // valueReceived := *(*valueType)(&promise.data) - // ok := promise.commaOk - c.builder.SetInsertPointBefore(wakeup.FirstInstruction()) - promiseType := c.mod.GetTypeByName("runtime.taskState") - promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{ - frame.taskHandle, - llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false), - llvm.ConstInt(c.ctx.Int1Type(), 0, false), - }, "task.promise.raw") - promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise") - dataPtr := c.builder.CreateInBoundsGEP(promise, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 2, false), - }, "task.promise.data") - valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), "")) - valueAlloca.EraseFromParentAsInstruction() - commaOkPtr := c.builder.CreateInBoundsGEP(promise, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 1, false), - }, "task.promise.comma-ok") - commaOk.ReplaceAllUsesWith(commaOkPtr) - recvOp.SetOperand(3, llvm.Undef(commaOk.Type())) } return true, c.lowerMakeGoroutineCalls() diff --git a/compiler/llvm.go b/compiler/llvm.go index ce52bcd0..77fc9c44 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -22,6 +22,17 @@ func getUses(value llvm.Value) []llvm.Value { return uses } +// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it +// first if it doesn't exist yet. +func (c *Compiler) getLifetimeEndFunc() llvm.Value { + fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8") + if fn.IsNil() { + fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false) + fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType) + } + 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. diff --git a/compiler/sizes.go b/compiler/sizes.go index 0d28712e..6d212da0 100644 --- a/compiler/sizes.go +++ b/compiler/sizes.go @@ -113,6 +113,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 { if k == types.Uintptr { return s.PtrSize } + if k == types.UnsafePointer { + return s.PtrSize + } panic("unknown basic type: " + t.String()) case *types.Array: n := t.Len() diff --git a/src/runtime/chan.go b/src/runtime/chan.go index fae7c5c8..60c69050 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -39,33 +39,27 @@ const ( chanStateClosed ) -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 // value and re-activates both goroutines. If not, it sets itself as waiting on // a value. -// -// The unsafe.Pointer value is used during lowering. During IR generation, it -// points to the to-be-received value. During coroutine lowering, this value is -// replaced with a read from the coroutine promise. -func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) { +func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr) { if ch == nil { // A nil channel blocks forever. Do not scheduler this goroutine again. return } switch ch.state { case chanStateEmpty: + sender.promise().ptr = value ch.state = chanStateSend ch.blocked = sender case chanStateRecv: receiver := ch.blocked receiverPromise := receiver.promise() - senderPromise := sender.promise() - memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size) - receiverPromise.commaOk = true + memcpy(receiverPromise.ptr, value, size) + receiverPromise.data = 1 // commaOk = true ch.blocked = receiverPromise.next receiverPromise.next = nil activateTask(receiver) @@ -76,6 +70,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) { case chanStateClosed: runtimePanic("send on closed channel") case chanStateSend: + sender.promise().ptr = value sender.promise().next = ch.blocked ch.blocked = sender } @@ -85,11 +80,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) { // sender, it receives the value immediately and re-activates both coroutines. // If not, it sets itself as available for receiving. If the channel is closed, // it immediately activates itself with a zero value as the result. -// -// The two unnamed values exist to help during lowering. The unsafe.Pointer -// points to the value, and the *bool points to the comma-ok value. Both are -// replaced by reads from the coroutine promise. -func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr) { +func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintptr) { if ch == nil { // A nil channel blocks forever. Do not scheduler this goroutine again. return @@ -97,10 +88,9 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size switch ch.state { case chanStateSend: sender := ch.blocked - receiverPromise := receiver.promise() senderPromise := sender.promise() - memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size) - receiverPromise.commaOk = true + memcpy(value, senderPromise.ptr, size) + receiver.promise().data = 1 // commaOk = true ch.blocked = senderPromise.next senderPromise.next = nil activateTask(receiver) @@ -109,14 +99,15 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size ch.state = chanStateEmpty } case chanStateEmpty: + receiver.promise().ptr = value ch.state = chanStateRecv ch.blocked = receiver case chanStateClosed: - receiverPromise := receiver.promise() - memzero(unsafe.Pointer(&receiverPromise.data), size) - receiverPromise.commaOk = false + memzero(value, size) + receiver.promise().data = 0 // commaOk = false activateTask(receiver) case chanStateRecv: + receiver.promise().ptr = value receiver.promise().next = ch.blocked ch.blocked = receiver } @@ -142,8 +133,8 @@ func chanClose(ch *channel, size uintptr) { case chanStateRecv: // The receiver must be re-activated with a zero value. receiverPromise := ch.blocked.promise() - memzero(unsafe.Pointer(&receiverPromise.data), size) - receiverPromise.commaOk = false + memzero(receiverPromise.ptr, size) + receiverPromise.data = 0 // commaOk = false activateTask(ch.blocked) ch.state = chanStateClosed ch.blocked = nil diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 2503e18d..b4af2360 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -49,13 +49,17 @@ func (t *coroutine) promise() *taskState { func makeGoroutine(*uint8) *uint8 +// Compiler stub to get the current goroutine. Calls to this function are +// removed in the goroutine lowering pass. +func getCoroutine() *coroutine + // State/promise of a task. Internally represented as: // // {i8* next, i1 commaOk, i32/i64 data} type taskState struct { - next *coroutine - commaOk bool // 'comma-ok' flag for channel receive operation - data uint + next *coroutine + ptr unsafe.Pointer + data uint } // Queues used by the scheduler. @@ -107,12 +111,22 @@ func activateTask(task *coroutine) { runqueuePushBack(task) } -func setTaskData(task *coroutine, value unsafe.Pointer) { - task.promise().data = uint(uintptr(value)) +// getTaskPromisePtr is a helper function to set the current .ptr field of a +// coroutine promise. +func setTaskPromisePtr(task *coroutine, value unsafe.Pointer) { + task.promise().ptr = value } -func getTaskData(task *coroutine) unsafe.Pointer { - return unsafe.Pointer(uintptr(task.promise().data)) +// getTaskPromisePtr is a helper function to get the current .ptr field from a +// coroutine promise. +func getTaskPromisePtr(task *coroutine) unsafe.Pointer { + return task.promise().ptr +} + +// getTaskPromiseData is a helper function to get the current .data field of a +// coroutine promise. +func getTaskPromiseData(task *coroutine) uint { + return task.promise().data } // Add this task to the end of the run queue. May also destroy the task if it's diff --git a/testdata/channel.go b/testdata/channel.go index 84025591..e3b8aed3 100644 --- a/testdata/channel.go +++ b/testdata/channel.go @@ -20,6 +20,11 @@ func main() { n, ok = <-ch println("recv from closed channel:", n, ok) + // Test bigger values + ch2 := make(chan complex128) + go sendComplex(ch2) + println("complex128:", <-ch2) + // Test multi-sender. ch = make(chan int) go fastsender(ch) @@ -62,6 +67,10 @@ func sender(ch chan int) { close(ch) } +func sendComplex(ch chan complex128) { + ch <- 7+10.5i +} + func fastsender(ch chan int) { ch <- 10 ch <- 11 diff --git a/testdata/channel.txt b/testdata/channel.txt index 4c26c2f5..0d11d4e6 100644 --- a/testdata/channel.txt +++ b/testdata/channel.txt @@ -9,6 +9,7 @@ received num: 6 received num: 7 received num: 8 recv from closed channel: 0 false +complex128: (+7.000000e+000+1.050000e+001i) got n: 10 got n: 11 got n: 10