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