internal/task: remove coroutines

Этот коммит содержится в:
Nia Waldvogel 2021-12-31 13:36:33 -05:00 коммит произвёл Nia
родитель d054d4d512
коммит ea2a6b70b2
21 изменённых файлов: 41 добавлений и 1962 удалений

Просмотреть файл

@ -114,7 +114,7 @@ func (c *Config) NeedsStackObjects() bool {
} }
// Scheduler returns the scheduler implementation. Valid values are "none", // Scheduler returns the scheduler implementation. Valid values are "none",
//"coroutines" and "tasks". // "asyncify" and "tasks".
func (c *Config) Scheduler() string { func (c *Config) Scheduler() string {
if c.Options.Scheduler != "" { if c.Options.Scheduler != "" {
return c.Options.Scheduler return c.Options.Scheduler
@ -122,8 +122,8 @@ func (c *Config) Scheduler() string {
if c.Target.Scheduler != "" { if c.Target.Scheduler != "" {
return c.Target.Scheduler return c.Target.Scheduler
} }
// Fall back to coroutines, which are supported everywhere. // Fall back to none.
return "coroutines" return "none"
} }
// Serial returns the serial implementation for this build configuration: uart, // Serial returns the serial implementation for this build configuration: uart,
@ -171,13 +171,10 @@ func (c *Config) FuncImplementation() string {
// being pointed to doesn't need a context. The function pointer is a // being pointed to doesn't need a context. The function pointer is a
// regular function pointer. // regular function pointer.
return "doubleword" return "doubleword"
case "none", "coroutines": case "none":
// As "doubleword", but with the function pointer replaced by a unique // As "doubleword", but with the function pointer replaced by a unique
// ID per function signature. Function values are called by using a // ID per function signature. Function values are called by using a
// switch statement and choosing which function to call. // switch statement and choosing which function to call.
// Pick the switch implementation with the coroutines scheduler, as it
// allows the use of blocking inside a function that is used as a func
// value.
return "switch" return "switch"
default: default:
panic("unknown scheduler type") panic("unknown scheduler type")

Просмотреть файл

@ -8,7 +8,7 @@ import (
var ( var (
validGCOptions = []string{"none", "leaking", "conservative"} validGCOptions = []string{"none", "leaking", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "coroutines", "asyncify"} validSchedulerOptions = []string{"none", "tasks", "asyncify"}
validSerialOptions = []string{"none", "uart", "usb"} validSerialOptions = []string{"none", "uart", "usb"}
validPrintSizeOptions = []string{"none", "short", "full"} validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"} validPanicStrategyOptions = []string{"print", "trap"}

Просмотреть файл

@ -10,7 +10,7 @@ import (
func TestVerifyOptions(t *testing.T) { func TestVerifyOptions(t *testing.T) {
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative`) expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines, asyncify`) expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`) expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`)
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
@ -67,12 +67,6 @@ func TestVerifyOptions(t *testing.T) {
Scheduler: "tasks", Scheduler: "tasks",
}, },
}, },
{
name: "SchedulerOptionCoroutines",
opts: compileopts.Options{
Scheduler: "coroutines",
},
},
{ {
name: "InvalidPrintSizeOption", name: "InvalidPrintSizeOption",
opts: compileopts.Options{ opts: compileopts.Options{

Просмотреть файл

@ -55,10 +55,9 @@ func TestCompiler(t *testing.T) {
{"string.go", "", ""}, {"string.go", "", ""},
{"float.go", "", ""}, {"float.go", "", ""},
{"interface.go", "", ""}, {"interface.go", "", ""},
{"func.go", "", "coroutines"}, {"func.go", "", "none"},
{"pragma.go", "", ""}, {"pragma.go", "", ""},
{"goroutine.go", "wasm", "asyncify"}, {"goroutine.go", "wasm", "asyncify"},
{"goroutine.go", "wasm", "coroutines"},
{"goroutine.go", "cortex-m-qemu", "tasks"}, {"goroutine.go", "cortex-m-qemu", "tasks"},
{"channel.go", "", ""}, {"channel.go", "", ""},
{"intrinsics.go", "cortex-m-qemu", ""}, {"intrinsics.go", "cortex-m-qemu", ""},

Просмотреть файл

@ -30,11 +30,6 @@ func (b *builder) createGo(instr *ssa.Go) {
switch value := instr.Call.Value.(type) { switch value := instr.Call.Value.(type) {
case *ssa.Function: case *ssa.Function:
// Goroutine call is regular function call. No context is necessary. // Goroutine call is regular function call. No context is necessary.
if b.Scheduler == "coroutines" {
// The context parameter is assumed to be always present in the
// coroutines scheduler.
context = llvm.Undef(b.i8ptrType)
}
case *ssa.MakeClosure: case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For // A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions. // example: immediately applied functions.
@ -98,7 +93,7 @@ func (b *builder) createGo(instr *ssa.Go) {
params = append(params, context) // context parameter params = append(params, context) // context parameter
hasContext = true hasContext = true
switch b.Scheduler { switch b.Scheduler {
case "none", "coroutines": case "none":
// There are no additional parameters needed for the goroutine start operation. // There are no additional parameters needed for the goroutine start operation.
case "tasks", "asyncify": case "tasks", "asyncify":
// Add the function pointer as a parameter to start the goroutine. // Add the function pointer as a parameter to start the goroutine.
@ -110,32 +105,22 @@ func (b *builder) createGo(instr *ssa.Go) {
} }
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value var stackSize llvm.Value
switch b.Scheduler { callee := b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
case "none", "tasks", "asyncify": if b.AutomaticStackSize {
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos()) // The stack size is not known until after linking. Call a dummy
if b.AutomaticStackSize { // function that will be replaced with a load from a special ELF
// The stack size is not known until after linking. Call a dummy // section that contains the stack size (and is modified after
// function that will be replaced with a load from a special ELF // linking).
// section that contains the stack size (and is modified after stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
// linking). stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) } else {
stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize") // The stack size is fixed at compile time. By emitting it here as a
} else { // constant, it can be optimized.
// The stack size is fixed at compile time. By emitting it here as a if (b.Scheduler == "tasks" || b.Scheduler == "asyncify") && b.DefaultStackSize == 0 {
// constant, it can be optimized. b.addError(instr.Pos(), "default stack size for goroutines is not set")
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)
} }
case "coroutines": stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
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)) 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)}, "") b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
@ -297,8 +282,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// The last parameter in the packed object has somewhat of a dual role. // The last parameter in the packed object has somewhat of a dual role.
// Inside the parameter bundle it's the function pointer, stored right // Inside the parameter bundle it's the function pointer, stored right
// after the context pointer. But in the IR call instruction, it's the // after the context pointer. But in the IR call instruction, it's the
// parentHandle function that's always undef outside of the coroutines // parentHandle function that's always undef. Thus, make the parameter
// scheduler. Thus, make the parameter undef here. // undef here.
params[len(params)-1] = llvm.Undef(c.i8ptrType) params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call. // Create the call.

Просмотреть файл

149
compiler/testdata/goroutine-wasm-coroutines.ll предоставленный
Просмотреть файл

@ -1,149 +0,0 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.gcData", %"internal/task.state" }
%"internal/task.gcData" = type {}
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
@"main$pack" = internal unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"main$pack.1" = internal unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
@"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
declare void @runtime.trackPointer(i8* nocapture readonly, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @main.regularFunction to i32), i8* bitcast ({ i32, i8* }* @"main$pack" to i8*), i32 undef, i8* undef, i8* null) #0
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.inlineFunctionGoroutine$1" to i32), i8* bitcast ({ i32, i8* }* @"main$pack.1" to i8*), i32 undef, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%0 = bitcast i8* %n to i32*
call void @runtime.trackPointer(i8* nonnull %n, i8* undef, i8* null) #0
store i32 3, i32* %0, align 4
call void @runtime.trackPointer(i8* nonnull %n, i8* undef, i8* null) #0
%1 = call i8* @runtime.alloc(i32 8, i8* null, i8* undef, i8* null) #0
call void @runtime.trackPointer(i8* nonnull %1, i8* undef, i8* null) #0
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* nonnull %1, i32 undef, i8* undef, i8* null) #0
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.funcGoroutine(i8* %fn.context, i32 %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %fn.context, i32 %fn.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null) #0
%1 = call i8* @runtime.alloc(i32 8, i8* null, i8* undef, i8* null) #0
call void @runtime.trackPointer(i8* nonnull %1, i8* undef, i8* null) #0
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %fn.context, i8** %4, align 4
call void @"internal/task.start"(i32 %0, i8* nonnull %1, i32 undef, i8* undef, i8* null) #0
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null) #0
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null) #0
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef, i8* null) #0
call void @runtime.trackPointer(i8* nonnull %0, i8* undef, i8* null) #0
%1 = bitcast i8* %0 to i8**
store i8* %itf.value, i8** %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%.repack = bitcast i8* %2 to i8**
store i8* getelementptr inbounds ([4 x i8], [4 x i8]* @"main$string", i32 0, i32 0), i8** %.repack, align 4
%.repack1 = getelementptr inbounds i8, i8* %0, i32 8
%3 = bitcast i8* %.repack1 to i32*
store i32 4, i32* %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 12
%5 = bitcast i8* %4 to i32*
store i32 %itf.typecode, i32* %5, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i8*, i8*, i32, i32, i8*, i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke" to i32), i8* nonnull %0, i32 undef, i8* undef, i8* null) #0
ret void
}
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8*, i8*, i32, i32, i8*, i8*) #1
attributes #0 = { nounwind }
attributes #1 = { "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }

12
main.go
Просмотреть файл

@ -30,7 +30,6 @@ import (
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
"go.bug.st/serial" "go.bug.st/serial"
@ -1100,15 +1099,6 @@ func printCompilerError(logln func(...interface{}), err error) {
logln() logln()
} }
} }
case transform.CoroutinesError:
logln(err.Pos.String() + ": " + err.Msg)
logln("\ntraceback:")
for _, line := range err.Traceback {
logln(line.Name)
if line.Position.IsValid() {
logln("\t" + line.Position.String())
}
}
case loader.Errors: case loader.Errors:
logln("#", err.Pkg.ImportPath) logln("#", err.Pkg.ImportPath)
for _, err := range err.Errs { for _, err := range err.Errs {
@ -1195,7 +1185,7 @@ func main() {
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)") gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks, asyncify)") scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)")
serial := flag.String("serial", "", "which serial output to use (none, uart, usb)") serial := flag.String("serial", "", "which serial output to use (none, uart, usb)")
printIR := flag.Bool("printir", false, "print LLVM IR") printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")

Просмотреть файл

@ -1,5 +1,5 @@
//go:build gc.conservative && tinygo.wasm && !scheduler.coroutines //go:build gc.conservative && tinygo.wasm
// +build gc.conservative,tinygo.wasm,!scheduler.coroutines // +build gc.conservative,tinygo.wasm
package task package task

Просмотреть файл

@ -1,5 +1,5 @@
//go:build !gc.conservative || !tinygo.wasm || scheduler.coroutines //go:build !gc.conservative || !tinygo.wasm
// +build !gc.conservative !tinygo.wasm scheduler.coroutines // +build !gc.conservative !tinygo.wasm
package task package task

Просмотреть файл

@ -1,90 +0,0 @@
// +build scheduler.coroutines
package task
import (
"unsafe"
)
// rawState is an underlying coroutine state exposed by llvm.coro.
// This matches *i8 in LLVM.
type rawState uint8
//export llvm.coro.resume
func coroResume(*rawState)
type state struct{ *rawState }
//export llvm.coro.noop
func noopState() *rawState
// Resume the task until it pauses or completes.
func (t *Task) Resume() {
coroResume(t.state.rawState)
}
// setState is used by the compiler to set the state of the function at the beginning of a function call.
// Returns the state of the caller.
func (t *Task) setState(s *rawState) *rawState {
caller := t.state
t.state = state{s}
return caller.rawState
}
// returnTo is used by the compiler to return to the state of the caller.
func (t *Task) returnTo(parent *rawState) {
t.state = state{parent}
t.returnCurrent()
}
// returnCurrent is used by the compiler to return to the state of the caller in a case where the state is not replaced.
func (t *Task) returnCurrent() {
scheduleTask(t)
}
//go:linkname scheduleTask runtime.runqueuePushBack
func scheduleTask(*Task)
// setReturnPtr is used by the compiler to store the return buffer into the task.
// This buffer is where the return value of a function that is about to be called will be stored.
func (t *Task) setReturnPtr(buf unsafe.Pointer) {
t.Ptr = buf
}
// getReturnPtr is used by the compiler to get the return buffer stored into the task.
// This is called at the beginning of an async function, and the return is stored into this buffer immediately before resuming the caller.
func (t *Task) getReturnPtr() unsafe.Pointer {
return t.Ptr
}
// createTask returns a new task struct initialized with a no-op state.
func createTask() *Task {
return &Task{
state: state{noopState()},
}
}
// start invokes a function in a new goroutine. Calls to this are inserted by the compiler.
// The created goroutine starts running immediately.
// This is implemented inside the compiler.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr)
// Current returns the current active task.
// This is implemented inside the compiler.
func Current() *Task
// Pause suspends the current running task.
// This is implemented inside the compiler.
func Pause()
func fake() {
// Hack to ensure intrinsics are discovered.
Current()
Pause()
}
// OnSystemStack returns whether the caller is running on the system stack.
func OnSystemStack() bool {
// This scheduler does not do any stack switching.
return true
}

Просмотреть файл

@ -15,13 +15,13 @@ package runtime
// closed: // closed:
// The channel is closed. Sends will panic, receives will get a zero value // The channel is closed. Sends will panic, receives will get a zero value
// plus optionally the indication that the channel is zero (with the // plus optionally the indication that the channel is zero (with the
// commao-ok value in the coroutine). // comma-ok value in the task).
// //
// A send/recv transmission is completed by copying from the data element of the // A send/recv transmission is completed by copying from the data element of the
// sending coroutine to the data element of the receiving coroutine, and setting // sending task to the data element of the receiving task, and setting
// the 'comma-ok' value to true. // the 'comma-ok' value to true.
// A receive operation on a closed channel is completed by zeroing the data // A receive operation on a closed channel is completed by zeroing the data
// element of the receiving coroutine and setting the 'comma-ok' value to false. // element of the receiving task and setting the 'comma-ok' value to false.
import ( import (
"internal/task" "internal/task"

Просмотреть файл

@ -6,13 +6,9 @@ package runtime
// were added to the queue (first-in, first-out). It also contains a sleep queue // were added to the queue (first-in, first-out). It also contains a sleep queue
// with sleeping goroutines in order of when they should be re-activated. // with sleeping goroutines in order of when they should be re-activated.
// //
// The scheduler is used both for the coroutine based scheduler and for the task // The scheduler is used both for the asyncify based scheduler and for the task
// based scheduler (see compiler/goroutine-lowering.go for a description). In // based scheduler. In both cases, the 'internal/task.Task' type is used to represent one
// both cases, the 'task' type is used to represent one goroutine. In the case // goroutine.
// of the task based scheduler, it literally is the goroutine itself: a pointer
// to the bottom of the stack where some important fields are kept. In the case
// of the coroutine-based scheduler, it is the coroutine pointer (a *i8 in
// LLVM).
import ( import (
"internal/task" "internal/task"

Просмотреть файл

@ -1,9 +0,0 @@
// +build scheduler.coroutines
package runtime
// getSystemStackPointer returns the current stack pointer of the system stack.
// This is always the current stack pointer.
func getSystemStackPointer() uintptr {
return getCurrentStackPointer()
}

2
testdata/goroutines.go предоставленный
Просмотреть файл

@ -20,7 +20,7 @@ func main() {
time.Sleep(2 * time.Millisecond) time.Sleep(2 * time.Millisecond)
println("main 3") println("main 3")
// Await a blocking call. This must create a new coroutine. // Await a blocking call.
println("wait:") println("wait:")
wait() wait()
println("end waiting") println("end waiting")

Различия файлов не показаны, т.к. их слишком много Показать различия

Просмотреть файл

@ -1,18 +0,0 @@
package transform_test
import (
"testing"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
func TestGoroutineLowering(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/coroutines", func(mod llvm.Module) {
err := transform.LowerCoroutines(mod, false)
if err != nil {
panic(err)
}
})
}

Просмотреть файл

@ -29,10 +29,9 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
if inlinerThreshold != 0 { if inlinerThreshold != 0 {
builder.UseInlinerWithThreshold(inlinerThreshold) builder.UseInlinerWithThreshold(inlinerThreshold)
} }
builder.AddCoroutinePassesToExtensionPoints()
// Make sure these functions are kept in tact during TinyGo transformation passes. // Make sure these functions are kept in tact during TinyGo transformation passes.
for _, name := range getFunctionsUsedInTransforms(config) { for _, name := range functionsUsedInTransforms {
fn := mod.NamedFunction(name) fn := mod.NamedFunction(name)
if fn.IsNil() { if fn.IsNil() {
panic(fmt.Errorf("missing core function %q", name)) panic(fmt.Errorf("missing core function %q", name))
@ -118,17 +117,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
goPasses.Run(mod) goPasses.Run(mod)
} }
// Lower async implementations. if config.Scheduler() == "none" {
switch config.Scheduler() {
case "coroutines":
// Lower async as coroutines.
err := LowerCoroutines(mod, config.NeedsStackObjects())
if err != nil {
return []error{err}
}
case "tasks", "asyncify":
// No transformations necessary.
case "none":
// Check for any goroutine starts. // Check for any goroutine starts.
if start := mod.NamedFunction("internal/task.start"); !start.IsNil() && len(getUses(start)) > 0 { if start := mod.NamedFunction("internal/task.start"); !start.IsNil() && len(getUses(start)) > 0 {
errs := []error{} errs := []error{}
@ -137,8 +126,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
} }
return errs return errs
} }
default:
return []error{errors.New("invalid scheduler")}
} }
if config.VerifyIR() { if config.VerifyIR() {
@ -151,7 +138,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
} }
// After TinyGo-specific transforms have finished, undo exporting these functions. // After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range getFunctionsUsedInTransforms(config) { for _, name := range functionsUsedInTransforms {
fn := mod.NamedFunction(name) fn := mod.NamedFunction(name)
if fn.IsNil() || fn.IsDeclaration() { if fn.IsNil() || fn.IsDeclaration() {
continue continue
@ -195,35 +182,3 @@ var functionsUsedInTransforms = []string{
"runtime.free", "runtime.free",
"runtime.nilPanic", "runtime.nilPanic",
} }
var taskFunctionsUsedInTransforms = []string{}
// These functions need to be preserved in the IR until after the coroutines
// pass has run.
var coroFunctionsUsedInTransforms = []string{
"internal/task.start",
"internal/task.Pause",
"internal/task.fake",
"internal/task.Current",
"internal/task.createTask",
"(*internal/task.Task).setState",
"(*internal/task.Task).returnTo",
"(*internal/task.Task).returnCurrent",
"(*internal/task.Task).setReturnPtr",
"(*internal/task.Task).getReturnPtr",
}
// getFunctionsUsedInTransforms gets a list of all special functions that should be preserved during transforms and optimization.
func getFunctionsUsedInTransforms(config *compileopts.Config) []string {
fnused := functionsUsedInTransforms
switch config.Scheduler() {
case "none":
case "coroutines":
fnused = append(append([]string{}, fnused...), coroFunctionsUsedInTransforms...)
case "tasks", "asyncify":
fnused = append(append([]string{}, fnused...), taskFunctionsUsedInTransforms...)
default:
panic(fmt.Errorf("invalid scheduler %q", config.Scheduler()))
}
return fnused
}

152
transform/testdata/coroutines.ll предоставленный
Просмотреть файл

@ -1,152 +0,0 @@
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%"internal/task.state" = type { i8* }
%"internal/task.Task" = type { %"internal/task.Task", i8*, i32, %"internal/task.state" }
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
declare void @"internal/task.Pause"(i8*, i8*)
declare void @runtime.scheduler(i8*, i8*)
declare i8* @runtime.alloc(i32, i8*, i8*, i8*)
declare void @runtime.free(i8*, i8*, i8*)
declare %"internal/task.Task"* @"internal/task.Current"(i8*, i8*)
declare i8* @"(*internal/task.Task).setState"(%"internal/task.Task"*, i8*, i8*, i8*)
declare void @"(*internal/task.Task).setReturnPtr"(%"internal/task.Task"*, i8*, i8*, i8*)
declare i8* @"(*internal/task.Task).getReturnPtr"(%"internal/task.Task"*, i8*, i8*)
declare void @"(*internal/task.Task).returnTo"(%"internal/task.Task"*, i8*, i8*, i8*)
declare void @"(*internal/task.Task).returnCurrent"(%"internal/task.Task"*, i8*, i8*)
declare %"internal/task.Task"* @"internal/task.createTask"(i8*, i8*)
declare void @callMain(i8*, i8*)
; Test a simple sleep-like scenario.
declare void @enqueueTimer(%"internal/task.Task"*, i64, i8*, i8*)
define void @sleep(i64, i8*, i8* %parentHandle) {
entry:
%2 = call %"internal/task.Task"* @"internal/task.Current"(i8* undef, i8* null)
call void @enqueueTimer(%"internal/task.Task"* %2, i64 %0, i8* undef, i8* null)
call void @"internal/task.Pause"(i8* undef, i8* null)
ret void
}
; Test a delayed value return.
define i32 @delayedValue(i32, i64, i8*, i8* %parentHandle) {
entry:
call void @sleep(i64 %1, i8* undef, i8* null)
ret i32 %0
}
; Test a deadlocking async func.
define void @deadlock(i8*, i8* %parentHandle) {
entry:
call void @"internal/task.Pause"(i8* undef, i8* null)
unreachable
}
; Test a regular tail call.
define i32 @tail(i32, i64, i8*, i8* %parentHandle) {
entry:
%3 = call i32 @delayedValue(i32 %0, i64 %1, i8* undef, i8* null)
ret i32 %3
}
; Test a ditching tail call.
define void @ditchTail(i32, i64, i8*, i8* %parentHandle) {
entry:
%3 = call i32 @delayedValue(i32 %0, i64 %1, i8* undef, i8* null)
ret void
}
; Test a void tail call.
define void @voidTail(i32, i64, i8*, i8* %parentHandle) {
entry:
call void @ditchTail(i32 %0, i64 %1, i8* undef, i8* null)
ret void
}
; Test a tail call returning an alternate value.
define i32 @alternateTail(i32, i32, i64, i8*, i8* %parentHandle) {
entry:
%4 = call i32 @delayedValue(i32 %1, i64 %2, i8* undef, i8* null)
ret i32 %0
}
; Test a normal return from a coroutine.
; This must be turned into a coroutine.
define i1 @coroutine(i32, i64, i8*, i8* %parentHandle) {
entry:
%3 = call i32 @delayedValue(i32 %0, i64 %1, i8* undef, i8* null)
%4 = icmp eq i32 %3, 0
ret i1 %4
}
; Normal function which should not be transformed.
define void @doNothing(i8*, i8* %parentHandle) {
entry:
ret void
}
; Regression test: ensure that a tail call does not destroy the frame while it is still in use.
; Previously, the tail-call lowering transform would branch to the cleanup block after usePtr.
; This caused the lifetime of %a to be incorrectly reduced, and allowed the coroutine lowering transform to keep %a on the stack.
; After a suspend %a would be used, resulting in memory corruption.
define i8 @coroutineTailRegression(i8*, i8* %parentHandle) {
entry:
%a = alloca i8
store i8 5, i8* %a
%val = call i8 @usePtr(i8* %a, i8* undef, i8* null)
ret i8 %val
}
; Regression test: ensure that stack allocations alive during a suspend end up on the heap.
; This used to not be transformed to a coroutine, keeping %a on the stack.
; After a suspend %a would be used, resulting in memory corruption.
define i8 @allocaTailRegression(i8*, i8* %parentHandle) {
entry:
%a = alloca i8
call void @sleep(i64 1000000, i8* undef, i8* null)
store i8 5, i8* %a
%val = call i8 @usePtr(i8* %a, i8* undef, i8* null)
ret i8 %val
}
; usePtr uses a pointer after a suspend.
define i8 @usePtr(i8*, i8*, i8* %parentHandle) {
entry:
call void @sleep(i64 1000000, i8* undef, i8* null)
%val = load i8, i8* %0
ret i8 %val
}
; Goroutine that sleeps and does nothing.
; Should be a void tail call.
define void @sleepGoroutine(i8*, i8* %parentHandle) {
call void @sleep(i64 1000000, i8* undef, i8* null)
ret void
}
; Program main function.
define void @progMain(i8*, i8* %parentHandle) {
entry:
; Call a sync func in a goroutine.
call void @"internal/task.start"(i32 ptrtoint (void (i8*, i8*)* @doNothing to i32), i8* undef, i32 undef, i8* undef, i8* null)
; Call an async func in a goroutine.
call void @"internal/task.start"(i32 ptrtoint (void (i8*, i8*)* @sleepGoroutine to i32), i8* undef, i32 undef, i8* undef, i8* null)
; Sleep a bit.
call void @sleep(i64 2000000, i8* undef, i8* null)
; Done.
ret void
}
; Entrypoint of runtime.
define void @main() {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i8*, i8*)* @progMain to i32), i8* undef, i32 undef, i8* undef, i8* null)
call void @runtime.scheduler(i8* undef, i8* null)
ret void
}

313
transform/testdata/coroutines.out.ll предоставленный
Просмотреть файл

@ -1,313 +0,0 @@
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%"internal/task.Task" = type { %"internal/task.Task", i8*, i32, %"internal/task.state" }
%"internal/task.state" = type { i8* }
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
declare void @"internal/task.Pause"(i8*, i8*)
declare void @runtime.scheduler(i8*, i8*)
declare i8* @runtime.alloc(i32, i8*, i8*, i8*)
declare void @runtime.free(i8*, i8*, i8*)
declare %"internal/task.Task"* @"internal/task.Current"(i8*, i8*)
declare i8* @"(*internal/task.Task).setState"(%"internal/task.Task"*, i8*, i8*, i8*)
declare void @"(*internal/task.Task).setReturnPtr"(%"internal/task.Task"*, i8*, i8*, i8*)
declare i8* @"(*internal/task.Task).getReturnPtr"(%"internal/task.Task"*, i8*, i8*)
declare void @"(*internal/task.Task).returnTo"(%"internal/task.Task"*, i8*, i8*, i8*)
declare void @"(*internal/task.Task).returnCurrent"(%"internal/task.Task"*, i8*, i8*)
declare %"internal/task.Task"* @"internal/task.createTask"(i8*, i8*)
declare void @callMain(i8*, i8*)
declare void @enqueueTimer(%"internal/task.Task"*, i64, i8*, i8*)
define void @sleep(i64 %0, i8* %1, i8* %parentHandle) {
entry:
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%task.current1 = bitcast i8* %parentHandle to %"internal/task.Task"*
call void @enqueueTimer(%"internal/task.Task"* %task.current1, i64 %0, i8* undef, i8* null)
ret void
}
define i32 @delayedValue(i32 %0, i64 %1, i8* %2, i8* %parentHandle) {
entry:
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%ret.ptr = call i8* @"(*internal/task.Task).getReturnPtr"(%"internal/task.Task"* %task.current, i8* undef, i8* undef)
%ret.ptr.bitcast = bitcast i8* %ret.ptr to i32*
store i32 %0, i32* %ret.ptr.bitcast, align 4
call void @sleep(i64 %1, i8* undef, i8* %parentHandle)
ret i32 undef
}
define void @deadlock(i8* %0, i8* %parentHandle) {
entry:
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
ret void
}
define i32 @tail(i32 %0, i64 %1, i8* %2, i8* %parentHandle) {
entry:
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%3 = call i32 @delayedValue(i32 %0, i64 %1, i8* undef, i8* %parentHandle)
ret i32 undef
}
define void @ditchTail(i32 %0, i64 %1, i8* %2, i8* %parentHandle) {
entry:
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%ret.ditch = call i8* @runtime.alloc(i32 4, i8* null, i8* undef, i8* undef)
call void @"(*internal/task.Task).setReturnPtr"(%"internal/task.Task"* %task.current, i8* %ret.ditch, i8* undef, i8* undef)
%3 = call i32 @delayedValue(i32 %0, i64 %1, i8* undef, i8* %parentHandle)
ret void
}
define void @voidTail(i32 %0, i64 %1, i8* %2, i8* %parentHandle) {
entry:
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
call void @ditchTail(i32 %0, i64 %1, i8* undef, i8* %parentHandle)
ret void
}
define i32 @alternateTail(i32 %0, i32 %1, i64 %2, i8* %3, i8* %parentHandle) {
entry:
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%ret.ptr = call i8* @"(*internal/task.Task).getReturnPtr"(%"internal/task.Task"* %task.current, i8* undef, i8* undef)
%ret.ptr.bitcast = bitcast i8* %ret.ptr to i32*
store i32 %0, i32* %ret.ptr.bitcast, align 4
%ret.alternate = call i8* @runtime.alloc(i32 4, i8* null, i8* undef, i8* undef)
call void @"(*internal/task.Task).setReturnPtr"(%"internal/task.Task"* %task.current, i8* %ret.alternate, i8* undef, i8* undef)
%4 = call i32 @delayedValue(i32 %1, i64 %2, i8* undef, i8* %parentHandle)
ret i32 undef
}
define i1 @coroutine(i32 %0, i64 %1, i8* %2, i8* %parentHandle) {
entry:
%call.return = alloca i32, align 4
%coro.id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%coro.size = call i32 @llvm.coro.size.i32()
%coro.alloc = call i8* @runtime.alloc(i32 %coro.size, i8* null, i8* undef, i8* undef)
%coro.state = call i8* @llvm.coro.begin(token %coro.id, i8* %coro.alloc)
%task.current2 = bitcast i8* %parentHandle to %"internal/task.Task"*
%task.state.parent = call i8* @"(*internal/task.Task).setState"(%"internal/task.Task"* %task.current2, i8* %coro.state, i8* undef, i8* undef)
%task.retPtr = call i8* @"(*internal/task.Task).getReturnPtr"(%"internal/task.Task"* %task.current2, i8* undef, i8* undef)
%task.retPtr.bitcast = bitcast i8* %task.retPtr to i1*
%call.return.bitcast = bitcast i32* %call.return to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* %call.return.bitcast)
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%call.return.bitcast1 = bitcast i32* %call.return to i8*
call void @"(*internal/task.Task).setReturnPtr"(%"internal/task.Task"* %task.current, i8* %call.return.bitcast1, i8* undef, i8* undef)
%3 = call i32 @delayedValue(i32 %0, i64 %1, i8* undef, i8* %parentHandle)
%coro.save = call token @llvm.coro.save(i8* %coro.state)
%call.suspend = call i8 @llvm.coro.suspend(token %coro.save, i1 false)
switch i8 %call.suspend, label %suspend [
i8 0, label %wakeup
i8 1, label %cleanup
]
wakeup: ; preds = %entry
%4 = load i32, i32* %call.return, align 4
call void @llvm.lifetime.end.p0i8(i64 4, i8* %call.return.bitcast)
%5 = icmp eq i32 %4, 0
store i1 %5, i1* %task.retPtr.bitcast, align 1
call void @"(*internal/task.Task).returnTo"(%"internal/task.Task"* %task.current2, i8* %task.state.parent, i8* undef, i8* undef)
br label %cleanup
suspend: ; preds = %entry, %cleanup
%unused = call i1 @llvm.coro.end(i8* %coro.state, i1 false)
ret i1 undef
cleanup: ; preds = %entry, %wakeup
%coro.memFree = call i8* @llvm.coro.free(token %coro.id, i8* %coro.state)
call void @runtime.free(i8* %coro.memFree, i8* undef, i8* undef)
br label %suspend
}
define void @doNothing(i8* %0, i8* %parentHandle) {
entry:
ret void
}
define i8 @coroutineTailRegression(i8* %0, i8* %parentHandle) {
entry:
%a = alloca i8, align 1
%coro.id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%coro.size = call i32 @llvm.coro.size.i32()
%coro.alloc = call i8* @runtime.alloc(i32 %coro.size, i8* null, i8* undef, i8* undef)
%coro.state = call i8* @llvm.coro.begin(token %coro.id, i8* %coro.alloc)
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%task.state.parent = call i8* @"(*internal/task.Task).setState"(%"internal/task.Task"* %task.current, i8* %coro.state, i8* undef, i8* undef)
%task.retPtr = call i8* @"(*internal/task.Task).getReturnPtr"(%"internal/task.Task"* %task.current, i8* undef, i8* undef)
store i8 5, i8* %a, align 1
%coro.state.restore = call i8* @"(*internal/task.Task).setState"(%"internal/task.Task"* %task.current, i8* %task.state.parent, i8* undef, i8* undef)
call void @"(*internal/task.Task).setReturnPtr"(%"internal/task.Task"* %task.current, i8* %task.retPtr, i8* undef, i8* undef)
%val = call i8 @usePtr(i8* %a, i8* undef, i8* %parentHandle)
br label %post.tail
suspend: ; preds = %post.tail, %cleanup
%unused = call i1 @llvm.coro.end(i8* %coro.state, i1 false)
ret i8 undef
cleanup: ; preds = %post.tail
%coro.memFree = call i8* @llvm.coro.free(token %coro.id, i8* %coro.state)
call void @runtime.free(i8* %coro.memFree, i8* undef, i8* undef)
br label %suspend
post.tail: ; preds = %entry
%coro.save = call token @llvm.coro.save(i8* %coro.state)
%call.suspend = call i8 @llvm.coro.suspend(token %coro.save, i1 false)
switch i8 %call.suspend, label %suspend [
i8 0, label %unreachable
i8 1, label %cleanup
]
unreachable: ; preds = %post.tail
unreachable
}
define i8 @allocaTailRegression(i8* %0, i8* %parentHandle) {
entry:
%a = alloca i8, align 1
%coro.id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%coro.size = call i32 @llvm.coro.size.i32()
%coro.alloc = call i8* @runtime.alloc(i32 %coro.size, i8* null, i8* undef, i8* undef)
%coro.state = call i8* @llvm.coro.begin(token %coro.id, i8* %coro.alloc)
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%task.state.parent = call i8* @"(*internal/task.Task).setState"(%"internal/task.Task"* %task.current, i8* %coro.state, i8* undef, i8* undef)
%task.retPtr = call i8* @"(*internal/task.Task).getReturnPtr"(%"internal/task.Task"* %task.current, i8* undef, i8* undef)
call void @sleep(i64 1000000, i8* undef, i8* %parentHandle)
%coro.save1 = call token @llvm.coro.save(i8* %coro.state)
%call.suspend2 = call i8 @llvm.coro.suspend(token %coro.save1, i1 false)
switch i8 %call.suspend2, label %suspend [
i8 0, label %wakeup
i8 1, label %cleanup
]
wakeup: ; preds = %entry
store i8 5, i8* %a, align 1
%1 = call i8* @"(*internal/task.Task).setState"(%"internal/task.Task"* %task.current, i8* %task.state.parent, i8* undef, i8* undef)
call void @"(*internal/task.Task).setReturnPtr"(%"internal/task.Task"* %task.current, i8* %task.retPtr, i8* undef, i8* undef)
%2 = call i8 @usePtr(i8* %a, i8* undef, i8* %parentHandle)
br label %post.tail
suspend: ; preds = %entry, %post.tail, %cleanup
%unused = call i1 @llvm.coro.end(i8* %coro.state, i1 false)
ret i8 undef
cleanup: ; preds = %entry, %post.tail
%coro.memFree = call i8* @llvm.coro.free(token %coro.id, i8* %coro.state)
call void @runtime.free(i8* %coro.memFree, i8* undef, i8* undef)
br label %suspend
post.tail: ; preds = %wakeup
%coro.save = call token @llvm.coro.save(i8* %coro.state)
%call.suspend = call i8 @llvm.coro.suspend(token %coro.save, i1 false)
switch i8 %call.suspend, label %suspend [
i8 0, label %unreachable
i8 1, label %cleanup
]
unreachable: ; preds = %post.tail
unreachable
}
define i8 @usePtr(i8* %0, i8* %1, i8* %parentHandle) {
entry:
%coro.id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%coro.size = call i32 @llvm.coro.size.i32()
%coro.alloc = call i8* @runtime.alloc(i32 %coro.size, i8* null, i8* undef, i8* undef)
%coro.state = call i8* @llvm.coro.begin(token %coro.id, i8* %coro.alloc)
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
%task.state.parent = call i8* @"(*internal/task.Task).setState"(%"internal/task.Task"* %task.current, i8* %coro.state, i8* undef, i8* undef)
%task.retPtr = call i8* @"(*internal/task.Task).getReturnPtr"(%"internal/task.Task"* %task.current, i8* undef, i8* undef)
call void @sleep(i64 1000000, i8* undef, i8* %parentHandle)
%coro.save = call token @llvm.coro.save(i8* %coro.state)
%call.suspend = call i8 @llvm.coro.suspend(token %coro.save, i1 false)
switch i8 %call.suspend, label %suspend [
i8 0, label %wakeup
i8 1, label %cleanup
]
wakeup: ; preds = %entry
%2 = load i8, i8* %0, align 1
store i8 %2, i8* %task.retPtr, align 1
call void @"(*internal/task.Task).returnTo"(%"internal/task.Task"* %task.current, i8* %task.state.parent, i8* undef, i8* undef)
br label %cleanup
suspend: ; preds = %entry, %cleanup
%unused = call i1 @llvm.coro.end(i8* %coro.state, i1 false)
ret i8 undef
cleanup: ; preds = %entry, %wakeup
%coro.memFree = call i8* @llvm.coro.free(token %coro.id, i8* %coro.state)
call void @runtime.free(i8* %coro.memFree, i8* undef, i8* undef)
br label %suspend
}
define void @sleepGoroutine(i8* %0, i8* %parentHandle) {
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
call void @sleep(i64 1000000, i8* undef, i8* %parentHandle)
ret void
}
define void @progMain(i8* %0, i8* %parentHandle) {
entry:
%task.current = bitcast i8* %parentHandle to %"internal/task.Task"*
call void @doNothing(i8* undef, i8* undef)
%start.task = call %"internal/task.Task"* @"internal/task.createTask"(i8* undef, i8* undef)
%start.task.bitcast = bitcast %"internal/task.Task"* %start.task to i8*
call void @sleepGoroutine(i8* undef, i8* %start.task.bitcast)
call void @sleep(i64 2000000, i8* undef, i8* %parentHandle)
ret void
}
define void @main() {
entry:
%start.task = call %"internal/task.Task"* @"internal/task.createTask"(i8* undef, i8* undef)
%start.task.bitcast = bitcast %"internal/task.Task"* %start.task to i8*
call void @progMain(i8* undef, i8* %start.task.bitcast)
call void @runtime.scheduler(i8* undef, i8* null)
ret void
}
; Function Attrs: argmemonly nounwind readonly
declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*) #0
; Function Attrs: nounwind readnone
declare i32 @llvm.coro.size.i32() #1
; Function Attrs: nounwind
declare i8* @llvm.coro.begin(token, i8* writeonly) #2
; Function Attrs: nounwind
declare i8 @llvm.coro.suspend(token, i1) #2
; Function Attrs: nounwind
declare i1 @llvm.coro.end(i8*, i1) #2
; Function Attrs: argmemonly nounwind readonly
declare i8* @llvm.coro.free(token, i8* nocapture readonly) #0
; Function Attrs: nounwind
declare token @llvm.coro.save(i8*) #2
; Function Attrs: argmemonly nofree nosync nounwind willreturn
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #3
; Function Attrs: argmemonly nofree nosync nounwind willreturn
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #3
attributes #0 = { argmemonly nounwind readonly }
attributes #1 = { nounwind readnone }
attributes #2 = { nounwind }
attributes #3 = { argmemonly nofree nosync nounwind willreturn }

Просмотреть файл

@ -36,7 +36,7 @@ func ExternalInt64AsPtr(mod llvm.Module, config *compileopts.Config) error {
if strings.HasPrefix(fn.Name(), "llvm.") || strings.HasPrefix(fn.Name(), "runtime.") { if strings.HasPrefix(fn.Name(), "llvm.") || strings.HasPrefix(fn.Name(), "runtime.") {
// Do not try to modify the signature of internal LLVM functions and // Do not try to modify the signature of internal LLVM functions and
// assume that runtime functions are only temporarily exported for // assume that runtime functions are only temporarily exported for
// coroutine lowering. // transforms.
continue continue
} }
if !fn.GetStringAttributeAtIndex(-1, "tinygo-methods").IsNil() { if !fn.GetStringAttributeAtIndex(-1, "tinygo-methods").IsNil() {