From 8890a0f3c845154d54d1f41a00daf9f626865aa0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 8 Jun 2019 16:12:30 +0200 Subject: [PATCH] compiler,runtime: store channel size in the channel itself This may have a small effect on code size sometimes, but will simplify the implementation of the select statement. --- compiler/channel.go | 28 ++++++++++++++++++---------- src/runtime/chan.go | 19 ++++++++++--------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/compiler/channel.go b/compiler/channel.go index 8098ea99..6265d7f6 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -4,6 +4,7 @@ package compiler // or pseudo-operations that are lowered during goroutine lowering. import ( + "fmt" "go/types" "golang.org/x/tools/go/ssa" @@ -12,11 +13,22 @@ import ( // emitMakeChan returns a new channel value for the given channel type. func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) { - chanType := c.getLLVMType(c.getRuntimeType("channel")) - size := c.targetData.TypeAllocSize(chanType) + chanType := c.getLLVMType(expr.Type()) + size := c.targetData.TypeAllocSize(chanType.ElementType()) sizeValue := llvm.ConstInt(c.uintptrType, size, false) ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc") - ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan") + ptr = c.builder.CreateBitCast(ptr, chanType, "chan") + // Set the elementSize field + elementSizePtr := c.builder.CreateGEP(ptr, []llvm.Value{ + llvm.ConstInt(c.ctx.Int32Type(), 0, false), + llvm.ConstInt(c.ctx.Int32Type(), 0, false), + }, "") + elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem())) + if elementSize > 0xffff { + return ptr, c.makeError(expr.Pos(), fmt.Sprintf("element size is %d bytes, which is bigger than the maximum of %d bytes", elementSize, 0xffff)) + } + elementSizeValue := llvm.ConstInt(c.ctx.Int16Type(), elementSize, false) + c.builder.CreateStore(elementSizeValue, elementSizePtr) return ptr, nil } @@ -33,8 +45,7 @@ func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) { // Do the send. coroutine := c.createRuntimeCall("getCoroutine", nil, "") - valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false) - c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "") + c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "") // End the lifetime of the alloca. // This also works around a bug in CoroSplit, at least in LLVM 8: @@ -53,8 +64,7 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value { // Do the receive. coroutine := c.createRuntimeCall("getCoroutine", nil, "") - valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false) - c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "") + c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "") received := c.builder.CreateLoad(valueAlloca, "chan.received") c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) @@ -72,8 +82,6 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value { // emitChanClose closes the given channel. func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) { - valueType := c.getLLVMType(param.Type().(*types.Chan).Elem()) - valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false) ch := c.getValue(frame, param) - c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "") + c.createRuntimeCall("chanClose", []llvm.Value{ch}, "") } diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 60c69050..d9851df4 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -28,8 +28,9 @@ import ( ) type channel struct { - state uint8 - blocked *coroutine + elementSize uint16 // the size of one value in this channel + state uint8 + blocked *coroutine } const ( @@ -45,7 +46,7 @@ func deadlockStub() // 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. -func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr) { +func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) { if ch == nil { // A nil channel blocks forever. Do not scheduler this goroutine again. return @@ -58,7 +59,7 @@ func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr case chanStateRecv: receiver := ch.blocked receiverPromise := receiver.promise() - memcpy(receiverPromise.ptr, value, size) + memcpy(receiverPromise.ptr, value, uintptr(ch.elementSize)) receiverPromise.data = 1 // commaOk = true ch.blocked = receiverPromise.next receiverPromise.next = nil @@ -80,7 +81,7 @@ func chanSend(sender *coroutine, ch *channel, value 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. -func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintptr) { +func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer) { if ch == nil { // A nil channel blocks forever. Do not scheduler this goroutine again. return @@ -89,7 +90,7 @@ func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintp case chanStateSend: sender := ch.blocked senderPromise := sender.promise() - memcpy(value, senderPromise.ptr, size) + memcpy(value, senderPromise.ptr, uintptr(ch.elementSize)) receiver.promise().data = 1 // commaOk = true ch.blocked = senderPromise.next senderPromise.next = nil @@ -103,7 +104,7 @@ func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintp ch.state = chanStateRecv ch.blocked = receiver case chanStateClosed: - memzero(value, size) + memzero(value, uintptr(ch.elementSize)) receiver.promise().data = 0 // commaOk = false activateTask(receiver) case chanStateRecv: @@ -115,7 +116,7 @@ func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintp // chanClose closes the given channel. If this channel has a receiver or is // empty, it closes the channel. Else, it panics. -func chanClose(ch *channel, size uintptr) { +func chanClose(ch *channel) { if ch == nil { // Not allowed by the language spec. runtimePanic("close of nil channel") @@ -133,7 +134,7 @@ func chanClose(ch *channel, size uintptr) { case chanStateRecv: // The receiver must be re-activated with a zero value. receiverPromise := ch.blocked.promise() - memzero(receiverPromise.ptr, size) + memzero(receiverPromise.ptr, uintptr(ch.elementSize)) receiverPromise.data = 0 // commaOk = false activateTask(ch.blocked) ch.state = chanStateClosed