mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
588 lines
24 KiB
588 lines
24 KiB
package compiler
|
|
|
|
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
|
|
// scheduler at runtime. It uses coroutine support in LLVM for this
|
|
// transformation: https://llvm.org/docs/Coroutines.html
|
|
//
|
|
// For example, take the following code:
|
|
//
|
|
// func main() {
|
|
// go foo()
|
|
// time.Sleep(2 * time.Second)
|
|
// println("some other operation")
|
|
// bar()
|
|
// println("done")
|
|
// }
|
|
//
|
|
// func foo() {
|
|
// for {
|
|
// println("foo!")
|
|
// time.Sleep(time.Second)
|
|
// }
|
|
// }
|
|
//
|
|
// func bar() {
|
|
// time.Sleep(time.Second)
|
|
// println("blocking operation completed)
|
|
// }
|
|
//
|
|
// It is transformed by the IR generator in compiler.go into the following
|
|
// pseudo-Go code:
|
|
//
|
|
// func main() {
|
|
// fn := runtime.makeGoroutine(foo)
|
|
// fn()
|
|
// time.Sleep(2 * time.Second)
|
|
// println("some other operation")
|
|
// bar() // imagine an 'await' keyword in front of this call
|
|
// println("done")
|
|
// }
|
|
//
|
|
// func foo() {
|
|
// for {
|
|
// println("foo!")
|
|
// time.Sleep(time.Second)
|
|
// }
|
|
// }
|
|
//
|
|
// func bar() {
|
|
// time.Sleep(time.Second)
|
|
// println("blocking operation completed)
|
|
// }
|
|
//
|
|
// The pass in this file transforms this code even further, to the following
|
|
// async/await style pseudocode:
|
|
//
|
|
// func main(parent) {
|
|
// hdl := llvm.makeCoroutine()
|
|
// foo(nil) // do not pass the parent coroutine: this is an independent goroutine
|
|
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
|
// llvm.suspend(hdl) // suspend point
|
|
// println("some other operation")
|
|
// bar(hdl) // await, pass a continuation (hdl) to bar
|
|
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
|
// println("done")
|
|
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
|
// }
|
|
//
|
|
// func foo(parent) {
|
|
// hdl := llvm.makeCoroutine()
|
|
// for {
|
|
// println("foo!")
|
|
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
|
// llvm.suspend(hdl) // suspend point
|
|
// }
|
|
// }
|
|
//
|
|
// func bar(parent) {
|
|
// hdl := llvm.makeCoroutine()
|
|
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
|
// llvm.suspend(hdl) // suspend point
|
|
// println("blocking operation completed)
|
|
// runtime.activateTask(parent) // re-activate the parent coroutine before returning
|
|
// }
|
|
//
|
|
// The real LLVM code is more complicated, but this is the general idea.
|
|
//
|
|
// The LLVM coroutine passes will then process this file further transforming
|
|
// these three functions into coroutines. Most of the actual work is done by the
|
|
// scheduler, which runs in the background scheduling all coroutines.
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
type asyncFunc struct {
|
|
taskHandle llvm.Value
|
|
cleanupBlock llvm.BasicBlock
|
|
suspendBlock llvm.BasicBlock
|
|
unreachableBlock llvm.BasicBlock
|
|
}
|
|
|
|
// LowerGoroutines is a pass called during optimization that transforms the IR
|
|
// into one where all blocking functions are turned into goroutines and blocking
|
|
// calls into await calls.
|
|
func (c *Compiler) LowerGoroutines() error {
|
|
needsScheduler, err := c.markAsyncFunctions()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
|
|
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
|
panic("expected exactly 1 call of runtime.callMain, check the entry point")
|
|
}
|
|
mainCall := uses[0]
|
|
|
|
// Replace call of runtime.callMain() with a real call to main.main(),
|
|
// optionally followed by a call to runtime.scheduler().
|
|
c.builder.SetInsertPointBefore(mainCall)
|
|
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
|
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType)}, "")
|
|
if needsScheduler {
|
|
c.createRuntimeCall("scheduler", nil, "")
|
|
}
|
|
mainCall.EraseFromParentAsInstruction()
|
|
|
|
if !needsScheduler {
|
|
go_scheduler := c.mod.NamedFunction("go_scheduler")
|
|
if !go_scheduler.IsNil() {
|
|
// This is the WebAssembly backend.
|
|
// There is no need to export the go_scheduler function, but it is
|
|
// still exported. Make sure it is optimized away.
|
|
go_scheduler.SetLinkage(llvm.InternalLinkage)
|
|
}
|
|
}
|
|
|
|
// main.main was set to external linkage during IR construction. Set it to
|
|
// internal linkage to enable interprocedural optimizations.
|
|
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.activateTask").SetLinkage(llvm.InternalLinkage)
|
|
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
|
|
|
return nil
|
|
}
|
|
|
|
// markAsyncFunctions does the bulk of the work of lowering goroutines. It
|
|
// determines whether a scheduler is needed, and if it is, it transforms
|
|
// blocking operations into goroutines and blocking calls into await calls.
|
|
//
|
|
// It does the following operations:
|
|
// * Find all blocking functions.
|
|
// * Determine whether a scheduler is necessary. If not, it skips the
|
|
// following operations.
|
|
// * Transform call instructions into await calls.
|
|
// * Transform return instructions into final suspends.
|
|
// * Set up the coroutine frames for async functions.
|
|
// * Transform blocking calls into their async equivalents.
|
|
func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|
var worklist []llvm.Value
|
|
|
|
sleep := c.mod.NamedFunction("time.Sleep")
|
|
if !sleep.IsNil() {
|
|
worklist = append(worklist, sleep)
|
|
}
|
|
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
|
|
if !chanSendStub.IsNil() {
|
|
worklist = append(worklist, chanSendStub)
|
|
}
|
|
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
|
|
if !chanRecvStub.IsNil() {
|
|
worklist = append(worklist, chanRecvStub)
|
|
}
|
|
|
|
if len(worklist) == 0 {
|
|
// There are no blocking operations, so no need to transform anything.
|
|
return false, c.lowerMakeGoroutineCalls()
|
|
}
|
|
|
|
// Find all async functions.
|
|
// Keep reducing this worklist by marking a function as recursively async
|
|
// from the worklist and pushing all its parents that are non-async.
|
|
// This is somewhat similar to a worklist in a mark-sweep garbage collector:
|
|
// the work items are then grey objects.
|
|
asyncFuncs := make(map[llvm.Value]*asyncFunc)
|
|
asyncList := make([]llvm.Value, 0, 4)
|
|
for len(worklist) != 0 {
|
|
// Pick the topmost.
|
|
f := worklist[len(worklist)-1]
|
|
worklist = worklist[:len(worklist)-1]
|
|
if _, ok := asyncFuncs[f]; ok {
|
|
continue // already processed
|
|
}
|
|
// Add to set of async functions.
|
|
asyncFuncs[f] = &asyncFunc{}
|
|
asyncList = append(asyncList, f)
|
|
|
|
// Add all callees to the worklist.
|
|
for _, use := range getUses(f) {
|
|
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
|
bitcastUses := getUses(use)
|
|
for _, call := range bitcastUses {
|
|
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
|
|
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
|
|
}
|
|
}
|
|
// This is a go statement. Do not mark the parent as async, as
|
|
// starting a goroutine is not a blocking operation.
|
|
continue
|
|
}
|
|
if use.IsACallInst().IsNil() {
|
|
// Not a call instruction. Maybe a store to a global? In any
|
|
// case, this requires support for async calls across function
|
|
// pointers which is not yet supported.
|
|
return false, errors.New("async function " + f.Name() + " used as function pointer")
|
|
}
|
|
parent := use.InstructionParent().Parent()
|
|
for i := 0; i < use.OperandsCount()-1; i++ {
|
|
if use.Operand(i) == f {
|
|
return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name())
|
|
}
|
|
}
|
|
worklist = append(worklist, parent)
|
|
}
|
|
}
|
|
|
|
// Check whether a scheduler is needed.
|
|
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
|
if c.GOOS == "js" && strings.HasPrefix(c.Triple, "wasm") {
|
|
// JavaScript always needs a scheduler, as in general no blocking
|
|
// operations are possible. Blocking operations block the browser UI,
|
|
// which is very bad.
|
|
needsScheduler = true
|
|
} else {
|
|
// Only use a scheduler when an async goroutine is started. When the
|
|
// goroutine is not async (does not do any blocking operation), no
|
|
// scheduler is necessary as it can be called directly.
|
|
for _, use := range getUses(makeGoroutine) {
|
|
// Input param must be const bitcast of function.
|
|
bitcast := use.Operand(0)
|
|
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
|
|
panic("expected const bitcast operand of runtime.makeGoroutine")
|
|
}
|
|
goroutine := bitcast.Operand(0)
|
|
if _, ok := asyncFuncs[goroutine]; ok {
|
|
needsScheduler = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !needsScheduler {
|
|
// No scheduler is needed. Do not transform all functions here.
|
|
// However, make sure that all go calls (which are all non-async) are
|
|
// transformed into regular calls.
|
|
return false, c.lowerMakeGoroutineCalls()
|
|
}
|
|
|
|
// Create a few LLVM intrinsics for coroutine support.
|
|
|
|
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
|
|
coroIdFunc := llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
|
|
|
|
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
|
|
coroSizeFunc := llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
|
|
|
|
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)
|
|
|
|
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
|
|
coroEndFunc := llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
|
|
|
|
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
|
coroFreeFunc := llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
|
|
|
|
// Transform all async functions into coroutines.
|
|
for _, f := range asyncList {
|
|
if f == sleep || f == chanSendStub || f == chanRecvStub {
|
|
continue
|
|
}
|
|
|
|
frame := asyncFuncs[f]
|
|
frame.cleanupBlock = c.ctx.AddBasicBlock(f, "task.cleanup")
|
|
frame.suspendBlock = c.ctx.AddBasicBlock(f, "task.suspend")
|
|
frame.unreachableBlock = c.ctx.AddBasicBlock(f, "task.unreachable")
|
|
|
|
// Scan for async calls and return instructions that need to have
|
|
// suspend points inserted.
|
|
var asyncCalls []llvm.Value
|
|
var returns []llvm.Value
|
|
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
|
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 == chanSendStub || callee == chanRecvStub {
|
|
continue
|
|
}
|
|
asyncCalls = append(asyncCalls, inst)
|
|
} else if !inst.IsAReturnInst().IsNil() {
|
|
returns = append(returns, inst)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Coroutine setup.
|
|
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
|
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
|
|
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
|
|
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
|
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
stateI8,
|
|
llvm.ConstNull(c.i8ptrType),
|
|
llvm.ConstNull(c.i8ptrType),
|
|
}, "task.token")
|
|
size := c.builder.CreateCall(coroSizeFunc, nil, "task.size")
|
|
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
|
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
|
|
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
|
|
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
|
}
|
|
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
|
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
|
|
|
// Modify async calls so this function suspends right after the child
|
|
// returns, because the child is probably not finished yet. Wait until
|
|
// the child reactivates the parent.
|
|
for _, inst := range asyncCalls {
|
|
inst.SetOperand(inst.OperandsCount()-2, frame.taskHandle)
|
|
|
|
// Split this basic block.
|
|
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
|
|
|
// Set task state to TASK_STATE_CALL.
|
|
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
|
|
|
// Suspend.
|
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
|
llvm.ConstNull(c.ctx.TokenType()),
|
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
|
}, "")
|
|
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(), 1, false), frame.cleanupBlock)
|
|
}
|
|
|
|
// Replace return instructions with suspend points that should
|
|
// reactivate the parent coroutine.
|
|
for _, inst := range returns {
|
|
if inst.OperandsCount() == 0 {
|
|
// These properties were added by the functionattrs pass.
|
|
// Remove them, because now we start using the parameter.
|
|
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
|
for _, kind := range []string{"nocapture", "readnone"} {
|
|
kindID := llvm.AttributeKindID(kind)
|
|
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)
|
|
|
|
parentHandle := f.LastParam()
|
|
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
|
|
|
// Suspend this coroutine.
|
|
// It would look like this is unnecessary, but if this
|
|
// suspend point is left out, it leads to undefined
|
|
// behavior somehow (with the unreachable instruction).
|
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
|
llvm.ConstNull(c.ctx.TokenType()),
|
|
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
|
|
}, "ret")
|
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
|
inst.EraseFromParentAsInstruction()
|
|
} else {
|
|
panic("todo: return value from coroutine")
|
|
}
|
|
}
|
|
|
|
// Coroutine cleanup. Free resources associated with this coroutine.
|
|
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
|
|
mem := c.builder.CreateCall(coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
|
|
c.createRuntimeCall("free", []llvm.Value{mem}, "")
|
|
c.builder.CreateBr(frame.suspendBlock)
|
|
|
|
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
|
|
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
|
|
c.builder.CreateCall(coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
|
|
returnType := f.Type().ElementType().ReturnType()
|
|
if returnType.TypeKind() == llvm.VoidTypeKind {
|
|
c.builder.CreateRetVoid()
|
|
} else {
|
|
c.builder.CreateRet(llvm.Undef(returnType))
|
|
}
|
|
|
|
// Coroutine exit. All final suspends (return instructions) will branch
|
|
// here.
|
|
c.builder.SetInsertPointAtEnd(frame.unreachableBlock)
|
|
c.builder.CreateUnreachable()
|
|
}
|
|
|
|
// Transform calls to time.Sleep() into coroutine suspend points.
|
|
for _, sleepCall := range getUses(sleep) {
|
|
// sleepCall must be a call instruction.
|
|
frame := asyncFuncs[sleepCall.InstructionParent().Parent()]
|
|
duration := sleepCall.Operand(0)
|
|
|
|
// Set task state to TASK_STATE_SLEEP and set the duration.
|
|
c.builder.SetInsertPointBefore(sleepCall)
|
|
c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, duration}, "")
|
|
|
|
// Yield to scheduler.
|
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
|
llvm.ConstNull(c.ctx.TokenType()),
|
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
|
}, "")
|
|
wakeup := c.splitBasicBlock(sleepCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
|
c.builder.SetInsertPointBefore(sleepCall)
|
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
|
sleepCall.EraseFromParentAsInstruction()
|
|
}
|
|
|
|
// Transform calls to runtime.chanSendStub into channel send operations.
|
|
for _, sendOp := range getUses(chanSendStub) {
|
|
// 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.CreateGEP(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{
|
|
llvm.ConstNull(c.ctx.TokenType()),
|
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
|
}, "")
|
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
|
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.sent")
|
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
|
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) {
|
|
// 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{
|
|
llvm.ConstNull(c.ctx.TokenType()),
|
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
|
}, "")
|
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
|
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.received")
|
|
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.CreateGEP(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.CreateGEP(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()
|
|
}
|
|
|
|
// Lower runtime.makeGoroutine calls to regular call instructions. This is done
|
|
// after the regular goroutine transformations. The started goroutines are
|
|
// either non-blocking (in which case they can be called directly) or blocking,
|
|
// in which case they will ask the scheduler themselves to be rescheduled.
|
|
func (c *Compiler) lowerMakeGoroutineCalls() error {
|
|
// The following Go code:
|
|
// go startedGoroutine()
|
|
//
|
|
// Is translated to the following during IR construction, to preserve the
|
|
// fact that this function should be called as a new goroutine.
|
|
// %0 = call i8* @runtime.makeGoroutine(i8* bitcast (void (i8*, i8*)* @main.startedGoroutine to i8*), i8* undef, i8* null)
|
|
// %1 = bitcast i8* %0 to void (i8*, i8*)*
|
|
// call void %1(i8* undef, i8* undef)
|
|
//
|
|
// This function rewrites it to a direct call:
|
|
// call void @main.startedGoroutine(i8* undef, i8* null)
|
|
|
|
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
|
for _, goroutine := range getUses(makeGoroutine) {
|
|
bitcastIn := goroutine.Operand(0)
|
|
origFunc := bitcastIn.Operand(0)
|
|
uses := getUses(goroutine)
|
|
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
|
|
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
|
|
}
|
|
bitcastOut := uses[0]
|
|
uses = getUses(bitcastOut)
|
|
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
|
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
|
|
}
|
|
realCall := uses[0]
|
|
|
|
// Create call instruction.
|
|
var params []llvm.Value
|
|
for i := 0; i < realCall.OperandsCount()-1; i++ {
|
|
params = append(params, realCall.Operand(i))
|
|
}
|
|
params[len(params)-1] = llvm.ConstPointerNull(c.i8ptrType) // parent coroutine handle (must be nil)
|
|
c.builder.SetInsertPointBefore(realCall)
|
|
c.builder.CreateCall(origFunc, params, "")
|
|
realCall.EraseFromParentAsInstruction()
|
|
bitcastOut.EraseFromParentAsInstruction()
|
|
goroutine.EraseFromParentAsInstruction()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|