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  }