mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
534 lines
19 KiB
534 lines
19 KiB
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) {
|
|
if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
|
// We cheat. None of the builtins do any long or blocking operation, so
|
|
// we might as well run these builtins right away without the program
|
|
// noticing the difference.
|
|
// Possible exceptions:
|
|
// - copy: this is a possibly long operation, but not a blocking
|
|
// operation. Semantically it makes no difference to run it right
|
|
// away (not in a goroutine). However, in practice it makes no sense
|
|
// to run copy in a goroutine as there is no way to (safely) know
|
|
// when it is finished.
|
|
// - panic: the error message would appear in the parent goroutine.
|
|
// But because `go panic("err")` would halt the program anyway
|
|
// (there is no recover), panicking right away would give the same
|
|
// behavior as creating a goroutine, switching the scheduler to that
|
|
// goroutine, and panicking there. So this optimization seems
|
|
// correct.
|
|
// - recover: because it runs in a new goroutine, it is never a
|
|
// deferred function. Thus this is a no-op.
|
|
if builtin.Name() == "recover" {
|
|
// This is a no-op, even in a deferred function:
|
|
// go recover()
|
|
return
|
|
}
|
|
var argTypes []types.Type
|
|
var argValues []llvm.Value
|
|
for _, arg := range instr.Call.Args {
|
|
argTypes = append(argTypes, arg.Type())
|
|
argValues = append(argValues, b.getValue(arg, getPos(instr)))
|
|
}
|
|
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
|
|
return
|
|
}
|
|
|
|
// Get all function parameters to pass to the goroutine.
|
|
var params []llvm.Value
|
|
for _, param := range instr.Call.Args {
|
|
params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...)
|
|
}
|
|
|
|
var prefix string
|
|
var funcPtr llvm.Value
|
|
var funcType llvm.Type
|
|
hasContext := false
|
|
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.
|
|
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, getPos(instr))
|
|
context = b.extractFuncContext(funcValue)
|
|
default:
|
|
panic("StaticCallee returned an unexpected value")
|
|
}
|
|
if !context.IsNil() {
|
|
params = append(params, context) // context parameter
|
|
hasContext = true
|
|
}
|
|
funcType, funcPtr = b.getFunction(callee)
|
|
} else if instr.Call.IsInvoke() {
|
|
// This is a method call on an interface value.
|
|
itf := b.getValue(instr.Call.Value, getPos(instr))
|
|
itfTypeCode := b.CreateExtractValue(itf, 0, "")
|
|
itfValue := b.CreateExtractValue(itf, 1, "")
|
|
funcPtr = b.getInvokeFunction(&instr.Call)
|
|
funcType = funcPtr.GlobalValueType()
|
|
params = append([]llvm.Value{itfValue}, params...) // start with receiver
|
|
params = append(params, itfTypeCode) // end with typecode
|
|
} else {
|
|
// 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, getPos(instr)))
|
|
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
|
|
params = append(params, context, funcPtr)
|
|
hasContext = true
|
|
prefix = b.fn.RelString(nil)
|
|
}
|
|
|
|
paramBundle := b.emitPointerPack(params)
|
|
var stackSize llvm.Value
|
|
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, 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).
|
|
stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
|
|
stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.dataPtrType)}, "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.Scheduler == "asyncify") && b.DefaultStackSize == 0 {
|
|
b.addError(instr.Pos(), "default stack size for goroutines is not set")
|
|
}
|
|
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
|
}
|
|
fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
|
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
|
|
}
|
|
|
|
// Create an exported wrapper function for functions with the //go:wasmexport
|
|
// pragma. This wrapper function is quite complex when the scheduler is enabled:
|
|
// it needs to start a new goroutine each time the exported function is called.
|
|
func (b *builder) createWasmExport() {
|
|
pos := b.info.wasmExportPos
|
|
if b.info.exported {
|
|
// //export really shouldn't be used anymore when //go:wasmexport is
|
|
// available, because //go:wasmexport is much better defined.
|
|
b.addError(pos, "cannot use //export and //go:wasmexport at the same time")
|
|
return
|
|
}
|
|
|
|
const suffix = "#wasmexport"
|
|
|
|
// Declare the exported function.
|
|
paramTypes := b.llvmFnType.ParamTypes()
|
|
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
|
|
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
|
|
b.addStandardAttributes(exportedFn)
|
|
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
|
|
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
|
|
|
|
// Create a builder for this wrapper function.
|
|
builder := newBuilder(b.compilerContext, b.ctx.NewBuilder(), b.fn)
|
|
defer builder.Dispose()
|
|
|
|
// Define this function as a separate function in DWARF
|
|
if b.Debug {
|
|
if b.fn.Syntax() != nil {
|
|
// Create debug info file if needed.
|
|
pos := b.program.Fset.Position(pos)
|
|
builder.difunc = builder.attachDebugInfoRaw(b.fn, exportedFn, suffix, pos.Filename, pos.Line)
|
|
}
|
|
builder.setDebugLocation(pos)
|
|
}
|
|
|
|
// Create a single basic block inside of it.
|
|
bb := llvm.AddBasicBlock(exportedFn, "entry")
|
|
builder.SetInsertPointAtEnd(bb)
|
|
|
|
// Insert an assertion to make sure this //go:wasmexport function is not
|
|
// called at a time when it is not allowed (for example, before the runtime
|
|
// is initialized).
|
|
builder.createRuntimeCall("wasmExportCheckRun", nil, "")
|
|
|
|
if b.Scheduler == "none" {
|
|
// When the scheduler has been disabled, this is really trivial: just
|
|
// call the function.
|
|
params := exportedFn.Params()
|
|
params = append(params, llvm.ConstNull(b.dataPtrType)) // context parameter
|
|
retval := builder.CreateCall(b.llvmFnType, b.llvmFn, params, "")
|
|
if b.fn.Signature.Results() == nil {
|
|
builder.CreateRetVoid()
|
|
} else {
|
|
builder.CreateRet(retval)
|
|
}
|
|
|
|
} else {
|
|
// The scheduler is enabled, so we need to start a new goroutine, wait
|
|
// for it to complete, and read the result value.
|
|
|
|
// Build a function that looks like this:
|
|
//
|
|
// func foo#wasmexport(param0, param1, ..., paramN) {
|
|
// var state *stateStruct
|
|
//
|
|
// // 'done' must be explicitly initialized ('state' is not zeroed)
|
|
// state.done = false
|
|
//
|
|
// // store the parameters in the state object
|
|
// state.param0 = param0
|
|
// state.param1 = param1
|
|
// ...
|
|
// state.paramN = paramN
|
|
//
|
|
// // create a goroutine and push it to the runqueue
|
|
// task.start(uintptr(gowrapper), &state)
|
|
//
|
|
// // run the scheduler
|
|
// runtime.wasmExportRun(&state.done)
|
|
//
|
|
// // if there is a return value, load it and return
|
|
// return state.result
|
|
// }
|
|
|
|
hasReturn := b.fn.Signature.Results() != nil
|
|
|
|
// Build the state struct type.
|
|
// It stores the function parameters, the 'done' flag, and reserves
|
|
// space for a return value if needed.
|
|
stateFields := exportedFnType.ParamTypes()
|
|
numParams := len(stateFields)
|
|
stateFields = append(stateFields, b.ctx.Int1Type()) // 'done' field
|
|
if hasReturn {
|
|
stateFields = append(stateFields, b.llvmFnType.ReturnType())
|
|
}
|
|
stateStruct := b.ctx.StructType(stateFields, false)
|
|
|
|
// Allocate the state struct on the stack.
|
|
statePtr := builder.CreateAlloca(stateStruct, "status")
|
|
|
|
// Initialize the 'done' field.
|
|
doneGEP := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams), false),
|
|
}, "done.gep")
|
|
builder.CreateStore(llvm.ConstNull(b.ctx.Int1Type()), doneGEP)
|
|
|
|
// Store all parameters in the state object.
|
|
for i, param := range exportedFn.Params() {
|
|
gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
|
}, "")
|
|
builder.CreateStore(param, gep)
|
|
}
|
|
|
|
// Create a new goroutine and add it to the runqueue.
|
|
wrapper := b.createGoroutineStartWrapper(b.llvmFnType, b.llvmFn, "", false, true, pos)
|
|
stackSize := llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
|
taskStartFnType, taskStartFn := builder.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
|
builder.createCall(taskStartFnType, taskStartFn, []llvm.Value{wrapper, statePtr, stackSize, llvm.Undef(b.dataPtrType)}, "")
|
|
|
|
// Run the scheduler.
|
|
builder.createRuntimeCall("wasmExportRun", []llvm.Value{doneGEP}, "")
|
|
|
|
// Read the return value (if any) and return to the caller of the
|
|
// //go:wasmexport function.
|
|
if hasReturn {
|
|
gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams)+1, false),
|
|
}, "")
|
|
retval := builder.CreateLoad(b.llvmFnType.ReturnType(), gep, "retval")
|
|
builder.CreateRet(retval)
|
|
} else {
|
|
builder.CreateRetVoid()
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// The hasContext parameter indicates whether the context parameter (the second
|
|
// to last parameter of the function) is used for this wrapper. If hasContext is
|
|
// false, the parameter bundle is assumed to have no context parameter and undef
|
|
// is passed instead.
|
|
func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext, isWasmExport bool, pos token.Pos) llvm.Value {
|
|
var wrapper llvm.Value
|
|
|
|
b := &builder{
|
|
compilerContext: c,
|
|
Builder: c.ctx.NewBuilder(),
|
|
}
|
|
defer b.Dispose()
|
|
|
|
var deadlock llvm.Value
|
|
var deadlockType llvm.Type
|
|
if c.Scheduler == "asyncify" {
|
|
deadlockType, deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function))
|
|
}
|
|
|
|
if !fn.IsAFunction().IsNil() {
|
|
// See whether this wrapper has already been created. If so, return it.
|
|
name := fn.Name()
|
|
wrapperName := name + "$gowrapper"
|
|
if isWasmExport {
|
|
wrapperName += "-wasmexport"
|
|
}
|
|
wrapper = c.mod.NamedFunction(wrapperName)
|
|
if !wrapper.IsNil() {
|
|
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
|
}
|
|
|
|
// Create the wrapper.
|
|
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
|
|
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType)
|
|
c.addStandardAttributes(wrapper)
|
|
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
wrapper.SetUnnamedAddr(true)
|
|
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
|
|
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
|
b.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)
|
|
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
|
}
|
|
|
|
if !isWasmExport {
|
|
// Regular 'go' instruction.
|
|
|
|
// Create the list of params for the call.
|
|
paramTypes := fnType.ParamTypes()
|
|
if !hasContext {
|
|
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
|
|
}
|
|
|
|
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
|
|
if !hasContext {
|
|
params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter
|
|
}
|
|
|
|
// Create the call.
|
|
b.CreateCall(fnType, fn, params, "")
|
|
|
|
if c.Scheduler == "asyncify" {
|
|
b.CreateCall(deadlockType, deadlock, []llvm.Value{
|
|
llvm.Undef(c.dataPtrType),
|
|
}, "")
|
|
}
|
|
} else {
|
|
// Goroutine started from a //go:wasmexport pragma.
|
|
// The function looks like this:
|
|
//
|
|
// func foo$gowrapper-wasmexport(state *stateStruct) {
|
|
// // load values
|
|
// param0 := state.params[0]
|
|
// param1 := state.params[1]
|
|
//
|
|
// // call wrapped functions
|
|
// result := foo(param0, param1, ...)
|
|
//
|
|
// // store result value (if there is any)
|
|
// state.result = result
|
|
//
|
|
// // finish exported function
|
|
// state.done = true
|
|
// runtime.wasmExportExit()
|
|
// }
|
|
//
|
|
// The state object here looks like:
|
|
//
|
|
// struct state {
|
|
// param0
|
|
// param1
|
|
// param* // etc
|
|
// done bool
|
|
// result returnType
|
|
// }
|
|
|
|
returnType := fnType.ReturnType()
|
|
hasReturn := returnType != b.ctx.VoidType()
|
|
statePtr := wrapper.Param(0)
|
|
|
|
// Create the state struct (it must match the type in createWasmExport).
|
|
stateFields := fnType.ParamTypes()
|
|
numParams := len(stateFields) - 1
|
|
stateFields = stateFields[:numParams:numParams] // strip 'context' parameter
|
|
stateFields = append(stateFields, c.ctx.Int1Type()) // 'done' bool
|
|
if hasReturn {
|
|
stateFields = append(stateFields, returnType)
|
|
}
|
|
stateStruct := b.ctx.StructType(stateFields, false)
|
|
|
|
// Extract parameters from the state object, and call the function
|
|
// that's being wrapped.
|
|
var callParams []llvm.Value
|
|
for i := 0; i < numParams; i++ {
|
|
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
|
}, "")
|
|
param := b.CreateLoad(stateFields[i], gep, "")
|
|
callParams = append(callParams, param)
|
|
}
|
|
callParams = append(callParams, llvm.ConstNull(c.dataPtrType)) // add 'context' parameter
|
|
result := b.CreateCall(fnType, fn, callParams, "")
|
|
|
|
// Store the return value back into the shared state.
|
|
// Unlike regular goroutines, these special //go:wasmexport
|
|
// goroutines can return a value.
|
|
if hasReturn {
|
|
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams)+1, false),
|
|
}, "result.ptr")
|
|
b.CreateStore(result, gep)
|
|
}
|
|
|
|
// Mark this function as having finished executing.
|
|
// This is important so the runtime knows the exported function
|
|
// didn't block.
|
|
doneGEP := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams), false),
|
|
}, "done.gep")
|
|
b.CreateStore(llvm.ConstInt(b.ctx.Int1Type(), 1, false), doneGEP)
|
|
|
|
// Call back into the runtime. This will exit the goroutine, switch
|
|
// back to the scheduler, which will in turn return from the
|
|
// //go:wasmexport function.
|
|
b.createRuntimeCall("wasmExportExit", nil, "")
|
|
}
|
|
|
|
} 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.dataPtrType}, false)
|
|
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
|
|
c.addStandardAttributes(wrapper)
|
|
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
wrapper.SetUnnamedAddr(true)
|
|
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
|
|
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
|
b.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)
|
|
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
|
}
|
|
|
|
// Get the list of parameters, with the extra parameters at the end.
|
|
paramTypes := fnType.ParamTypes()
|
|
paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer
|
|
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
|
|
|
|
// Get the function pointer.
|
|
fnPtr := params[len(params)-1]
|
|
params = params[:len(params)-1]
|
|
|
|
// Create the call.
|
|
b.CreateCall(fnType, fnPtr, params, "")
|
|
|
|
if c.Scheduler == "asyncify" {
|
|
b.CreateCall(deadlockType, deadlock, []llvm.Value{
|
|
llvm.Undef(c.dataPtrType),
|
|
}, "")
|
|
}
|
|
}
|
|
|
|
if c.Scheduler == "asyncify" {
|
|
// The goroutine was terminated via deadlock.
|
|
b.CreateUnreachable()
|
|
} else {
|
|
// Finish the function. Every basic block must end in a terminator, and
|
|
// because goroutines never return a value we can simply return void.
|
|
b.CreateRetVoid()
|
|
}
|
|
|
|
// Return a ptrtoint of the wrapper, not the function itself.
|
|
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
|
}
|
|
|