goroutine.go raw
1 package compiler
2
3 // This file implements goroutine start wrappers used by spawn, //go:wasmexport,
4 // and runtime-internal 'go' calls. User code cannot use 'go' (enforced in
5 // compiler.go); runtime/internal packages are exempt.
6
7 import (
8 "go/token"
9 "go/types"
10
11 "moxie/compiler/llvmutil"
12 "golang.org/x/tools/go/ssa"
13 "tinygo.org/x/go-llvm"
14 )
15
16 // createGo emits code to start a new goroutine (runtime/internal only).
17 func (b *builder) createGo(instr *ssa.Go) {
18 if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
19 if builtin.Name() == "recover" {
20 return
21 }
22 var argTypes []types.Type
23 var argValues []llvm.Value
24 for _, arg := range instr.Call.Args {
25 argTypes = append(argTypes, arg.Type())
26 argValues = append(argValues, b.getValue(arg, getPos(instr)))
27 }
28 b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
29 return
30 }
31
32 var params []llvm.Value
33 for _, param := range instr.Call.Args {
34 params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...)
35 }
36
37 var prefix string
38 var funcPtr llvm.Value
39 var funcType llvm.Type
40 hasContext := false
41 if callee := instr.Call.StaticCallee(); callee != nil {
42 var context llvm.Value
43 switch value := instr.Call.Value.(type) {
44 case *ssa.Function:
45 // Regular function call. No context.
46 case *ssa.MakeClosure:
47 funcValue := b.getValue(value, getPos(instr))
48 context = b.extractFuncContext(funcValue)
49 default:
50 panic("StaticCallee returned an unexpected value")
51 }
52 if !context.IsNil() {
53 params = append(params, context)
54 hasContext = true
55 }
56 funcType, funcPtr = b.getFunction(callee)
57 } else if instr.Call.IsInvoke() {
58 itf := b.getValue(instr.Call.Value, getPos(instr))
59 itfTypeCode := b.CreateExtractValue(itf, 0, "")
60 itfValue := b.CreateExtractValue(itf, 1, "")
61 funcPtr = b.getInvokeFunction(&instr.Call)
62 funcType = funcPtr.GlobalValueType()
63 params = append([]llvm.Value{itfValue}, params...)
64 params = append(params, itfTypeCode)
65 } else {
66 var context llvm.Value
67 funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)))
68 funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
69 params = append(params, context, funcPtr)
70 hasContext = true
71 prefix = b.fn.RelString(nil)
72 }
73
74 paramBundle := b.emitPointerPack(params)
75 var stackSize llvm.Value
76 callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
77 if b.AutomaticStackSize {
78 stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
79 stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.dataPtrType)}, "stacksize")
80 } else {
81 if (b.Scheduler == "tasks" || b.Scheduler == "asyncify") && b.DefaultStackSize == 0 {
82 b.addError(instr.Pos(), "default stack size for goroutines is not set")
83 }
84 stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
85 }
86 fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
87 b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
88 }
89
90 // Create an exported wrapper function for functions with the //go:wasmexport
91 // pragma. This wrapper function is quite complex when the scheduler is enabled:
92 // it needs to start a new goroutine each time the exported function is called.
93 func (b *builder) createWasmExport() {
94 pos := b.info.wasmExportPos
95 if b.info.exported {
96 // //export really shouldn't be used anymore when //go:wasmexport is
97 // available, because //go:wasmexport is much better defined.
98 b.addError(pos, "cannot use //export and //go:wasmexport at the same time")
99 return
100 }
101
102 const suffix = "#wasmexport"
103
104 // Declare the exported function.
105 paramTypes := b.llvmFnType.ParamTypes()
106 exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
107 exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
108 b.addStandardAttributes(exportedFn)
109 llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
110 exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
111
112 // Create a builder for this wrapper function.
113 builder := newBuilder(b.compilerContext, b.ctx.NewBuilder(), b.fn)
114 defer builder.Dispose()
115
116 // Define this function as a separate function in DWARF
117 if b.Debug {
118 if b.fn.Syntax() != nil {
119 // Create debug info file if needed.
120 pos := b.program.Fset.Position(pos)
121 builder.difunc = builder.attachDebugInfoRaw(b.fn, exportedFn, suffix, pos.Filename, pos.Line)
122 }
123 builder.setDebugLocation(pos)
124 }
125
126 // Create a single basic block inside of it.
127 bb := llvm.AddBasicBlock(exportedFn, "entry")
128 builder.SetInsertPointAtEnd(bb)
129
130 // Insert an assertion to make sure this //go:wasmexport function is not
131 // called at a time when it is not allowed (for example, before the runtime
132 // is initialized).
133 builder.createRuntimeCall("wasmExportCheckRun", nil, "")
134
135 if b.Scheduler == "none" {
136 // When the scheduler has been disabled, this is really trivial: just
137 // call the function.
138 params := exportedFn.Params()
139 params = append(params, llvm.ConstNull(b.dataPtrType)) // context parameter
140 retval := builder.CreateCall(b.llvmFnType, b.llvmFn, params, "")
141 if b.fn.Signature.Results() == nil {
142 builder.CreateRetVoid()
143 } else {
144 builder.CreateRet(retval)
145 }
146
147 } else {
148 // The scheduler is enabled, so we need to start a new goroutine, wait
149 // for it to complete, and read the result value.
150
151 // Build a function that looks like this:
152 //
153 // func foo#wasmexport(param0, param1, ..., paramN) {
154 // var state *stateStruct
155 //
156 // // 'done' must be explicitly initialized ('state' is not zeroed)
157 // state.done = false
158 //
159 // // store the parameters in the state object
160 // state.param0 = param0
161 // state.param1 = param1
162 // ...
163 // state.paramN = paramN
164 //
165 // // create a goroutine and push it to the runqueue
166 // task.start(uintptr(gowrapper), &state)
167 //
168 // // run the scheduler
169 // runtime.wasmExportRun(&state.done)
170 //
171 // // if there is a return value, load it and return
172 // return state.result
173 // }
174
175 hasReturn := b.fn.Signature.Results() != nil
176
177 // Build the state struct type.
178 // It stores the function parameters, the 'done' flag, and reserves
179 // space for a return value if needed.
180 stateFields := exportedFnType.ParamTypes()
181 numParams := len(stateFields)
182 stateFields = append(stateFields, b.ctx.Int1Type()) // 'done' field
183 if hasReturn {
184 stateFields = append(stateFields, b.llvmFnType.ReturnType())
185 }
186 stateStruct := b.ctx.StructType(stateFields, false)
187
188 // Allocate the state struct on the stack.
189 statePtr := builder.CreateAlloca(stateStruct, "status")
190
191 // Initialize the 'done' field.
192 doneGEP := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
193 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
194 llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams), false),
195 }, "done.gep")
196 builder.CreateStore(llvm.ConstNull(b.ctx.Int1Type()), doneGEP)
197
198 // Store all parameters in the state object.
199 for i, param := range exportedFn.Params() {
200 gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
201 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
202 llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
203 }, "")
204 builder.CreateStore(param, gep)
205 }
206
207 // Create a new goroutine and add it to the runqueue.
208 wrapper := b.createGoroutineStartWrapper(b.llvmFnType, b.llvmFn, "", false, true, pos)
209 stackSize := llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
210 taskStartFnType, taskStartFn := builder.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
211 builder.createCall(taskStartFnType, taskStartFn, []llvm.Value{wrapper, statePtr, stackSize, llvm.Undef(b.dataPtrType)}, "")
212
213 // Run the scheduler.
214 builder.createRuntimeCall("wasmExportRun", []llvm.Value{doneGEP}, "")
215
216 // Read the return value (if any) and return to the caller of the
217 // //go:wasmexport function.
218 if hasReturn {
219 gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
220 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
221 llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams)+1, false),
222 }, "")
223 retval := builder.CreateLoad(b.llvmFnType.ReturnType(), gep, "retval")
224 builder.CreateRet(retval)
225 } else {
226 builder.CreateRetVoid()
227 }
228 }
229 }
230
231 // createGoroutineStartWrapper creates a wrapper for the task-based
232 // implementation of goroutines. For example, to call a function like this:
233 //
234 // func add(x, y int) int { ... }
235 //
236 // It creates a wrapper like this:
237 //
238 // func add$gowrapper(ptr *unsafe.Pointer) {
239 // args := (*struct{
240 // x, y int
241 // })(ptr)
242 // add(args.x, args.y)
243 // }
244 //
245 // This is useful because the task-based goroutine start implementation only
246 // allows a single (pointer) argument to the newly started goroutine. Also, it
247 // ignores the return value because newly started goroutines do not have a
248 // return value.
249 //
250 // The hasContext parameter indicates whether the context parameter (the second
251 // to last parameter of the function) is used for this wrapper. If hasContext is
252 // false, the parameter bundle is assumed to have no context parameter and undef
253 // is passed instead.
254 func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext, isWasmExport bool, pos token.Pos) llvm.Value {
255 var wrapper llvm.Value
256
257 b := &builder{
258 compilerContext: c,
259 Builder: c.ctx.NewBuilder(),
260 }
261 defer b.Dispose()
262
263 var deadlock llvm.Value
264 var deadlockType llvm.Type
265 if c.Scheduler == "asyncify" {
266 deadlockType, deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function))
267 }
268
269 if !fn.IsAFunction().IsNil() {
270 // See whether this wrapper has already been created. If so, return it.
271 name := fn.Name()
272 wrapperName := name + "$gowrapper"
273 if isWasmExport {
274 wrapperName += "-wasmexport"
275 }
276 wrapper = c.mod.NamedFunction(wrapperName)
277 if !wrapper.IsNil() {
278 return llvm.ConstPtrToInt(wrapper, c.uintptrType)
279 }
280
281 // Create the wrapper.
282 wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
283 wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType)
284 c.addStandardAttributes(wrapper)
285 wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
286 wrapper.SetUnnamedAddr(true)
287 wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("moxie-gowrapper", name))
288 entry := c.ctx.AddBasicBlock(wrapper, "entry")
289 b.SetInsertPointAtEnd(entry)
290
291 if c.Debug {
292 pos := c.program.Fset.Position(pos)
293 diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
294 File: c.getDIFile(pos.Filename),
295 Parameters: nil, // do not show parameters in debugger
296 Flags: 0, // ?
297 })
298 difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
299 Name: "<goroutine wrapper>",
300 File: c.getDIFile(pos.Filename),
301 Line: pos.Line,
302 Type: diFuncType,
303 LocalToUnit: true,
304 IsDefinition: true,
305 ScopeLine: 0,
306 Flags: llvm.FlagPrototyped,
307 Optimized: true,
308 })
309 wrapper.SetSubprogram(difunc)
310 b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
311 }
312
313 if !isWasmExport {
314 // Regular 'go' instruction.
315
316 // Create the list of params for the call.
317 paramTypes := fnType.ParamTypes()
318 if !hasContext {
319 paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
320 }
321
322 params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
323 if !hasContext {
324 params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter
325 }
326
327 // Create the call.
328 b.CreateCall(fnType, fn, params, "")
329
330 if c.Scheduler == "asyncify" {
331 b.CreateCall(deadlockType, deadlock, []llvm.Value{
332 llvm.Undef(c.dataPtrType),
333 }, "")
334 }
335 } else {
336 // Goroutine started from a //go:wasmexport pragma.
337 // The function looks like this:
338 //
339 // func foo$gowrapper-wasmexport(state *stateStruct) {
340 // // load values
341 // param0 := state.params[0]
342 // param1 := state.params[1]
343 //
344 // // call wrapped functions
345 // result := foo(param0, param1, ...)
346 //
347 // // store result value (if there is any)
348 // state.result = result
349 //
350 // // finish exported function
351 // state.done = true
352 // runtime.wasmExportExit()
353 // }
354 //
355 // The state object here looks like:
356 //
357 // struct state {
358 // param0
359 // param1
360 // param* // etc
361 // done bool
362 // result returnType
363 // }
364
365 returnType := fnType.ReturnType()
366 hasReturn := returnType != b.ctx.VoidType()
367 statePtr := wrapper.Param(0)
368
369 // Create the state struct (it must match the type in createWasmExport).
370 stateFields := fnType.ParamTypes()
371 numParams := len(stateFields) - 1
372 stateFields = stateFields[:numParams:numParams] // strip 'context' parameter
373 stateFields = append(stateFields, c.ctx.Int1Type()) // 'done' bool
374 if hasReturn {
375 stateFields = append(stateFields, returnType)
376 }
377 stateStruct := b.ctx.StructType(stateFields, false)
378
379 // Extract parameters from the state object, and call the function
380 // that's being wrapped.
381 var callParams []llvm.Value
382 for i := 0; i < numParams; i++ {
383 gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
384 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
385 llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
386 }, "")
387 param := b.CreateLoad(stateFields[i], gep, "")
388 callParams = append(callParams, param)
389 }
390 callParams = append(callParams, llvm.ConstNull(c.dataPtrType)) // add 'context' parameter
391 result := b.CreateCall(fnType, fn, callParams, "")
392
393 // Store the return value back into the shared state.
394 // Unlike regular goroutines, these special //go:wasmexport
395 // goroutines can return a value.
396 if hasReturn {
397 gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
398 llvm.ConstInt(c.ctx.Int32Type(), 0, false),
399 llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams)+1, false),
400 }, "result.ptr")
401 b.CreateStore(result, gep)
402 }
403
404 // Mark this function as having finished executing.
405 // This is important so the runtime knows the exported function
406 // didn't block.
407 doneGEP := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
408 llvm.ConstInt(c.ctx.Int32Type(), 0, false),
409 llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams), false),
410 }, "done.gep")
411 b.CreateStore(llvm.ConstInt(b.ctx.Int1Type(), 1, false), doneGEP)
412
413 // Call back into the runtime. This will exit the goroutine, switch
414 // back to the scheduler, which will in turn return from the
415 // //go:wasmexport function.
416 b.createRuntimeCall("wasmExportExit", nil, "")
417 }
418
419 } else {
420 // For a function pointer like this:
421 //
422 // var funcPtr func(x, y int) int
423 //
424 // A wrapper like the following is created:
425 //
426 // func .gowrapper(ptr *unsafe.Pointer) {
427 // args := (*struct{
428 // x, y int
429 // fn func(x, y int) int
430 // })(ptr)
431 // args.fn(x, y)
432 // }
433 //
434 // With a bit of luck, identical wrapper functions like these can be
435 // merged into one.
436
437 // Create the wrapper.
438 wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
439 wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
440 c.addStandardAttributes(wrapper)
441 wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
442 wrapper.SetUnnamedAddr(true)
443 wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("moxie-gowrapper", ""))
444 entry := c.ctx.AddBasicBlock(wrapper, "entry")
445 b.SetInsertPointAtEnd(entry)
446
447 if c.Debug {
448 pos := c.program.Fset.Position(pos)
449 diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
450 File: c.getDIFile(pos.Filename),
451 Parameters: nil, // do not show parameters in debugger
452 Flags: 0, // ?
453 })
454 difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
455 Name: "<goroutine wrapper>",
456 File: c.getDIFile(pos.Filename),
457 Line: pos.Line,
458 Type: diFuncType,
459 LocalToUnit: true,
460 IsDefinition: true,
461 ScopeLine: 0,
462 Flags: llvm.FlagPrototyped,
463 Optimized: true,
464 })
465 wrapper.SetSubprogram(difunc)
466 b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
467 }
468
469 // Get the list of parameters, with the extra parameters at the end.
470 paramTypes := fnType.ParamTypes()
471 paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer
472 params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
473
474 // Get the function pointer.
475 fnPtr := params[len(params)-1]
476 params = params[:len(params)-1]
477
478 // Create the call.
479 b.CreateCall(fnType, fnPtr, params, "")
480
481 if c.Scheduler == "asyncify" {
482 b.CreateCall(deadlockType, deadlock, []llvm.Value{
483 llvm.Undef(c.dataPtrType),
484 }, "")
485 }
486 }
487
488 if c.Scheduler == "asyncify" {
489 // The goroutine was terminated via deadlock.
490 b.CreateUnreachable()
491 } else {
492 // Finish the function. Every basic block must end in a terminator, and
493 // because goroutines never return a value we can simply return void.
494 b.CreateRetVoid()
495 }
496
497 // Return a ptrtoint of the wrapper, not the function itself.
498 return llvm.ConstPtrToInt(wrapper, c.uintptrType)
499 }
500