github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/compiler/goroutine.go (about) 1 package compiler 2 3 // This file implements the 'go' keyword to start a new goroutine. See 4 // goroutine-lowering.go for more details. 5 6 import ( 7 "go/token" 8 "go/types" 9 10 "golang.org/x/tools/go/ssa" 11 "tinygo.org/x/go-llvm" 12 ) 13 14 // createGo emits code to start a new goroutine. 15 func (b *builder) createGo(instr *ssa.Go) { 16 // Get all function parameters to pass to the goroutine. 17 var params []llvm.Value 18 for _, param := range instr.Call.Args { 19 params = append(params, b.getValue(param, getPos(instr))) 20 } 21 22 var prefix string 23 var funcPtr llvm.Value 24 var funcType llvm.Type 25 hasContext := false 26 if callee := instr.Call.StaticCallee(); callee != nil { 27 // Static callee is known. This makes it easier to start a new 28 // goroutine. 29 var context llvm.Value 30 switch value := instr.Call.Value.(type) { 31 case *ssa.Function: 32 // Goroutine call is regular function call. No context is necessary. 33 case *ssa.MakeClosure: 34 // A goroutine call on a func value, but the callee is trivial to find. For 35 // example: immediately applied functions. 36 funcValue := b.getValue(value, getPos(instr)) 37 context = b.extractFuncContext(funcValue) 38 default: 39 panic("StaticCallee returned an unexpected value") 40 } 41 if !context.IsNil() { 42 params = append(params, context) // context parameter 43 hasContext = true 44 } 45 funcType, funcPtr = b.getFunction(callee) 46 } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { 47 // We cheat. None of the builtins do any long or blocking operation, so 48 // we might as well run these builtins right away without the program 49 // noticing the difference. 50 // Possible exceptions: 51 // - copy: this is a possibly long operation, but not a blocking 52 // operation. Semantically it makes no difference to run it right 53 // away (not in a goroutine). However, in practice it makes no sense 54 // to run copy in a goroutine as there is no way to (safely) know 55 // when it is finished. 56 // - panic: the error message would appear in the parent goroutine. 57 // But because `go panic("err")` would halt the program anyway 58 // (there is no recover), panicking right away would give the same 59 // behavior as creating a goroutine, switching the scheduler to that 60 // goroutine, and panicking there. So this optimization seems 61 // correct. 62 // - recover: because it runs in a new goroutine, it is never a 63 // deferred function. Thus this is a no-op. 64 if builtin.Name() == "recover" { 65 // This is a no-op, even in a deferred function: 66 // go recover() 67 return 68 } 69 var argTypes []types.Type 70 var argValues []llvm.Value 71 for _, arg := range instr.Call.Args { 72 argTypes = append(argTypes, arg.Type()) 73 argValues = append(argValues, b.getValue(arg, getPos(instr))) 74 } 75 b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos()) 76 return 77 } else if instr.Call.IsInvoke() { 78 // This is a method call on an interface value. 79 itf := b.getValue(instr.Call.Value, getPos(instr)) 80 itfTypeCode := b.CreateExtractValue(itf, 0, "") 81 itfValue := b.CreateExtractValue(itf, 1, "") 82 funcPtr = b.getInvokeFunction(&instr.Call) 83 funcType = funcPtr.GlobalValueType() 84 params = append([]llvm.Value{itfValue}, params...) // start with receiver 85 params = append(params, itfTypeCode) // end with typecode 86 } else { 87 // This is a function pointer. 88 // At the moment, two extra params are passed to the newly started 89 // goroutine: 90 // * The function context, for closures. 91 // * The function pointer (for tasks). 92 var context llvm.Value 93 funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr))) 94 funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature)) 95 params = append(params, context, funcPtr) 96 hasContext = true 97 prefix = b.fn.RelString(nil) 98 } 99 100 paramBundle := b.emitPointerPack(params) 101 var stackSize llvm.Value 102 callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, instr.Pos()) 103 if b.AutomaticStackSize { 104 // The stack size is not known until after linking. Call a dummy 105 // function that will be replaced with a load from a special ELF 106 // section that contains the stack size (and is modified after 107 // linking). 108 stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) 109 stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.dataPtrType)}, "stacksize") 110 } else { 111 // The stack size is fixed at compile time. By emitting it here as a 112 // constant, it can be optimized. 113 if (b.Scheduler == "tasks" || b.Scheduler == "asyncify") && b.DefaultStackSize == 0 { 114 b.addError(instr.Pos(), "default stack size for goroutines is not set") 115 } 116 stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) 117 } 118 fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) 119 b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "") 120 } 121 122 // createGoroutineStartWrapper creates a wrapper for the task-based 123 // implementation of goroutines. For example, to call a function like this: 124 // 125 // func add(x, y int) int { ... } 126 // 127 // It creates a wrapper like this: 128 // 129 // func add$gowrapper(ptr *unsafe.Pointer) { 130 // args := (*struct{ 131 // x, y int 132 // })(ptr) 133 // add(args.x, args.y) 134 // } 135 // 136 // This is useful because the task-based goroutine start implementation only 137 // allows a single (pointer) argument to the newly started goroutine. Also, it 138 // ignores the return value because newly started goroutines do not have a 139 // return value. 140 // 141 // The hasContext parameter indicates whether the context parameter (the second 142 // to last parameter of the function) is used for this wrapper. If hasContext is 143 // false, the parameter bundle is assumed to have no context parameter and undef 144 // is passed instead. 145 func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value { 146 var wrapper llvm.Value 147 148 b := &builder{ 149 compilerContext: c, 150 Builder: c.ctx.NewBuilder(), 151 } 152 defer b.Dispose() 153 154 var deadlock llvm.Value 155 var deadlockType llvm.Type 156 if c.Scheduler == "asyncify" { 157 deadlockType, deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function)) 158 } 159 160 if !fn.IsAFunction().IsNil() { 161 // See whether this wrapper has already been created. If so, return it. 162 name := fn.Name() 163 wrapper = c.mod.NamedFunction(name + "$gowrapper") 164 if !wrapper.IsNil() { 165 return llvm.ConstPtrToInt(wrapper, c.uintptrType) 166 } 167 168 // Create the wrapper. 169 wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) 170 wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) 171 c.addStandardAttributes(wrapper) 172 wrapper.SetLinkage(llvm.LinkOnceODRLinkage) 173 wrapper.SetUnnamedAddr(true) 174 wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name)) 175 entry := c.ctx.AddBasicBlock(wrapper, "entry") 176 b.SetInsertPointAtEnd(entry) 177 178 if c.Debug { 179 pos := c.program.Fset.Position(pos) 180 diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ 181 File: c.getDIFile(pos.Filename), 182 Parameters: nil, // do not show parameters in debugger 183 Flags: 0, // ? 184 }) 185 difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{ 186 Name: "<goroutine wrapper>", 187 File: c.getDIFile(pos.Filename), 188 Line: pos.Line, 189 Type: diFuncType, 190 LocalToUnit: true, 191 IsDefinition: true, 192 ScopeLine: 0, 193 Flags: llvm.FlagPrototyped, 194 Optimized: true, 195 }) 196 wrapper.SetSubprogram(difunc) 197 b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) 198 } 199 200 // Create the list of params for the call. 201 paramTypes := fnType.ParamTypes() 202 if !hasContext { 203 paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter 204 } 205 params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) 206 if !hasContext { 207 params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter 208 } 209 210 // Create the call. 211 b.CreateCall(fnType, fn, params, "") 212 213 if c.Scheduler == "asyncify" { 214 b.CreateCall(deadlockType, deadlock, []llvm.Value{ 215 llvm.Undef(c.dataPtrType), 216 }, "") 217 } 218 219 } else { 220 // For a function pointer like this: 221 // 222 // var funcPtr func(x, y int) int 223 // 224 // A wrapper like the following is created: 225 // 226 // func .gowrapper(ptr *unsafe.Pointer) { 227 // args := (*struct{ 228 // x, y int 229 // fn func(x, y int) int 230 // })(ptr) 231 // args.fn(x, y) 232 // } 233 // 234 // With a bit of luck, identical wrapper functions like these can be 235 // merged into one. 236 237 // Create the wrapper. 238 wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) 239 wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType) 240 c.addStandardAttributes(wrapper) 241 wrapper.SetLinkage(llvm.LinkOnceODRLinkage) 242 wrapper.SetUnnamedAddr(true) 243 wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", "")) 244 entry := c.ctx.AddBasicBlock(wrapper, "entry") 245 b.SetInsertPointAtEnd(entry) 246 247 if c.Debug { 248 pos := c.program.Fset.Position(pos) 249 diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ 250 File: c.getDIFile(pos.Filename), 251 Parameters: nil, // do not show parameters in debugger 252 Flags: 0, // ? 253 }) 254 difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{ 255 Name: "<goroutine wrapper>", 256 File: c.getDIFile(pos.Filename), 257 Line: pos.Line, 258 Type: diFuncType, 259 LocalToUnit: true, 260 IsDefinition: true, 261 ScopeLine: 0, 262 Flags: llvm.FlagPrototyped, 263 Optimized: true, 264 }) 265 wrapper.SetSubprogram(difunc) 266 b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) 267 } 268 269 // Get the list of parameters, with the extra parameters at the end. 270 paramTypes := fnType.ParamTypes() 271 paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer 272 params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) 273 274 // Get the function pointer. 275 fnPtr := params[len(params)-1] 276 params = params[:len(params)-1] 277 278 // Create the call. 279 b.CreateCall(fnType, fnPtr, params, "") 280 281 if c.Scheduler == "asyncify" { 282 b.CreateCall(deadlockType, deadlock, []llvm.Value{ 283 llvm.Undef(c.dataPtrType), 284 }, "") 285 } 286 } 287 288 if c.Scheduler == "asyncify" { 289 // The goroutine was terminated via deadlock. 290 b.CreateUnreachable() 291 } else { 292 // Finish the function. Every basic block must end in a terminator, and 293 // because goroutines never return a value we can simply return void. 294 b.CreateRetVoid() 295 } 296 297 // Return a ptrtoint of the wrapper, not the function itself. 298 return b.CreatePtrToInt(wrapper, c.uintptrType, "") 299 }