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.
Этот коммит содержится в:
Ayke van Laethem 2019-05-04 21:32:00 +02:00 коммит произвёл Ron Evans
родитель 46d5ea8cf6
коммит 9a54ee4241
9 изменённых файлов: 112 добавлений и 130 удалений

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

@ -12,13 +12,6 @@ import (
// emitMakeChan returns a new channel value for the given channel type. // emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) { 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") chanType := c.mod.GetTypeByName("runtime.channel")
size := c.targetData.TypeAllocSize(chanType) size := c.targetData.TypeAllocSize(chanType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false) 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 // emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering. // channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) { 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) ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X) chanValue := c.getValue(frame, instr.X)
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false) 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") valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
c.builder.SetInsertPointBefore(coroutine)
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
c.builder.CreateStore(chanValue, valueAlloca) c.builder.CreateStore(chanValue, valueAlloca)
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr") 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 // 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()) valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false) valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch := c.getValue(frame, unop.X) 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") 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") 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") received := c.builder.CreateLoad(valueAlloca, "chan.received")
if unop.CommaOk { 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 := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "") tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")

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

@ -344,11 +344,9 @@ func (c *Compiler) Compile(mainPath string) []error {
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.free").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.sleepTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.setTaskData").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.getTaskData").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage) c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)

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

@ -62,7 +62,7 @@ package compiler
// llvm.suspend(hdl) // suspend point // llvm.suspend(hdl) // suspend point
// println("some other operation") // println("some other operation")
// var i *int // allocate space on the stack for the return value // 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 // bar(hdl) // await, pass a continuation (hdl) to bar
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate // llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done", *i) // println("done", *i)
@ -146,11 +146,9 @@ func (c *Compiler) LowerGoroutines() error {
realMain.SetLinkage(llvm.InternalLinkage) realMain.SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage) c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.free").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.sleepTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.setTaskData").SetLinkage(llvm.InternalLinkage) c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.getTaskData").SetLinkage(llvm.InternalLinkage) c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage) c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
return nil return nil
@ -179,13 +177,13 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
if !deadlockStub.IsNil() { if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub) worklist = append(worklist, deadlockStub)
} }
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub") chanSend := c.mod.NamedFunction("runtime.chanSend")
if !chanSendStub.IsNil() { if !chanSend.IsNil() {
worklist = append(worklist, chanSendStub) worklist = append(worklist, chanSend)
} }
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub") chanRecv := c.mod.NamedFunction("runtime.chanRecv")
if !chanRecvStub.IsNil() { if !chanRecv.IsNil() {
worklist = append(worklist, chanRecvStub) worklist = append(worklist, chanRecv)
} }
if len(worklist) == 0 { 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) coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType) 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) coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType) 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. // Transform all async functions into coroutines.
for _, f := range asyncList { for _, f := range asyncList {
if f == sleep || f == deadlockStub || f == chanSendStub || f == chanRecvStub { if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
continue continue
} }
@ -314,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() { if !inst.IsACallInst().IsNil() {
callee := inst.CalledValue() 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 continue
} }
asyncCalls = append(asyncCalls, inst) asyncCalls = append(asyncCalls, inst)
@ -359,7 +354,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca") retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
c.builder.SetInsertPointBefore(inst) c.builder.SetInsertPointBefore(inst)
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "") data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
c.createRuntimeCall("setTaskData", []llvm.Value{frame.taskHandle, data}, "") c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
} }
// Suspend. // Suspend.
@ -404,7 +399,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Return this value by writing to the pointer stored in the // Return this value by writing to the pointer stored in the
// parent handle. The parent coroutine has made an alloca that // parent handle. The parent coroutine has made an alloca that
// we can write to to store our return value. // 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") alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
c.builder.CreateStore(inst.Operand(0), alloca) c.builder.CreateStore(inst.Operand(0), alloca)
default: default:
@ -452,6 +447,14 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
c.builder.CreateUnreachable() 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. // Transform calls to time.Sleep() into coroutine suspend points.
for _, sleepCall := range getUses(sleep) { for _, sleepCall := range getUses(sleep) {
// sleepCall must be a call instruction. // sleepCall must be a call instruction.
@ -495,37 +498,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
deadlockCall.EraseFromParentAsInstruction() deadlockCall.EraseFromParentAsInstruction()
} }
// Transform calls to runtime.chanSendStub into channel send operations. // Transform calls to runtime.chanSend into channel send operations.
for _, sendOp := range getUses(chanSendStub) { for _, sendOp := range getUses(chanSend) {
// sendOp must be a call instruction. // sendOp must be a call instruction.
frame := asyncFuncs[sendOp.InstructionParent().Parent()] 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. // Yield to scheduler.
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp)) c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ 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) sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
} }
// Transform calls to runtime.chanRecvStub into channel receive operations. // Transform calls to runtime.chanRecv into channel receive operations.
for _, recvOp := range getUses(chanRecvStub) { for _, recvOp := range getUses(chanRecv) {
// recvOp must be a call instruction. // recvOp must be a call instruction.
frame := asyncFuncs[recvOp.InstructionParent().Parent()] 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. // Yield to scheduler.
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp)) c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
@ -564,32 +531,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent()) c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup) sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) 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() return true, c.lowerMakeGoroutineCalls()

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

@ -22,6 +22,17 @@ func getUses(value llvm.Value) []llvm.Value {
return uses 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 // splitBasicBlock splits a LLVM basic block into two parts. All instructions
// after afterInst are moved into a new basic block (created right after the // after afterInst are moved into a new basic block (created right after the
// current one) with the given name. // current one) with the given name.

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

@ -113,6 +113,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
if k == types.Uintptr { if k == types.Uintptr {
return s.PtrSize return s.PtrSize
} }
if k == types.UnsafePointer {
return s.PtrSize
}
panic("unknown basic type: " + t.String()) panic("unknown basic type: " + t.String())
case *types.Array: case *types.Array:
n := t.Len() n := t.Len()

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

@ -39,33 +39,27 @@ const (
chanStateClosed chanStateClosed
) )
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
func deadlockStub() func deadlockStub()
// chanSend sends a single value over the channel. If this operation can // 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 // 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 // value and re-activates both goroutines. If not, it sets itself as waiting on
// a value. // a value.
// func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
// 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) {
if ch == nil { if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again. // A nil channel blocks forever. Do not scheduler this goroutine again.
return return
} }
switch ch.state { switch ch.state {
case chanStateEmpty: case chanStateEmpty:
sender.promise().ptr = value
ch.state = chanStateSend ch.state = chanStateSend
ch.blocked = sender ch.blocked = sender
case chanStateRecv: case chanStateRecv:
receiver := ch.blocked receiver := ch.blocked
receiverPromise := receiver.promise() receiverPromise := receiver.promise()
senderPromise := sender.promise() memcpy(receiverPromise.ptr, value, size)
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size) receiverPromise.data = 1 // commaOk = true
receiverPromise.commaOk = true
ch.blocked = receiverPromise.next ch.blocked = receiverPromise.next
receiverPromise.next = nil receiverPromise.next = nil
activateTask(receiver) activateTask(receiver)
@ -76,6 +70,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
case chanStateClosed: case chanStateClosed:
runtimePanic("send on closed channel") runtimePanic("send on closed channel")
case chanStateSend: case chanStateSend:
sender.promise().ptr = value
sender.promise().next = ch.blocked sender.promise().next = ch.blocked
ch.blocked = sender 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. // 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, // 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. // it immediately activates itself with a zero value as the result.
// func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
// 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) {
if ch == nil { if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again. // A nil channel blocks forever. Do not scheduler this goroutine again.
return return
@ -97,10 +88,9 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
switch ch.state { switch ch.state {
case chanStateSend: case chanStateSend:
sender := ch.blocked sender := ch.blocked
receiverPromise := receiver.promise()
senderPromise := sender.promise() senderPromise := sender.promise()
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size) memcpy(value, senderPromise.ptr, size)
receiverPromise.commaOk = true receiver.promise().data = 1 // commaOk = true
ch.blocked = senderPromise.next ch.blocked = senderPromise.next
senderPromise.next = nil senderPromise.next = nil
activateTask(receiver) activateTask(receiver)
@ -109,14 +99,15 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
ch.state = chanStateEmpty ch.state = chanStateEmpty
} }
case chanStateEmpty: case chanStateEmpty:
receiver.promise().ptr = value
ch.state = chanStateRecv ch.state = chanStateRecv
ch.blocked = receiver ch.blocked = receiver
case chanStateClosed: case chanStateClosed:
receiverPromise := receiver.promise() memzero(value, size)
memzero(unsafe.Pointer(&receiverPromise.data), size) receiver.promise().data = 0 // commaOk = false
receiverPromise.commaOk = false
activateTask(receiver) activateTask(receiver)
case chanStateRecv: case chanStateRecv:
receiver.promise().ptr = value
receiver.promise().next = ch.blocked receiver.promise().next = ch.blocked
ch.blocked = receiver ch.blocked = receiver
} }
@ -142,8 +133,8 @@ func chanClose(ch *channel, size uintptr) {
case chanStateRecv: case chanStateRecv:
// The receiver must be re-activated with a zero value. // The receiver must be re-activated with a zero value.
receiverPromise := ch.blocked.promise() receiverPromise := ch.blocked.promise()
memzero(unsafe.Pointer(&receiverPromise.data), size) memzero(receiverPromise.ptr, size)
receiverPromise.commaOk = false receiverPromise.data = 0 // commaOk = false
activateTask(ch.blocked) activateTask(ch.blocked)
ch.state = chanStateClosed ch.state = chanStateClosed
ch.blocked = nil ch.blocked = nil

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

@ -49,12 +49,16 @@ func (t *coroutine) promise() *taskState {
func makeGoroutine(*uint8) *uint8 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: // State/promise of a task. Internally represented as:
// //
// {i8* next, i1 commaOk, i32/i64 data} // {i8* next, i1 commaOk, i32/i64 data}
type taskState struct { type taskState struct {
next *coroutine next *coroutine
commaOk bool // 'comma-ok' flag for channel receive operation ptr unsafe.Pointer
data uint data uint
} }
@ -107,12 +111,22 @@ func activateTask(task *coroutine) {
runqueuePushBack(task) runqueuePushBack(task)
} }
func setTaskData(task *coroutine, value unsafe.Pointer) { // getTaskPromisePtr is a helper function to set the current .ptr field of a
task.promise().data = uint(uintptr(value)) // coroutine promise.
func setTaskPromisePtr(task *coroutine, value unsafe.Pointer) {
task.promise().ptr = value
} }
func getTaskData(task *coroutine) unsafe.Pointer { // getTaskPromisePtr is a helper function to get the current .ptr field from a
return unsafe.Pointer(uintptr(task.promise().data)) // 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 // Add this task to the end of the run queue. May also destroy the task if it's

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

@ -20,6 +20,11 @@ func main() {
n, ok = <-ch n, ok = <-ch
println("recv from closed channel:", n, ok) println("recv from closed channel:", n, ok)
// Test bigger values
ch2 := make(chan complex128)
go sendComplex(ch2)
println("complex128:", <-ch2)
// Test multi-sender. // Test multi-sender.
ch = make(chan int) ch = make(chan int)
go fastsender(ch) go fastsender(ch)
@ -62,6 +67,10 @@ func sender(ch chan int) {
close(ch) close(ch)
} }
func sendComplex(ch chan complex128) {
ch <- 7+10.5i
}
func fastsender(ch chan int) { func fastsender(ch chan int) {
ch <- 10 ch <- 10
ch <- 11 ch <- 11

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

@ -9,6 +9,7 @@ received num: 6
received num: 7 received num: 7
received num: 8 received num: 8
recv from closed channel: 0 false recv from closed channel: 0 false
complex128: (+7.000000e+000+1.050000e+001i)
got n: 10 got n: 10
got n: 11 got n: 11
got n: 10 got n: 10