
Move the code from the compiler.go file to the goroutine.go file, which is a more appropriate place. This keeps all the goroutine related code in one file, to make it easier to find.
241 строка
8,6 КиБ
Go
241 строка
8,6 КиБ
Go
package compiler
|
|
|
|
// This file implements the 'go' keyword to start a new goroutine. See
|
|
// goroutine-lowering.go for more details.
|
|
|
|
import (
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
|
"golang.org/x/tools/go/ssa"
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// createGo emits code to start a new goroutine.
|
|
func (b *builder) createGo(instr *ssa.Go) {
|
|
// Get all function parameters to pass to the goroutine.
|
|
var params []llvm.Value
|
|
for _, param := range instr.Call.Args {
|
|
params = append(params, b.getValue(param))
|
|
}
|
|
|
|
var prefix string
|
|
var funcPtr llvm.Value
|
|
if callee := instr.Call.StaticCallee(); callee != nil {
|
|
// Static callee is known. This makes it easier to start a new
|
|
// goroutine.
|
|
var context llvm.Value
|
|
switch value := instr.Call.Value.(type) {
|
|
case *ssa.Function:
|
|
// Goroutine call is regular function call. No context is necessary.
|
|
context = llvm.Undef(b.i8ptrType)
|
|
case *ssa.MakeClosure:
|
|
// A goroutine call on a func value, but the callee is trivial to find. For
|
|
// example: immediately applied functions.
|
|
funcValue := b.getValue(value)
|
|
context = b.extractFuncContext(funcValue)
|
|
default:
|
|
panic("StaticCallee returned an unexpected value")
|
|
}
|
|
params = append(params, context) // context parameter
|
|
funcPtr = b.getFunction(callee)
|
|
} else if !instr.Call.IsInvoke() {
|
|
// This is a function pointer.
|
|
// At the moment, two extra params are passed to the newly started
|
|
// goroutine:
|
|
// * The function context, for closures.
|
|
// * The function pointer (for tasks).
|
|
var context llvm.Value
|
|
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
|
|
params = append(params, context) // context parameter
|
|
switch b.Scheduler {
|
|
case "none", "coroutines":
|
|
// There are no additional parameters needed for the goroutine start operation.
|
|
case "tasks":
|
|
// Add the function pointer as a parameter to start the goroutine.
|
|
params = append(params, funcPtr)
|
|
default:
|
|
panic("unknown scheduler type")
|
|
}
|
|
prefix = b.fn.RelString(nil)
|
|
} else {
|
|
b.addError(instr.Pos(), "todo: go on interface call")
|
|
return
|
|
}
|
|
|
|
paramBundle := b.emitPointerPack(params)
|
|
var callee, stackSize llvm.Value
|
|
switch b.Scheduler {
|
|
case "none", "tasks":
|
|
callee = b.createGoroutineStartWrapper(funcPtr, prefix, instr.Pos())
|
|
if b.AutomaticStackSize {
|
|
// The stack size is not known until after linking. Call a dummy
|
|
// function that will be replaced with a load from a special ELF
|
|
// section that contains the stack size (and is modified after
|
|
// linking).
|
|
stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
|
|
stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
|
|
} else {
|
|
// The stack size is fixed at compile time. By emitting it here as a
|
|
// constant, it can be optimized.
|
|
if b.Scheduler == "tasks" && b.DefaultStackSize == 0 {
|
|
b.addError(instr.Pos(), "default stack size for goroutines is not set")
|
|
}
|
|
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
|
}
|
|
case "coroutines":
|
|
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
|
|
// There is no goroutine stack size: coroutines are used instead of
|
|
// stacks.
|
|
stackSize = llvm.Undef(b.uintptrType)
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
|
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
|
|
}
|
|
|
|
// createGoroutineStartWrapper creates a wrapper for the task-based
|
|
// implementation of goroutines. For example, to call a function like this:
|
|
//
|
|
// func add(x, y int) int { ... }
|
|
//
|
|
// It creates a wrapper like this:
|
|
//
|
|
// func add$gowrapper(ptr *unsafe.Pointer) {
|
|
// args := (*struct{
|
|
// x, y int
|
|
// })(ptr)
|
|
// add(args.x, args.y)
|
|
// }
|
|
//
|
|
// This is useful because the task-based goroutine start implementation only
|
|
// allows a single (pointer) argument to the newly started goroutine. Also, it
|
|
// ignores the return value because newly started goroutines do not have a
|
|
// return value.
|
|
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
|
|
var wrapper llvm.Value
|
|
|
|
builder := c.ctx.NewBuilder()
|
|
defer builder.Dispose()
|
|
|
|
if !fn.IsAFunction().IsNil() {
|
|
// See whether this wrapper has already been created. If so, return it.
|
|
name := fn.Name()
|
|
wrapper = c.mod.NamedFunction(name + "$gowrapper")
|
|
if !wrapper.IsNil() {
|
|
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
|
}
|
|
|
|
// Create the wrapper.
|
|
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
|
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
|
|
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
wrapper.SetUnnamedAddr(true)
|
|
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
|
|
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
|
builder.SetInsertPointAtEnd(entry)
|
|
|
|
if c.Debug {
|
|
pos := c.program.Fset.Position(pos)
|
|
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
|
File: c.getDIFile(pos.Filename),
|
|
Parameters: nil, // do not show parameters in debugger
|
|
Flags: 0, // ?
|
|
})
|
|
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
|
|
Name: "<goroutine wrapper>",
|
|
File: c.getDIFile(pos.Filename),
|
|
Line: pos.Line,
|
|
Type: diFuncType,
|
|
LocalToUnit: true,
|
|
IsDefinition: true,
|
|
ScopeLine: 0,
|
|
Flags: llvm.FlagPrototyped,
|
|
Optimized: true,
|
|
})
|
|
wrapper.SetSubprogram(difunc)
|
|
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
|
}
|
|
|
|
// Create the list of params for the call.
|
|
paramTypes := fn.Type().ElementType().ParamTypes()
|
|
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
|
|
params = append(params, llvm.Undef(c.i8ptrType))
|
|
|
|
// Create the call.
|
|
builder.CreateCall(fn, params, "")
|
|
|
|
} else {
|
|
// For a function pointer like this:
|
|
//
|
|
// var funcPtr func(x, y int) int
|
|
//
|
|
// A wrapper like the following is created:
|
|
//
|
|
// func .gowrapper(ptr *unsafe.Pointer) {
|
|
// args := (*struct{
|
|
// x, y int
|
|
// fn func(x, y int) int
|
|
// })(ptr)
|
|
// args.fn(x, y)
|
|
// }
|
|
//
|
|
// With a bit of luck, identical wrapper functions like these can be
|
|
// merged into one.
|
|
|
|
// Create the wrapper.
|
|
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
|
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
|
|
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
wrapper.SetUnnamedAddr(true)
|
|
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
|
|
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
|
builder.SetInsertPointAtEnd(entry)
|
|
|
|
if c.Debug {
|
|
pos := c.program.Fset.Position(pos)
|
|
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
|
File: c.getDIFile(pos.Filename),
|
|
Parameters: nil, // do not show parameters in debugger
|
|
Flags: 0, // ?
|
|
})
|
|
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
|
|
Name: "<goroutine wrapper>",
|
|
File: c.getDIFile(pos.Filename),
|
|
Line: pos.Line,
|
|
Type: diFuncType,
|
|
LocalToUnit: true,
|
|
IsDefinition: true,
|
|
ScopeLine: 0,
|
|
Flags: llvm.FlagPrototyped,
|
|
Optimized: true,
|
|
})
|
|
wrapper.SetSubprogram(difunc)
|
|
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
|
}
|
|
|
|
// Get the list of parameters, with the extra parameters at the end.
|
|
paramTypes := fn.Type().ElementType().ParamTypes()
|
|
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
|
|
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
|
|
|
|
// Get the function pointer.
|
|
fnPtr := params[len(params)-1]
|
|
|
|
// Ignore the last param, which isn't used anymore.
|
|
// TODO: avoid this extra "parent handle" parameter in most functions.
|
|
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
|
|
|
// Create the call.
|
|
builder.CreateCall(fnPtr, params, "")
|
|
}
|
|
|
|
// Finish the function. Every basic block must end in a terminator, and
|
|
// because goroutines never return a value we can simply return void.
|
|
builder.CreateRetVoid()
|
|
|
|
// Return a ptrtoint of the wrapper, not the function itself.
|
|
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
|
|
}
|