11package compiler
22
3+ // This file implements lowering for the goroutine scheduler. There are two
4+ // scheduler implementations, one based on tasks (like RTOSes and the main Go
5+ // runtime) and one based on a coroutine compiler transformation. The task based
6+ // implementation requires very little work from the compiler but is not very
7+ // portable (in particular, it is very hard if not impossible to support on
8+ // WebAssembly). The coroutine based one requires a lot of work by the compiler
9+ // to implement, but can run virtually anywhere with a single scheduler
10+ // implementation.
11+ //
12+ // The below description is for the coroutine based scheduler.
13+ //
314// This file lowers goroutine pseudo-functions into coroutines scheduled by a
415// scheduler at runtime. It uses coroutine support in LLVM for this
516// transformation: https://llvm.org/docs/Coroutines.html
@@ -62,7 +73,7 @@ package compiler
6273// llvm.suspend(hdl) // suspend point
6374// println("some other operation")
6475// var i *int // allocate space on the stack for the return value
65- // runtime.setTaskPromisePtr (hdl, &i) // store return value alloca in our coroutine promise
76+ // runtime.setTaskStatePtr (hdl, &i) // store return value alloca in our coroutine promise
6677// bar(hdl) // await, pass a continuation (hdl) to bar
6778// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
6879// println("done", *i)
@@ -106,10 +117,65 @@ type asyncFunc struct {
106117 unreachableBlock llvm.BasicBlock
107118}
108119
109- // LowerGoroutines is a pass called during optimization that transforms the IR
110- // into one where all blocking functions are turned into goroutines and blocking
111- // calls into await calls.
120+ // LowerGoroutines performs some IR transformations necessary to support
121+ // goroutines. It does something different based on whether it uses the
122+ // coroutine or the tasks implementation of goroutines, and whether goroutines
123+ // are necessary at all.
112124func (c * Compiler ) LowerGoroutines () error {
125+ switch c .selectScheduler () {
126+ case "coroutines" :
127+ return c .lowerCoroutines ()
128+ case "tasks" :
129+ return c .lowerTasks ()
130+ default :
131+ panic ("unknown scheduler type" )
132+ }
133+ }
134+
135+ // lowerTasks starts the main goroutine and then runs the scheduler.
136+ // This is enough compiler-level transformation for the task-based scheduler.
137+ func (c * Compiler ) lowerTasks () error {
138+ uses := getUses (c .mod .NamedFunction ("runtime.callMain" ))
139+ if len (uses ) != 1 || uses [0 ].IsACallInst ().IsNil () {
140+ panic ("expected exactly 1 call of runtime.callMain, check the entry point" )
141+ }
142+ mainCall := uses [0 ]
143+
144+ realMain := c .mod .NamedFunction (c .ir .MainPkg ().Pkg .Path () + ".main" )
145+ if len (getUses (c .mod .NamedFunction ("runtime.startGoroutine" ))) != 0 {
146+ // Program needs a scheduler. Start main.main as a goroutine and start
147+ // the scheduler.
148+ realMainWrapper := c .createGoroutineStartWrapper (realMain )
149+ c .builder .SetInsertPointBefore (mainCall )
150+ zero := llvm .ConstInt (c .uintptrType , 0 , false )
151+ c .createRuntimeCall ("startGoroutine" , []llvm.Value {realMainWrapper , zero }, "" )
152+ c .createRuntimeCall ("scheduler" , nil , "" )
153+ } else {
154+ // Program doesn't need a scheduler. Call main.main directly.
155+ c .builder .SetInsertPointBefore (mainCall )
156+ params := []llvm.Value {
157+ llvm .Undef (c .i8ptrType ), // unused context parameter
158+ llvm .Undef (c .i8ptrType ), // unused coroutine handle
159+ }
160+ c .createCall (realMain , params , "" )
161+ // runtime.Goexit isn't needed so let it be optimized away by
162+ // globalopt.
163+ c .mod .NamedFunction ("runtime.Goexit" ).SetLinkage (llvm .InternalLinkage )
164+ }
165+ mainCall .EraseFromParentAsInstruction ()
166+
167+ // main.main was set to external linkage during IR construction. Set it to
168+ // internal linkage to enable interprocedural optimizations.
169+ realMain .SetLinkage (llvm .InternalLinkage )
170+
171+ return nil
172+ }
173+
174+ // lowerCoroutines transforms the IR into one where all blocking functions are
175+ // turned into goroutines and blocking calls into await calls. It also makes
176+ // sure that the first coroutine is started and the coroutine scheduler will be
177+ // run.
178+ func (c * Compiler ) lowerCoroutines () error {
113179 needsScheduler , err := c .markAsyncFunctions ()
114180 if err != nil {
115181 return err
@@ -144,12 +210,6 @@ func (c *Compiler) LowerGoroutines() error {
144210 // main.main was set to external linkage during IR construction. Set it to
145211 // internal linkage to enable interprocedural optimizations.
146212 realMain .SetLinkage (llvm .InternalLinkage )
147- c .mod .NamedFunction ("runtime.alloc" ).SetLinkage (llvm .InternalLinkage )
148- c .mod .NamedFunction ("runtime.free" ).SetLinkage (llvm .InternalLinkage )
149- c .mod .NamedFunction ("runtime.sleepTask" ).SetLinkage (llvm .InternalLinkage )
150- c .mod .NamedFunction ("runtime.setTaskPromisePtr" ).SetLinkage (llvm .InternalLinkage )
151- c .mod .NamedFunction ("runtime.getTaskPromisePtr" ).SetLinkage (llvm .InternalLinkage )
152- c .mod .NamedFunction ("runtime.scheduler" ).SetLinkage (llvm .InternalLinkage )
153213
154214 return nil
155215}
@@ -173,9 +233,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
173233 if ! sleep .IsNil () {
174234 worklist = append (worklist , sleep )
175235 }
176- deadlockStub := c .mod .NamedFunction ("runtime.deadlockStub " )
177- if ! deadlockStub .IsNil () {
178- worklist = append (worklist , deadlockStub )
236+ deadlock := c .mod .NamedFunction ("runtime.deadlock " )
237+ if ! deadlock .IsNil () {
238+ worklist = append (worklist , deadlock )
179239 }
180240 chanSend := c .mod .NamedFunction ("runtime.chanSend" )
181241 if ! chanSend .IsNil () {
@@ -300,7 +360,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
300360
301361 // Transform all async functions into coroutines.
302362 for _ , f := range asyncList {
303- if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
363+ if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
304364 continue
305365 }
306366
@@ -317,7 +377,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
317377 for inst := bb .FirstInstruction (); ! inst .IsNil (); inst = llvm .NextInstruction (inst ) {
318378 if ! inst .IsACallInst ().IsNil () {
319379 callee := inst .CalledValue ()
320- if _ , ok := asyncFuncs [callee ]; ! ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv {
380+ if _ , ok := asyncFuncs [callee ]; ! ok || callee == sleep || callee == deadlock || callee == chanSend || callee == chanRecv {
321381 continue
322382 }
323383 asyncCalls = append (asyncCalls , inst )
@@ -365,7 +425,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
365425 retvalAlloca = c .builder .CreateAlloca (inst .Type (), "coro.retvalAlloca" )
366426 c .builder .SetInsertPointBefore (inst )
367427 data := c .builder .CreateBitCast (retvalAlloca , c .i8ptrType , "" )
368- c .createRuntimeCall ("setTaskPromisePtr " , []llvm.Value {frame .taskHandle , data }, "" )
428+ c .createRuntimeCall ("setTaskStatePtr " , []llvm.Value {frame .taskHandle , data }, "" )
369429 }
370430
371431 // Suspend.
@@ -403,7 +463,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
403463 var parentHandle llvm.Value
404464 if f .Linkage () == llvm .ExternalLinkage {
405465 // Exported function.
406- // Note that getTaskPromisePtr will panic if it is called with
466+ // Note that getTaskStatePtr will panic if it is called with
407467 // a nil pointer, so blocking exported functions that try to
408468 // return anything will not work.
409469 parentHandle = llvm .ConstPointerNull (c .i8ptrType )
@@ -423,7 +483,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
423483 // Return this value by writing to the pointer stored in the
424484 // parent handle. The parent coroutine has made an alloca that
425485 // we can write to to store our return value.
426- returnValuePtr := c .createRuntimeCall ("getTaskPromisePtr " , []llvm.Value {parentHandle }, "coro.parentData" )
486+ returnValuePtr := c .createRuntimeCall ("getTaskStatePtr " , []llvm.Value {parentHandle }, "coro.parentData" )
427487 alloca := c .builder .CreateBitCast (returnValuePtr , llvm .PointerType (inst .Operand (0 ).Type (), 0 ), "coro.parentAlloca" )
428488 c .builder .CreateStore (inst .Operand (0 ), alloca )
429489 default :
@@ -502,9 +562,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
502562 sleepCall .EraseFromParentAsInstruction ()
503563 }
504564
505- // Transform calls to runtime.deadlockStub into coroutine suspends (without
565+ // Transform calls to runtime.deadlock into coroutine suspends (without
506566 // resume).
507- for _ , deadlockCall := range getUses (deadlockStub ) {
567+ for _ , deadlockCall := range getUses (deadlock ) {
508568 // deadlockCall must be a call instruction.
509569 frame := asyncFuncs [deadlockCall .InstructionParent ().Parent ()]
510570
0 commit comments