github.com/aykevl/tinygo@v0.5.0/compiler/goroutine-lowering.go (about) 1 package compiler 2 3 // This file lowers goroutine pseudo-functions into coroutines scheduled by a 4 // scheduler at runtime. It uses coroutine support in LLVM for this 5 // transformation: https://llvm.org/docs/Coroutines.html 6 // 7 // For example, take the following code: 8 // 9 // func main() { 10 // go foo() 11 // time.Sleep(2 * time.Second) 12 // println("some other operation") 13 // bar() 14 // println("done") 15 // } 16 // 17 // func foo() { 18 // for { 19 // println("foo!") 20 // time.Sleep(time.Second) 21 // } 22 // } 23 // 24 // func bar() { 25 // time.Sleep(time.Second) 26 // println("blocking operation completed) 27 // } 28 // 29 // It is transformed by the IR generator in compiler.go into the following 30 // pseudo-Go code: 31 // 32 // func main() { 33 // fn := runtime.makeGoroutine(foo) 34 // fn() 35 // time.Sleep(2 * time.Second) 36 // println("some other operation") 37 // bar() // imagine an 'await' keyword in front of this call 38 // println("done") 39 // } 40 // 41 // func foo() { 42 // for { 43 // println("foo!") 44 // time.Sleep(time.Second) 45 // } 46 // } 47 // 48 // func bar() { 49 // time.Sleep(time.Second) 50 // println("blocking operation completed) 51 // } 52 // 53 // The pass in this file transforms this code even further, to the following 54 // async/await style pseudocode: 55 // 56 // func main(parent) { 57 // hdl := llvm.makeCoroutine() 58 // foo(nil) // do not pass the parent coroutine: this is an independent goroutine 59 // runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time 60 // llvm.suspend(hdl) // suspend point 61 // println("some other operation") 62 // bar(hdl) // await, pass a continuation (hdl) to bar 63 // llvm.suspend(hdl) // suspend point, wait for the callee to re-activate 64 // println("done") 65 // runtime.activateTask(parent) // re-activate the parent (nop, there is no parent) 66 // } 67 // 68 // func foo(parent) { 69 // hdl := llvm.makeCoroutine() 70 // for { 71 // println("foo!") 72 // runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time 73 // llvm.suspend(hdl) // suspend point 74 // } 75 // } 76 // 77 // func bar(parent) { 78 // hdl := llvm.makeCoroutine() 79 // runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time 80 // llvm.suspend(hdl) // suspend point 81 // println("blocking operation completed) 82 // runtime.activateTask(parent) // re-activate the parent coroutine before returning 83 // } 84 // 85 // The real LLVM code is more complicated, but this is the general idea. 86 // 87 // The LLVM coroutine passes will then process this file further transforming 88 // these three functions into coroutines. Most of the actual work is done by the 89 // scheduler, which runs in the background scheduling all coroutines. 90 91 import ( 92 "errors" 93 "strings" 94 95 "tinygo.org/x/go-llvm" 96 ) 97 98 type asyncFunc struct { 99 taskHandle llvm.Value 100 cleanupBlock llvm.BasicBlock 101 suspendBlock llvm.BasicBlock 102 unreachableBlock llvm.BasicBlock 103 } 104 105 // LowerGoroutines is a pass called during optimization that transforms the IR 106 // into one where all blocking functions are turned into goroutines and blocking 107 // calls into await calls. 108 func (c *Compiler) LowerGoroutines() error { 109 needsScheduler, err := c.markAsyncFunctions() 110 if err != nil { 111 return err 112 } 113 114 uses := getUses(c.mod.NamedFunction("runtime.callMain")) 115 if len(uses) != 1 || uses[0].IsACallInst().IsNil() { 116 panic("expected exactly 1 call of runtime.callMain, check the entry point") 117 } 118 mainCall := uses[0] 119 120 // Replace call of runtime.callMain() with a real call to main.main(), 121 // optionally followed by a call to runtime.scheduler(). 122 c.builder.SetInsertPointBefore(mainCall) 123 realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main") 124 c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType)}, "") 125 if needsScheduler { 126 c.createRuntimeCall("scheduler", nil, "") 127 } 128 mainCall.EraseFromParentAsInstruction() 129 130 if !needsScheduler { 131 go_scheduler := c.mod.NamedFunction("go_scheduler") 132 if !go_scheduler.IsNil() { 133 // This is the WebAssembly backend. 134 // There is no need to export the go_scheduler function, but it is 135 // still exported. Make sure it is optimized away. 136 go_scheduler.SetLinkage(llvm.InternalLinkage) 137 } 138 } 139 140 // main.main was set to external linkage during IR construction. Set it to 141 // internal linkage to enable interprocedural optimizations. 142 realMain.SetLinkage(llvm.InternalLinkage) 143 c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage) 144 c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage) 145 c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage) 146 c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.InternalLinkage) 147 c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage) 148 c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.InternalLinkage) 149 c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage) 150 151 return nil 152 } 153 154 // markAsyncFunctions does the bulk of the work of lowering goroutines. It 155 // determines whether a scheduler is needed, and if it is, it transforms 156 // blocking operations into goroutines and blocking calls into await calls. 157 // 158 // It does the following operations: 159 // * Find all blocking functions. 160 // * Determine whether a scheduler is necessary. If not, it skips the 161 // following operations. 162 // * Transform call instructions into await calls. 163 // * Transform return instructions into final suspends. 164 // * Set up the coroutine frames for async functions. 165 // * Transform blocking calls into their async equivalents. 166 func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { 167 var worklist []llvm.Value 168 169 sleep := c.mod.NamedFunction("time.Sleep") 170 if !sleep.IsNil() { 171 worklist = append(worklist, sleep) 172 } 173 deadlockStub := c.mod.NamedFunction("runtime.deadlockStub") 174 if !deadlockStub.IsNil() { 175 worklist = append(worklist, deadlockStub) 176 } 177 chanSendStub := c.mod.NamedFunction("runtime.chanSendStub") 178 if !chanSendStub.IsNil() { 179 worklist = append(worklist, chanSendStub) 180 } 181 chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub") 182 if !chanRecvStub.IsNil() { 183 worklist = append(worklist, chanRecvStub) 184 } 185 186 if len(worklist) == 0 { 187 // There are no blocking operations, so no need to transform anything. 188 return false, c.lowerMakeGoroutineCalls() 189 } 190 191 // Find all async functions. 192 // Keep reducing this worklist by marking a function as recursively async 193 // from the worklist and pushing all its parents that are non-async. 194 // This is somewhat similar to a worklist in a mark-sweep garbage collector: 195 // the work items are then grey objects. 196 asyncFuncs := make(map[llvm.Value]*asyncFunc) 197 asyncList := make([]llvm.Value, 0, 4) 198 for len(worklist) != 0 { 199 // Pick the topmost. 200 f := worklist[len(worklist)-1] 201 worklist = worklist[:len(worklist)-1] 202 if _, ok := asyncFuncs[f]; ok { 203 continue // already processed 204 } 205 // Add to set of async functions. 206 asyncFuncs[f] = &asyncFunc{} 207 asyncList = append(asyncList, f) 208 209 // Add all callees to the worklist. 210 for _, use := range getUses(f) { 211 if use.IsConstant() && use.Opcode() == llvm.BitCast { 212 bitcastUses := getUses(use) 213 for _, call := range bitcastUses { 214 if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" { 215 return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine") 216 } 217 } 218 // This is a go statement. Do not mark the parent as async, as 219 // starting a goroutine is not a blocking operation. 220 continue 221 } 222 if use.IsACallInst().IsNil() { 223 // Not a call instruction. Maybe a store to a global? In any 224 // case, this requires support for async calls across function 225 // pointers which is not yet supported. 226 return false, errors.New("async function " + f.Name() + " used as function pointer") 227 } 228 parent := use.InstructionParent().Parent() 229 for i := 0; i < use.OperandsCount()-1; i++ { 230 if use.Operand(i) == f { 231 return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name()) 232 } 233 } 234 worklist = append(worklist, parent) 235 } 236 } 237 238 // Check whether a scheduler is needed. 239 makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine") 240 if c.GOOS == "js" && strings.HasPrefix(c.Triple, "wasm") { 241 // JavaScript always needs a scheduler, as in general no blocking 242 // operations are possible. Blocking operations block the browser UI, 243 // which is very bad. 244 needsScheduler = true 245 } else { 246 // Only use a scheduler when an async goroutine is started. When the 247 // goroutine is not async (does not do any blocking operation), no 248 // scheduler is necessary as it can be called directly. 249 for _, use := range getUses(makeGoroutine) { 250 // Input param must be const bitcast of function. 251 bitcast := use.Operand(0) 252 if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast { 253 panic("expected const bitcast operand of runtime.makeGoroutine") 254 } 255 goroutine := bitcast.Operand(0) 256 if _, ok := asyncFuncs[goroutine]; ok { 257 needsScheduler = true 258 break 259 } 260 } 261 } 262 263 if !needsScheduler { 264 // No scheduler is needed. Do not transform all functions here. 265 // However, make sure that all go calls (which are all non-async) are 266 // transformed into regular calls. 267 return false, c.lowerMakeGoroutineCalls() 268 } 269 270 // Create a few LLVM intrinsics for coroutine support. 271 272 coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false) 273 coroIdFunc := llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType) 274 275 coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false) 276 coroSizeFunc := llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType) 277 278 coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false) 279 coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType) 280 281 coroPromiseType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.i8ptrType, c.ctx.Int32Type(), c.ctx.Int1Type()}, false) 282 coroPromiseFunc := llvm.AddFunction(c.mod, "llvm.coro.promise", coroPromiseType) 283 284 coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false) 285 coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType) 286 287 coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false) 288 coroEndFunc := llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType) 289 290 coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false) 291 coroFreeFunc := llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType) 292 293 // Transform all async functions into coroutines. 294 for _, f := range asyncList { 295 if f == sleep || f == deadlockStub || f == chanSendStub || f == chanRecvStub { 296 continue 297 } 298 299 frame := asyncFuncs[f] 300 frame.cleanupBlock = c.ctx.AddBasicBlock(f, "task.cleanup") 301 frame.suspendBlock = c.ctx.AddBasicBlock(f, "task.suspend") 302 frame.unreachableBlock = c.ctx.AddBasicBlock(f, "task.unreachable") 303 304 // Scan for async calls and return instructions that need to have 305 // suspend points inserted. 306 var asyncCalls []llvm.Value 307 var returns []llvm.Value 308 for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { 309 for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { 310 if !inst.IsACallInst().IsNil() { 311 callee := inst.CalledValue() 312 if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSendStub || callee == chanRecvStub { 313 continue 314 } 315 asyncCalls = append(asyncCalls, inst) 316 } else if !inst.IsAReturnInst().IsNil() { 317 returns = append(returns, inst) 318 } 319 } 320 } 321 322 // Coroutine setup. 323 c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction()) 324 taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state") 325 stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8") 326 id := c.builder.CreateCall(coroIdFunc, []llvm.Value{ 327 llvm.ConstInt(c.ctx.Int32Type(), 0, false), 328 stateI8, 329 llvm.ConstNull(c.i8ptrType), 330 llvm.ConstNull(c.i8ptrType), 331 }, "task.token") 332 size := c.builder.CreateCall(coroSizeFunc, nil, "task.size") 333 if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { 334 size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr") 335 } else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) { 336 size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr") 337 } 338 data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data") 339 frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle") 340 341 // Modify async calls so this function suspends right after the child 342 // returns, because the child is probably not finished yet. Wait until 343 // the child reactivates the parent. 344 for _, inst := range asyncCalls { 345 inst.SetOperand(inst.OperandsCount()-2, frame.taskHandle) 346 347 // Split this basic block. 348 await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await") 349 350 // Set task state to TASK_STATE_CALL. 351 c.builder.SetInsertPointAtEnd(inst.InstructionParent()) 352 353 // Suspend. 354 continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ 355 llvm.ConstNull(c.ctx.TokenType()), 356 llvm.ConstInt(c.ctx.Int1Type(), 0, false), 357 }, "") 358 sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) 359 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await) 360 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) 361 } 362 363 // Replace return instructions with suspend points that should 364 // reactivate the parent coroutine. 365 for _, inst := range returns { 366 if inst.OperandsCount() == 0 { 367 // These properties were added by the functionattrs pass. 368 // Remove them, because now we start using the parameter. 369 // https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes 370 for _, kind := range []string{"nocapture", "readnone"} { 371 kindID := llvm.AttributeKindID(kind) 372 f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID) 373 } 374 375 // Reactivate the parent coroutine. This adds it back to 376 // the run queue, so it is started again by the 377 // scheduler when possible (possibly right after the 378 // following suspend). 379 c.builder.SetInsertPointBefore(inst) 380 381 parentHandle := f.LastParam() 382 c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "") 383 384 // Suspend this coroutine. 385 // It would look like this is unnecessary, but if this 386 // suspend point is left out, it leads to undefined 387 // behavior somehow (with the unreachable instruction). 388 continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ 389 llvm.ConstNull(c.ctx.TokenType()), 390 llvm.ConstInt(c.ctx.Int1Type(), 1, false), 391 }, "ret") 392 sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) 393 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock) 394 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) 395 inst.EraseFromParentAsInstruction() 396 } else { 397 panic("todo: return value from coroutine") 398 } 399 } 400 401 // Coroutine cleanup. Free resources associated with this coroutine. 402 c.builder.SetInsertPointAtEnd(frame.cleanupBlock) 403 mem := c.builder.CreateCall(coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free") 404 c.createRuntimeCall("free", []llvm.Value{mem}, "") 405 c.builder.CreateBr(frame.suspendBlock) 406 407 // Coroutine suspend. A call to llvm.coro.suspend() will branch here. 408 c.builder.SetInsertPointAtEnd(frame.suspendBlock) 409 c.builder.CreateCall(coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused") 410 returnType := f.Type().ElementType().ReturnType() 411 if returnType.TypeKind() == llvm.VoidTypeKind { 412 c.builder.CreateRetVoid() 413 } else { 414 c.builder.CreateRet(llvm.Undef(returnType)) 415 } 416 417 // Coroutine exit. All final suspends (return instructions) will branch 418 // here. 419 c.builder.SetInsertPointAtEnd(frame.unreachableBlock) 420 c.builder.CreateUnreachable() 421 } 422 423 // Transform calls to time.Sleep() into coroutine suspend points. 424 for _, sleepCall := range getUses(sleep) { 425 // sleepCall must be a call instruction. 426 frame := asyncFuncs[sleepCall.InstructionParent().Parent()] 427 duration := sleepCall.Operand(0) 428 429 // Set task state to TASK_STATE_SLEEP and set the duration. 430 c.builder.SetInsertPointBefore(sleepCall) 431 c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, duration}, "") 432 433 // Yield to scheduler. 434 continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ 435 llvm.ConstNull(c.ctx.TokenType()), 436 llvm.ConstInt(c.ctx.Int1Type(), 0, false), 437 }, "") 438 wakeup := c.splitBasicBlock(sleepCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup") 439 c.builder.SetInsertPointBefore(sleepCall) 440 sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) 441 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup) 442 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) 443 sleepCall.EraseFromParentAsInstruction() 444 } 445 446 // Transform calls to runtime.deadlockStub into coroutine suspends (without 447 // resume). 448 for _, deadlockCall := range getUses(deadlockStub) { 449 // deadlockCall must be a call instruction. 450 frame := asyncFuncs[deadlockCall.InstructionParent().Parent()] 451 452 // Exit coroutine. 453 c.builder.SetInsertPointBefore(deadlockCall) 454 continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ 455 llvm.ConstNull(c.ctx.TokenType()), 456 llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend 457 }, "") 458 c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead") 459 c.builder.SetInsertPointBefore(deadlockCall) 460 sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) 461 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock) 462 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) 463 deadlockCall.EraseFromParentAsInstruction() 464 } 465 466 // Transform calls to runtime.chanSendStub into channel send operations. 467 for _, sendOp := range getUses(chanSendStub) { 468 // sendOp must be a call instruction. 469 frame := asyncFuncs[sendOp.InstructionParent().Parent()] 470 471 // Send the value over the channel, or block. 472 sendOp.SetOperand(0, frame.taskHandle) 473 sendOp.SetOperand(sendOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanSend")) 474 475 // Use taskState.data to store the value to send: 476 // *(*valueType)(&coroutine.promise().data) = valueToSend 477 // runtime.chanSend(coroutine, ch) 478 bitcast := sendOp.Operand(2) 479 valueAlloca := bitcast.Operand(0) 480 c.builder.SetInsertPointBefore(valueAlloca) 481 promiseType := c.mod.GetTypeByName("runtime.taskState") 482 promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{ 483 frame.taskHandle, 484 llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false), 485 llvm.ConstInt(c.ctx.Int1Type(), 0, false), 486 }, "task.promise.raw") 487 promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise") 488 dataPtr := c.builder.CreateGEP(promise, []llvm.Value{ 489 llvm.ConstInt(c.ctx.Int32Type(), 0, false), 490 llvm.ConstInt(c.ctx.Int32Type(), 2, false), 491 }, "task.promise.data") 492 sendOp.SetOperand(2, llvm.Undef(c.i8ptrType)) 493 valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), "")) 494 bitcast.EraseFromParentAsInstruction() 495 valueAlloca.EraseFromParentAsInstruction() 496 497 // Yield to scheduler. 498 c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp)) 499 continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ 500 llvm.ConstNull(c.ctx.TokenType()), 501 llvm.ConstInt(c.ctx.Int1Type(), 0, false), 502 }, "") 503 sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) 504 wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.sent") 505 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup) 506 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) 507 } 508 509 // Transform calls to runtime.chanRecvStub into channel receive operations. 510 for _, recvOp := range getUses(chanRecvStub) { 511 // recvOp must be a call instruction. 512 frame := asyncFuncs[recvOp.InstructionParent().Parent()] 513 514 bitcast := recvOp.Operand(2) 515 commaOk := recvOp.Operand(3) 516 valueAlloca := bitcast.Operand(0) 517 518 // Receive the value over the channel, or block. 519 recvOp.SetOperand(0, frame.taskHandle) 520 recvOp.SetOperand(recvOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanRecv")) 521 recvOp.SetOperand(2, llvm.Undef(c.i8ptrType)) 522 bitcast.EraseFromParentAsInstruction() 523 524 // Yield to scheduler. 525 c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp)) 526 continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ 527 llvm.ConstNull(c.ctx.TokenType()), 528 llvm.ConstInt(c.ctx.Int1Type(), 0, false), 529 }, "") 530 sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) 531 wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.received") 532 c.builder.SetInsertPointAtEnd(recvOp.InstructionParent()) 533 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup) 534 sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock) 535 536 // The value to receive is stored in taskState.data: 537 // runtime.chanRecv(coroutine, ch) 538 // promise := coroutine.promise() 539 // valueReceived := *(*valueType)(&promise.data) 540 // ok := promise.commaOk 541 c.builder.SetInsertPointBefore(wakeup.FirstInstruction()) 542 promiseType := c.mod.GetTypeByName("runtime.taskState") 543 promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{ 544 frame.taskHandle, 545 llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false), 546 llvm.ConstInt(c.ctx.Int1Type(), 0, false), 547 }, "task.promise.raw") 548 promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise") 549 dataPtr := c.builder.CreateGEP(promise, []llvm.Value{ 550 llvm.ConstInt(c.ctx.Int32Type(), 0, false), 551 llvm.ConstInt(c.ctx.Int32Type(), 2, false), 552 }, "task.promise.data") 553 valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), "")) 554 valueAlloca.EraseFromParentAsInstruction() 555 commaOkPtr := c.builder.CreateGEP(promise, []llvm.Value{ 556 llvm.ConstInt(c.ctx.Int32Type(), 0, false), 557 llvm.ConstInt(c.ctx.Int32Type(), 1, false), 558 }, "task.promise.comma-ok") 559 commaOk.ReplaceAllUsesWith(commaOkPtr) 560 recvOp.SetOperand(3, llvm.Undef(commaOk.Type())) 561 } 562 563 return true, c.lowerMakeGoroutineCalls() 564 } 565 566 // Lower runtime.makeGoroutine calls to regular call instructions. This is done 567 // after the regular goroutine transformations. The started goroutines are 568 // either non-blocking (in which case they can be called directly) or blocking, 569 // in which case they will ask the scheduler themselves to be rescheduled. 570 func (c *Compiler) lowerMakeGoroutineCalls() error { 571 // The following Go code: 572 // go startedGoroutine() 573 // 574 // Is translated to the following during IR construction, to preserve the 575 // fact that this function should be called as a new goroutine. 576 // %0 = call i8* @runtime.makeGoroutine(i8* bitcast (void (i8*, i8*)* @main.startedGoroutine to i8*), i8* undef, i8* null) 577 // %1 = bitcast i8* %0 to void (i8*, i8*)* 578 // call void %1(i8* undef, i8* undef) 579 // 580 // This function rewrites it to a direct call: 581 // call void @main.startedGoroutine(i8* undef, i8* null) 582 583 makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine") 584 for _, goroutine := range getUses(makeGoroutine) { 585 bitcastIn := goroutine.Operand(0) 586 origFunc := bitcastIn.Operand(0) 587 uses := getUses(goroutine) 588 if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() { 589 return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine") 590 } 591 bitcastOut := uses[0] 592 uses = getUses(bitcastOut) 593 if len(uses) != 1 || uses[0].IsACallInst().IsNil() { 594 return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast") 595 } 596 realCall := uses[0] 597 598 // Create call instruction. 599 var params []llvm.Value 600 for i := 0; i < realCall.OperandsCount()-1; i++ { 601 params = append(params, realCall.Operand(i)) 602 } 603 params[len(params)-1] = llvm.ConstPointerNull(c.i8ptrType) // parent coroutine handle (must be nil) 604 c.builder.SetInsertPointBefore(realCall) 605 c.builder.CreateCall(origFunc, params, "") 606 realCall.EraseFromParentAsInstruction() 607 bitcastOut.EraseFromParentAsInstruction() 608 goroutine.EraseFromParentAsInstruction() 609 } 610 611 return nil 612 }