compiler: support returning values from async functions
This is implemented as follows: * The parent coroutine allocates space for the return value in its frame and stores a pointer to this frame in the parent coroutine handle. * The child coroutine obtains the alloca from its parent using the parent coroutine handle. It then stores the result value there. * The parent value reads the data from the alloca on resumption.
Этот коммит содержится в:
родитель
fb952a722a
коммит
46d5ea8cf6
5 изменённых файлов: 93 добавлений и 42 удалений
|
@ -347,6 +347,8 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||||
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.ExternalLinkage)
|
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.ExternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.chanRecv").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.getTaskData").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)
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,8 @@ package compiler
|
||||||
// go foo()
|
// go foo()
|
||||||
// time.Sleep(2 * time.Second)
|
// time.Sleep(2 * time.Second)
|
||||||
// println("some other operation")
|
// println("some other operation")
|
||||||
// bar()
|
// i := bar()
|
||||||
// println("done")
|
// println("done", *i)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func foo() {
|
// func foo() {
|
||||||
|
@ -21,9 +21,10 @@ package compiler
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func bar() {
|
// func bar() *int {
|
||||||
// time.Sleep(time.Second)
|
// time.Sleep(time.Second)
|
||||||
// println("blocking operation completed)
|
// println("blocking operation completed)
|
||||||
|
// return new(int)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// It is transformed by the IR generator in compiler.go into the following
|
// It is transformed by the IR generator in compiler.go into the following
|
||||||
|
@ -34,8 +35,8 @@ package compiler
|
||||||
// fn()
|
// fn()
|
||||||
// time.Sleep(2 * time.Second)
|
// time.Sleep(2 * time.Second)
|
||||||
// println("some other operation")
|
// println("some other operation")
|
||||||
// bar() // imagine an 'await' keyword in front of this call
|
// i := bar() // imagine an 'await' keyword in front of this call
|
||||||
// println("done")
|
// println("done", *i)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func foo() {
|
// func foo() {
|
||||||
|
@ -45,9 +46,10 @@ package compiler
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func bar() {
|
// func bar() *int {
|
||||||
// time.Sleep(time.Second)
|
// time.Sleep(time.Second)
|
||||||
// println("blocking operation completed)
|
// println("blocking operation completed)
|
||||||
|
// return new(int)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// The pass in this file transforms this code even further, to the following
|
// The pass in this file transforms this code even further, to the following
|
||||||
|
@ -59,9 +61,11 @@ package compiler
|
||||||
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||||
// 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
|
||||||
|
// runtime.setTaskData(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")
|
// println("done", *i)
|
||||||
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
@ -145,7 +149,8 @@ func (c *Compiler) LowerGoroutines() error {
|
||||||
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.chanRecv").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.activateTask").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.setTaskData").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.getTaskData").SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -347,10 +352,18 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||||
// Split this basic block.
|
// Split this basic block.
|
||||||
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
||||||
|
|
||||||
// Set task state to TASK_STATE_CALL.
|
// Allocate space for the return value.
|
||||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
var retvalAlloca llvm.Value
|
||||||
|
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
c.builder.SetInsertPointBefore(inst.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
|
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}, "")
|
||||||
|
}
|
||||||
|
|
||||||
// Suspend.
|
// Suspend.
|
||||||
|
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
llvm.ConstNull(c.ctx.TokenType()),
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||||
|
@ -358,27 +371,49 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
|
||||||
|
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
// Load the return value from the alloca. The callee has
|
||||||
|
// written the return value to it.
|
||||||
|
c.builder.SetInsertPointBefore(await.FirstInstruction())
|
||||||
|
retval := c.builder.CreateLoad(retvalAlloca, "coro.retval")
|
||||||
|
inst.ReplaceAllUsesWith(retval)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace return instructions with suspend points that should
|
// Replace return instructions with suspend points that should
|
||||||
// reactivate the parent coroutine.
|
// reactivate the parent coroutine.
|
||||||
for _, inst := range returns {
|
for _, inst := range returns {
|
||||||
if inst.OperandsCount() == 0 {
|
// These properties were added by the functionattrs pass. Remove
|
||||||
// These properties were added by the functionattrs pass.
|
// them, because now we start using the parameter.
|
||||||
// Remove them, because now we start using the parameter.
|
|
||||||
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||||
for _, kind := range []string{"nocapture", "readnone"} {
|
for _, kind := range []string{"nocapture", "readnone"} {
|
||||||
kindID := llvm.AttributeKindID(kind)
|
kindID := llvm.AttributeKindID(kind)
|
||||||
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reactivate the parent coroutine. This adds it back to
|
|
||||||
// the run queue, so it is started again by the
|
|
||||||
// scheduler when possible (possibly right after the
|
|
||||||
// following suspend).
|
|
||||||
c.builder.SetInsertPointBefore(inst)
|
c.builder.SetInsertPointBefore(inst)
|
||||||
|
|
||||||
parentHandle := f.LastParam()
|
parentHandle := f.LastParam()
|
||||||
|
|
||||||
|
// Store return values.
|
||||||
|
switch inst.OperandsCount() {
|
||||||
|
case 0:
|
||||||
|
// Nothing to return.
|
||||||
|
case 1:
|
||||||
|
// 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")
|
||||||
|
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
|
||||||
|
c.builder.CreateStore(inst.Operand(0), alloca)
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactivate the parent coroutine. This adds it back to the run
|
||||||
|
// queue, so it is started again by the scheduler when possible
|
||||||
|
// (possibly right after the following suspend).
|
||||||
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||||
|
|
||||||
// Suspend this coroutine.
|
// Suspend this coroutine.
|
||||||
|
@ -393,9 +428,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
inst.EraseFromParentAsInstruction()
|
inst.EraseFromParentAsInstruction()
|
||||||
} else {
|
|
||||||
panic("todo: return value from coroutine")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coroutine cleanup. Free resources associated with this coroutine.
|
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||||
|
|
|
@ -107,6 +107,14 @@ func activateTask(task *coroutine) {
|
||||||
runqueuePushBack(task)
|
runqueuePushBack(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setTaskData(task *coroutine, value unsafe.Pointer) {
|
||||||
|
task.promise().data = uint(uintptr(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTaskData(task *coroutine) unsafe.Pointer {
|
||||||
|
return unsafe.Pointer(uintptr(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
|
||||||
// done.
|
// done.
|
||||||
func runqueuePushBack(t *coroutine) {
|
func runqueuePushBack(t *coroutine) {
|
||||||
|
|
8
testdata/coroutines.go
предоставленный
8
testdata/coroutines.go
предоставленный
|
@ -15,6 +15,9 @@ func main() {
|
||||||
wait()
|
wait()
|
||||||
println("end waiting")
|
println("end waiting")
|
||||||
|
|
||||||
|
value := delayedValue()
|
||||||
|
println("value produced after some time:", value)
|
||||||
|
|
||||||
// Run a non-blocking call in a goroutine. This should be turned into a
|
// Run a non-blocking call in a goroutine. This should be turned into a
|
||||||
// regular call, so should be equivalent to calling nowait() without 'go'
|
// regular call, so should be equivalent to calling nowait() without 'go'
|
||||||
// prefix.
|
// prefix.
|
||||||
|
@ -39,6 +42,11 @@ func wait() {
|
||||||
println(" wait end")
|
println(" wait end")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func delayedValue() int {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
|
||||||
func nowait() {
|
func nowait() {
|
||||||
println("non-blocking goroutine")
|
println("non-blocking goroutine")
|
||||||
}
|
}
|
||||||
|
|
1
testdata/coroutines.txt
предоставленный
1
testdata/coroutines.txt
предоставленный
|
@ -7,6 +7,7 @@ wait:
|
||||||
wait start
|
wait start
|
||||||
wait end
|
wait end
|
||||||
end waiting
|
end waiting
|
||||||
|
value produced after some time: 42
|
||||||
non-blocking goroutine
|
non-blocking goroutine
|
||||||
done with non-blocking goroutine
|
done with non-blocking goroutine
|
||||||
async interface method call
|
async interface method call
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче