1
1
package compiler
2
2
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
+ //
3
14
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
4
15
// scheduler at runtime. It uses coroutine support in LLVM for this
5
16
// transformation: https://llvm.org/docs/Coroutines.html
@@ -62,7 +73,7 @@ package compiler
62
73
// llvm.suspend(hdl) // suspend point
63
74
// println("some other operation")
64
75
// 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
66
77
// bar(hdl) // await, pass a continuation (hdl) to bar
67
78
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
68
79
// println("done", *i)
@@ -106,10 +117,65 @@ type asyncFunc struct {
106
117
unreachableBlock llvm.BasicBlock
107
118
}
108
119
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.
112
124
func (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 {
113
179
needsScheduler , err := c .markAsyncFunctions ()
114
180
if err != nil {
115
181
return err
@@ -144,12 +210,6 @@ func (c *Compiler) LowerGoroutines() error {
144
210
// main.main was set to external linkage during IR construction. Set it to
145
211
// internal linkage to enable interprocedural optimizations.
146
212
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 )
153
213
154
214
return nil
155
215
}
@@ -173,9 +233,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
173
233
if ! sleep .IsNil () {
174
234
worklist = append (worklist , sleep )
175
235
}
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 )
179
239
}
180
240
chanSend := c .mod .NamedFunction ("runtime.chanSend" )
181
241
if ! chanSend .IsNil () {
@@ -300,7 +360,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
300
360
301
361
// Transform all async functions into coroutines.
302
362
for _ , f := range asyncList {
303
- if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
363
+ if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
304
364
continue
305
365
}
306
366
@@ -317,7 +377,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
317
377
for inst := bb .FirstInstruction (); ! inst .IsNil (); inst = llvm .NextInstruction (inst ) {
318
378
if ! inst .IsACallInst ().IsNil () {
319
379
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 {
321
381
continue
322
382
}
323
383
asyncCalls = append (asyncCalls , inst )
@@ -365,7 +425,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
365
425
retvalAlloca = c .builder .CreateAlloca (inst .Type (), "coro.retvalAlloca" )
366
426
c .builder .SetInsertPointBefore (inst )
367
427
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 }, "" )
369
429
}
370
430
371
431
// Suspend.
@@ -403,7 +463,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
403
463
var parentHandle llvm.Value
404
464
if f .Linkage () == llvm .ExternalLinkage {
405
465
// Exported function.
406
- // Note that getTaskPromisePtr will panic if it is called with
466
+ // Note that getTaskStatePtr will panic if it is called with
407
467
// a nil pointer, so blocking exported functions that try to
408
468
// return anything will not work.
409
469
parentHandle = llvm .ConstPointerNull (c .i8ptrType )
@@ -423,7 +483,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
423
483
// Return this value by writing to the pointer stored in the
424
484
// parent handle. The parent coroutine has made an alloca that
425
485
// 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" )
427
487
alloca := c .builder .CreateBitCast (returnValuePtr , llvm .PointerType (inst .Operand (0 ).Type (), 0 ), "coro.parentAlloca" )
428
488
c .builder .CreateStore (inst .Operand (0 ), alloca )
429
489
default :
@@ -502,9 +562,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
502
562
sleepCall .EraseFromParentAsInstruction ()
503
563
}
504
564
505
- // Transform calls to runtime.deadlockStub into coroutine suspends (without
565
+ // Transform calls to runtime.deadlock into coroutine suspends (without
506
566
// resume).
507
- for _ , deadlockCall := range getUses (deadlockStub ) {
567
+ for _ , deadlockCall := range getUses (deadlock ) {
508
568
// deadlockCall must be a call instruction.
509
569
frame := asyncFuncs [deadlockCall .InstructionParent ().Parent ()]
510
570
0 commit comments