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.
This commit is contained in:
Ayke van Laethem
2019-05-04 21:32:00 +02:00
committed by Ron Evans
parent 46d5ea8cf6
commit 9a54ee4241
9 changed files with 112 additions and 130 deletions
+25 -84
View File
@@ -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()