diff --git a/cl/_testdata/async/in.go b/cl/_testdata/async/in.go new file mode 100644 index 00000000..259b98b2 --- /dev/null +++ b/cl/_testdata/async/in.go @@ -0,0 +1,37 @@ +package async_compile + +import ( + "fmt" + + "github.com/goplus/llgo/x/async" +) + +func GenInts() (co *async.Promise[int]) { + co.Yield(1) + co.Yield(2) + co.Yield(3) + return +} + +func WrapGenInts() *async.Promise[int] { + return GenInts() +} + +func UseGenInts() int { + co := WrapGenInts() + r := 0 + for !co.Done() { + r += co.Next() + } + return r +} + +func GenIntsWithDefer() (co *async.Promise[int]) { + defer func() { + if r := recover(); r != nil { + fmt.Println("panic:", r) + } + }() + co.Yield(1) + panic("GenIntsWithDefer") +} diff --git a/cl/_testdata/async/out.ll b/cl/_testdata/async/out.ll new file mode 100644 index 00000000..3306d581 --- /dev/null +++ b/cl/_testdata/async/out.ll @@ -0,0 +1,372 @@ +; ModuleID = 'github.com/goplus/llgo/cl/_testdata/async' +source_filename = "github.com/goplus/llgo/cl/_testdata/async" + +%"github.com/goplus/llgo/internal/runtime.Defer" = type { ptr, i64, ptr, ptr } +%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } +%"github.com/goplus/llgo/internal/runtime.eface" = type { ptr, ptr } +%"github.com/goplus/llgo/internal/runtime.Slice" = type { ptr, i64, i64 } +%"github.com/goplus/llgo/internal/runtime.iface" = type { ptr, ptr } +%"github.com/goplus/llgo/x/async.Promise[int]" = type { ptr, i64 } + +@"github.com/goplus/llgo/cl/_testdata/async.init$guard" = global i1 false, align 1 +@__llgo_defer = linkonce global i32 0, align 4 +@0 = private unnamed_addr constant [16 x i8] c"GenIntsWithDefer", align 1 +@_llgo_string = linkonce global ptr null, align 8 +@1 = private unnamed_addr constant [6 x i8] c"panic:", align 1 + +define ptr @"github.com/goplus/llgo/cl/_testdata/async.GenInts"() #0 { +entry: + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + +alloc: ; preds = %entry + %frame.size = call i64 @llvm.coro.size.i64() + %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + br label %_llgo_4 + +clean: ; preds = %_llgo_7, %_llgo_6, %_llgo_5, %_llgo_4 + %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %_llgo_6, %_llgo_5, %_llgo_4, %clean + %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +_llgo_4: ; preds = %alloc, %entry + %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + store ptr %hdl, ptr %promise, align 8 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) + %2 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %2, label %suspend [ + i8 0, label %_llgo_5 + i8 1, label %clean + ] + +_llgo_5: ; preds = %_llgo_4 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 2) + %3 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %3, label %suspend [ + i8 0, label %_llgo_6 + i8 1, label %clean + ] + +_llgo_6: ; preds = %_llgo_5 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 3) + %4 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %4, label %suspend [ + i8 0, label %_llgo_7 + i8 1, label %clean + ] + +_llgo_7: ; preds = %_llgo_6 + br label %clean +} + +define ptr @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer"() #0 { +entry: + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + +alloc: ; preds = %entry + %frame.size = call i64 @llvm.coro.size.i64() + %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + br label %_llgo_4 + +clean: ; preds = %_llgo_5, %_llgo_8 + %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %_llgo_8, %clean + %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +_llgo_4: ; preds = %alloc, %entry + %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + store ptr %hdl, ptr %promise, align 8 + %2 = alloca ptr, align 8 + %3 = call ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr %2, i64 8) + %4 = load i32, ptr @__llgo_defer, align 4 + %5 = call ptr @pthread_getspecific(i32 %4) + %6 = alloca i8, i64 196, align 1 + %7 = alloca i8, i64 32, align 1 + %8 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 0 + store ptr %6, ptr %8, align 8 + %9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 + store i64 0, ptr %9, align 4 + %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 2 + store ptr %5, ptr %10, align 8 + %11 = call i32 @pthread_setspecific(i32 %4, ptr %7) + %12 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 + %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 + %14 = call i32 @sigsetjmp(ptr %6, i32 0) + %15 = icmp eq i32 %14, 0 + br i1 %15, label %_llgo_8, label %_llgo_9 + +_llgo_5: ; preds = %_llgo_8, %_llgo_7 + %16 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 0 + store ptr @0, ptr %17, align 8 + %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 1 + store i64 16, ptr %18, align 4 + %19 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %16, align 8 + %20 = load ptr, ptr @_llgo_string, align 8 + %21 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/internal/runtime.String" %19, ptr %21, align 8 + %22 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %23 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, i32 0, i32 0 + store ptr %20, ptr %23, align 8 + %24 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, i32 0, i32 1 + store ptr %21, ptr %24, align 8 + %25 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, align 8 + call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %25) + unreachable + %26 = load ptr, ptr %3, align 8 + br label %clean + +_llgo_6: ; preds = %_llgo_9 + %27 = load i64, ptr %12, align 4 + call void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() + %28 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, align 8 + %29 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %28, 2 + %30 = call i32 @pthread_setspecific(i32 %4, ptr %29) + %31 = load ptr, ptr %13, align 8 + indirectbr ptr %31, [label %_llgo_7] + +_llgo_7: ; preds = %_llgo_6 + call void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr %5) + br label %_llgo_5 + +_llgo_8: ; preds = %_llgo_4 + %32 = load ptr, ptr %3, align 8 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) + %33 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %33, label %suspend [ + i8 0, label %_llgo_5 + i8 1, label %clean + ] + +_llgo_9: ; preds = %_llgo_4 + store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %13, align 8 + br label %_llgo_6 + +_llgo_10: ; No predecessors! +} + +define void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() { +_llgo_0: + %0 = call %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/internal/runtime.Recover"() + %1 = call i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface" %0, %"github.com/goplus/llgo/internal/runtime.eface" zeroinitializer) + %2 = xor i1 %1, true + br i1 %2, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %3 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 32) + %4 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %3, i64 0 + %5 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %6 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %5, i32 0, i32 0 + store ptr @1, ptr %6, align 8 + %7 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %5, i32 0, i32 1 + store i64 6, ptr %7, align 4 + %8 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %5, align 8 + %9 = load ptr, ptr @_llgo_string, align 8 + %10 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/internal/runtime.String" %8, ptr %10, align 8 + %11 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %12 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, i32 0, i32 0 + store ptr %9, ptr %12, align 8 + %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, i32 0, i32 1 + store ptr %10, ptr %13, align 8 + %14 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, align 8 + store %"github.com/goplus/llgo/internal/runtime.eface" %14, ptr %4, align 8 + %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %3, i64 1 + store %"github.com/goplus/llgo/internal/runtime.eface" %0, ptr %15, align 8 + %16 = alloca %"github.com/goplus/llgo/internal/runtime.Slice", align 8 + %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 0 + store ptr %3, ptr %17, align 8 + %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 1 + store i64 2, ptr %18, align 4 + %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 2 + store i64 2, ptr %19, align 4 + %20 = load %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, align 8 + %21 = call { i64, %"github.com/goplus/llgo/internal/runtime.iface" } @fmt.Println(%"github.com/goplus/llgo/internal/runtime.Slice" %20) + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i64 @"github.com/goplus/llgo/cl/_testdata/async.UseGenInts"() { +_llgo_0: + %0 = call ptr @"github.com/goplus/llgo/cl/_testdata/async.WrapGenInts"() + br label %_llgo_3 + +_llgo_1: ; preds = %_llgo_3 + %1 = call i64 @"github.com/goplus/llgo/x/async.(*Promise).Next[int]"(ptr %0) + %2 = add i64 %3, %1 + br label %_llgo_3 + +_llgo_2: ; preds = %_llgo_3 + ret i64 %3 + +_llgo_3: ; preds = %_llgo_1, %_llgo_0 + %3 = phi i64 [ 0, %_llgo_0 ], [ %2, %_llgo_1 ] + %4 = call i1 @"github.com/goplus/llgo/x/async.(*Promise).Done[int]"(ptr %0) + br i1 %4, label %_llgo_2, label %_llgo_1 +} + +define ptr @"github.com/goplus/llgo/cl/_testdata/async.WrapGenInts"() #0 { +entry: + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + +alloc: ; preds = %entry + %frame.size = call i64 @llvm.coro.size.i64() + %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + br label %_llgo_4 + +clean: ; preds = %_llgo_4 + %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %clean + %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +_llgo_4: ; preds = %alloc, %entry + %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + store ptr %hdl, ptr %promise, align 8 + %2 = call ptr @"github.com/goplus/llgo/cl/_testdata/async.GenInts"() + br label %clean +} + +define void @"github.com/goplus/llgo/cl/_testdata/async.init"() { +_llgo_0: + %0 = load i1, ptr @"github.com/goplus/llgo/cl/_testdata/async.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"github.com/goplus/llgo/cl/_testdata/async.init$guard", align 1 + call void @fmt.init() + call void @"github.com/goplus/llgo/cl/_testdata/async.init$after"() + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) #1 + +; Function Attrs: nounwind +declare i1 @llvm.coro.alloc(token) #2 + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.size.i64() #3 + +; Function Attrs: nounwind +declare ptr @llvm.coro.begin(token, ptr writeonly) #2 + +; Function Attrs: nounwind memory(argmem: read) +declare ptr @llvm.coro.free(token, ptr nocapture readonly) #4 + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(ptr, i1, token) #2 + +declare void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr, i64) + +; Function Attrs: nounwind +declare i8 @llvm.coro.suspend(token, i1) #2 + +declare ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr, i64) + +declare ptr @pthread_getspecific(i32) + +declare i32 @pthread_setspecific(i32, ptr) + +declare i32 @sigsetjmp(ptr, i32) + +declare void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr) + +define void @"github.com/goplus/llgo/cl/_testdata/async.init$after"() { +_llgo_0: + %0 = load ptr, ptr @_llgo_string, align 8 + %1 = icmp eq ptr %0, null + br i1 %1, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %2 = call ptr @"github.com/goplus/llgo/internal/runtime.Basic"(i64 24) + store ptr %2, ptr @_llgo_string, align 8 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + %3 = load i32, ptr @__llgo_defer, align 4 + %4 = icmp eq i32 %3, 0 + br i1 %4, label %_llgo_3, label %_llgo_4 + +_llgo_3: ; preds = %_llgo_2 + %5 = call i32 @pthread_key_create(ptr @__llgo_defer, ptr null) + br label %_llgo_4 + +_llgo_4: ; preds = %_llgo_3, %_llgo_2 + ret void +} + +declare ptr @"github.com/goplus/llgo/internal/runtime.Basic"(i64) + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64) + +declare void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface") + +declare %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/internal/runtime.Recover"() + +declare i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface", %"github.com/goplus/llgo/internal/runtime.eface") + +declare { i64, %"github.com/goplus/llgo/internal/runtime.iface" } @fmt.Println(%"github.com/goplus/llgo/internal/runtime.Slice") + +define i1 @"github.com/goplus/llgo/x/async.(*Promise).Done[int]"(ptr %0) { +_llgo_0: + %1 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 0 + %2 = load ptr, ptr %1, align 8 + %3 = call i1 @llvm.coro.done(ptr %2) + %4 = zext i1 %3 to i64 + %5 = trunc i64 %4 to i8 + %6 = icmp ne i8 %5, 0 + ret i1 %6 +} + +define i64 @"github.com/goplus/llgo/x/async.(*Promise).Next[int]"(ptr %0) { +_llgo_0: + %1 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 0 + %2 = load ptr, ptr %1, align 8 + call void @llvm.coro.resume(ptr %2) + %3 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 1 + %4 = load i64, ptr %3, align 4 + ret i64 %4 +} + +declare void @fmt.init() + +; Function Attrs: nounwind memory(argmem: readwrite) +declare i1 @llvm.coro.done(ptr nocapture readonly) #5 + +declare void @llvm.coro.resume(ptr) + +declare i32 @pthread_key_create(ptr, ptr) + +attributes #0 = { "presplitcoroutine" } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) } +attributes #2 = { nounwind } +attributes #3 = { nounwind memory(none) } +attributes #4 = { nounwind memory(argmem: read) } +attributes #5 = { nounwind memory(argmem: readwrite) } diff --git a/cl/async.go b/cl/async.go new file mode 100644 index 00000000..217f80ae --- /dev/null +++ b/cl/async.go @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cl + +import ( + "go/types" + "strings" + + llssa "github.com/goplus/llgo/ssa" + "golang.org/x/tools/go/ssa" +) + +// TODO(lijie): need more generics, shouldn't limit to async.Promise +func promiseType(ty types.Type) (types.Type, bool) { + // ty is a generic type, so we need to check the package path and type name + if ptrTy, ok := ty.(*types.Pointer); ok { + ty = ptrTy.Elem() + if ty, ok := ty.(*types.Named); ok { + if ty.Obj().Pkg() == nil { + return nil, false + } + if ty.Obj().Pkg().Path() == "github.com/goplus/llgo/x/async" && ty.Obj().Name() == "Promise" { + return ty, true + } + } + } + return nil, false +} + +// check function return async.Promise[T] +// TODO(lijie): make it generic +func isAsyncFunc(sig *types.Signature) bool { + r := sig.Results() + if r.Len() != 1 { + return false + } + ty := r.At(0).Type() + _, ok := promiseType(ty) + return ok +} + +func (p *context) coAwait(b llssa.Builder, args []ssa.Value) llssa.Expr { + if !isAsyncFunc(b.Func.RawType().(*types.Signature)) { + panic("coAwait(promise *T) T: invalid context") + } + if len(args) == 1 { + // promise := p.compileValue(b, args[0]) + b.Unreachable() + // return b.CoroutineAwait(promise) + } + panic("coAwait(promise *T) T: invalid arguments") +} + +func (p *context) coSuspend(b llssa.Builder, final llssa.Expr) { + b.CoSuspend(b.AsyncToken(), final) +} + +func (p *context) coDone(b llssa.Builder, args []ssa.Value) llssa.Expr { + if len(args) != 1 { + panic("coDone(promise *T): invalid arguments") + } + hdl := p.compileValue(b, args[0]) + return b.CoDone(hdl) +} + +func (p *context) coResume(b llssa.Builder, args []ssa.Value) { + if len(args) == 1 { + hdl := p.compileValue(b, args[0]) + b.CoResume(hdl) + } +} + +func (p *context) coReturn(b llssa.Builder, args []ssa.Value) { + cargs := make([]llssa.Expr, len(args)) + for i, arg := range args { + cargs[i] = p.compileValue(b, arg) + } + b.CoReturn(cargs...) +} + +func (p *context) coYield(b llssa.Builder, fn *ssa.Function, args []ssa.Value) { + typ := fn.Signature.Recv().Type() + mthds := p.goProg.MethodSets.MethodSet(typ) + // TODO(lijie): make llgo instruction callable (e.g. llgo.yield) + var setValue *ssa.Function + for i := 0; i < mthds.Len(); i++ { + m := mthds.At(i) + if ssaMthd := p.goProg.MethodValue(m); ssaMthd != nil { + if ssaMthd.Name() == "setValue" || strings.HasPrefix(ssaMthd.Name(), "setValue[") { + setValue = ssaMthd + break + } + } + } + if setValue == nil { + panic("coYield(): not found method setValue") + } + value := p.compileValue(b, args[1]) + setValueFn, _, _ := p.funcOf(setValue) + b.CoYield(setValueFn, value) +} + +func (p *context) coRun(b llssa.Builder, args []ssa.Value) { + panic("coRun(): not implemented") +} diff --git a/cl/compile.go b/cl/compile.go index cadc8a8d..e36e838a 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -214,6 +214,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun log.Println("==> NewFunc", name, "type:", sig.Recv(), sig, "ftype:", ftype) } } + async := isAsyncFunc(f.Signature) if fn == nil { if name == "main" { argc := types.NewParam(token.NoPos, pkgTypes, "", types.Typ[types.Int32]) @@ -223,13 +224,20 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun results := types.NewTuple(ret) sig = types.NewSignatureType(nil, nil, nil, params, results, false) } - fn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), hasCtx) + fn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), hasCtx, async) } - + nBlkOff := 0 if nblk := len(f.Blocks); nblk > 0 { + if async { + nBlkOff = 4 + fn.MakeBlock("entry") + fn.MakeBlock("alloc") + fn.MakeBlock("clean") + fn.MakeBlock("suspend") + } fn.MakeBlocks(nblk) // to set fn.HasBody() = true if f.Recover != nil { // set recover block - fn.SetRecover(fn.Block(f.Recover.Index)) + fn.SetRecover(fn.Block(f.Recover.Index + nBlkOff)) } p.inits = append(p.inits, func() { p.fn = fn @@ -245,6 +253,10 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun log.Println("==> FuncBody", name) } b := fn.NewBuilder() + b.SetBlockOffset(nBlkOff) + if async { + b.BeginAsync(fn) + } p.bvals = make(map[ssa.Value]llssa.Expr) off := make([]int, len(f.Blocks)) for i, block := range f.Blocks { @@ -280,7 +292,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do var pkg = p.pkg var fn = p.fn var instrs = block.Instrs[n:] - var ret = fn.Block(block.Index) + var ret = fn.Block(block.Index + b.BlockOffset()) b.SetBlock(ret) if doModInit { if pyModInit = p.pyMod != ""; pyModInit { @@ -650,7 +662,11 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { results = make([]llssa.Expr, 1) results[0] = p.prog.IntVal(0, p.prog.CInt()) } - b.Return(results...) + if b.Async() { + b.EndAsync() + } else { + b.Return(results...) + } case *ssa.If: fn := p.fn cond := p.compileValue(b, v.Cond) diff --git a/cl/import.go b/cl/import.go index 5d9b8b25..d723f3a8 100644 --- a/cl/import.go +++ b/cl/import.go @@ -412,7 +412,16 @@ const ( llgoAtomicUMax = llgoAtomicOpBase + llssa.OpUMax llgoAtomicUMin = llgoAtomicOpBase + llssa.OpUMin - llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin) + llgoCoBase = llgoInstrBase + 0x30 + llgoCoAwait = llgoCoBase + 0 + llgoCoSuspend = llgoCoBase + 1 + llgoCoDone = llgoCoBase + 2 + llgoCoResume = llgoCoBase + 3 + llgoCoReturn = llgoCoBase + 4 + llgoCoYield = llgoCoBase + 5 + llgoCoRun = llgoCoBase + 6 + + llgoAtomicOpLast = llgoCoRun ) func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, string, int) { diff --git a/cl/instr.go b/cl/instr.go index 83837ad0..9a65ba8e 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -237,6 +237,14 @@ var llgoInstrs = map[string]int{ "atomicMin": int(llgoAtomicMin), "atomicUMax": int(llgoAtomicUMax), "atomicUMin": int(llgoAtomicUMin), + + "coAwait": int(llgoCoAwait), + "coResume": int(llgoCoResume), + "coSuspend": int(llgoCoSuspend), + "coDone": int(llgoCoDone), + "coReturn": int(llgoCoReturn), + "coYield": int(llgoCoYield), + "coRun": int(llgoCoRun), } // funcOf returns a function by name and set ftype = goFunc, cFunc, etc. @@ -265,7 +273,8 @@ func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyObj return nil, nil, ignoredFunc } sig := fn.Signature - aFn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false) + async := isAsyncFunc(sig) + aFn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false, async) } } return @@ -390,6 +399,20 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon ret = p.funcAddr(b, args) case llgoUnreachable: // func unreachable() b.Unreachable() + case llgoCoAwait: + ret = p.coAwait(b, args) + case llgoCoSuspend: + p.coSuspend(b, p.prog.BoolVal(false)) + case llgoCoDone: + return p.coDone(b, args) + case llgoCoResume: + p.coResume(b, args) + case llgoCoReturn: + p.coReturn(b, args) + case llgoCoYield: + p.coYield(b, cv, args) + case llgoCoRun: + p.coRun(b, args) default: if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast { ret = p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args) diff --git a/ssa/coro.go b/ssa/coro.go index d6183f93..e765332c 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -17,11 +17,46 @@ package ssa import ( + "fmt" + "go/constant" "go/token" "go/types" + "log" ) -// declare void @llvm.coro.destroy(i8*) +// declare void @llvm.coro.destroy(ptr ) +// declare void @llvm.coro.resume(ptr ) +// declare i1 @llvm.coro.done(ptr ) +// declare ptr @llvm.coro.promise(ptr , i32 , i1 ) +// declare i32 @llvm.coro.size.i32() +// declare i32 @llvm.coro.size.i64() +// declare i32 @llvm.coro.align.i32() +// declare i64 @llvm.coro.align.i64() +// declare ptr @llvm.coro.begin(token , ptr ) +// declare ptr @llvm.coro.free(token %id, ptr ) +// declare i1 @llvm.coro.alloc(token ) +// declare ptr @llvm.coro.noop() +// declare ptr @llvm.coro.frame() +// declare token @llvm.coro.id(i32 , ptr , ptr , ptr ) +// declare token @llvm.coro.id.async(i32 , i32 , ptr , ptr ) +// declare token @llvm.coro.id.retcon(i32 , i32 , ptr , ptr , ptr , ptr ) +// declare token @llvm.coro.id.retcon.once(i32 , i32 , ptr , ptr , ptr , ptr ) +// declare i1 @llvm.coro.end(ptr , i1 , token ) +// declare token @llvm.coro.end.results(...) +// declare i1 @llvm.coro.end.async(ptr , i1 , ...) +// declare i8 @llvm.coro.suspend(token , i1 ) +// declare token @llvm.coro.save(ptr ) +// declare {ptr, ptr, ptr} @llvm.coro.suspend.async(ptr , ptr , ... ... ) +// declare ptr @llvm.coro.prepare.async(ptr ) +// declare i1 @llvm.coro.suspend.retcon(...) +// declare void @await_suspend_function(ptr %awaiter, ptr %hdl) +// declare void @llvm.coro.await.suspend.void(ptr , ptr , ptr ) +// declare i1 @llvm.coro.await.suspend.bool(ptr , ptr , ptr ) +// declare void @llvm.coro.await.suspend.handle(ptr , ptr , ptr ) + +// ----------------------------------------------------------------------------- + +// declare void @llvm.coro.destroy(ptr ) func (p Program) tyCoDestroy() *types.Signature { if p.coDestroyTy == nil { i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) @@ -31,7 +66,7 @@ func (p Program) tyCoDestroy() *types.Signature { return p.coDestroyTy } -// declare void @llvm.coro.resume(i8*) +// declare void @llvm.coro.resume(ptr ) func (p Program) tyCoResume() *types.Signature { if p.coResumeTy == nil { i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) @@ -101,7 +136,7 @@ func (p Program) tyCoAlignI64() *types.Signature { return p.coAlignI64Ty } -// declare i8* @llvm.coro.begin(token, i8*) +// declare ptr @llvm.coro.begin(token , ptr ) func (p Program) tyCoBegin() *types.Signature { if p.coBeginTy == nil { tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) @@ -113,7 +148,7 @@ func (p Program) tyCoBegin() *types.Signature { return p.coBeginTy } -// declare i8* @llvm.coro.free(token, i8*) +// declare ptr @llvm.coro.free(token %id, ptr ) func (p Program) tyCoFree() *types.Signature { if p.coFreeTy == nil { tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) @@ -125,7 +160,7 @@ func (p Program) tyCoFree() *types.Signature { return p.coFreeTy } -// declare i1 @llvm.coro.alloc(token) +// declare i1 @llvm.coro.alloc(token ) func (p Program) tyCoAlloc() *types.Signature { if p.coAllocTy == nil { tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) @@ -155,7 +190,7 @@ func (p Program) tyCoFrame() *types.Signature { return p.coFrameTy } -// declare token @llvm.coro.id(i32, i8*, i8*, i8*) +// declare token @llvm.coro.id(i32 , ptr , ptr , ptr ) func (p Program) tyCoID() *types.Signature { if p.coIDTy == nil { i32 := types.NewParam(token.NoPos, nil, "", p.Int32().raw.Type) @@ -207,7 +242,7 @@ func (p Program) tyCoIDRetconOnce() *types.Signature { return p.coIDRetconOnceTy } -// declare i1 @llvm.coro.end(i8*, i1, token) +// declare i1 @llvm.coro.end(ptr , i1 , token ) func (p Program) tyCoEnd() *types.Signature { if p.coEndTy == nil { i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) @@ -322,6 +357,112 @@ func (p Program) tyCoAwaitSuspendHandle() *types.Signature { // ----------------------------------------------------------------------------- +func (b Builder) SetBlockOffset(offset int) { + b.blkOffset = offset +} + +func (b Builder) BlockOffset() int { + return b.blkOffset +} + +func (b Builder) Async() bool { + return b.async +} + +func (b Builder) AsyncToken() Expr { + return b.asyncToken +} + +func (b Builder) SetAsyncToken(token Expr) { + b.asyncToken = token +} + +func (b Builder) EndAsync() { + _, _, cleanBlk := b.onSuspBlk(b.blk) + b.Jump(cleanBlk) +} + +func promiseImplType(ty types.Type) types.Type { + ty = ty.Underlying().(*types.Struct).Field(0).Type() + if ptrTy, ok := ty.(*types.Pointer); ok { + return ptrTy.Elem() + } + panic(fmt.Sprintf("unexpected promise impl type: %v", ty)) +} + +/* +id := @llvm.coro.id(0, null, null, null) +frameSize := @llvm.coro.size.i64() +needAlloc := @llvm.coro.alloc(id) +; Allocate memory for return type and coroutine frame +frame := null + + if needAlloc { + frame := malloc(frameSize) + } + +hdl := @llvm.coro.begin(id, frame) +*retPtr = hdl +*/ +func (b Builder) BeginAsync(fn Function) { + ty := fn.Type.RawType().(*types.Signature).Results().At(0).Type() + ptrTy, ok := ty.(*types.Pointer) + if !ok { + panic("async function must return a *async.Promise") + } + promiseTy := b.Prog.Type(ptrTy.Elem(), InGo) + + b.async = true + entryBlk := fn.Block(0) + allocBlk := fn.Block(1) + cleanBlk := fn.Block(2) + suspdBlk := fn.Block(3) + beginBlk := fn.Block(4) + + b.SetBlock(entryBlk) + promiseSize := b.Const(constant.MakeUint64(b.Prog.SizeOf(promiseTy)), b.Prog.Int64()).SetName("promise.size") + promise := b.AllocZ(promiseSize).SetName("promise") + promise.Type = b.Prog.Pointer(promiseTy) + b.promise = promise + log.Printf("promise ptr: %v", promise.RawType()) + align := b.Const(constant.MakeInt64(0), b.Prog.CInt()).SetName("align") + null := b.Const(nil, b.Prog.CIntPtr()) + id := b.CoID(align, null, null, null).SetName("id") + b.asyncToken = id + needAlloc := b.CoAlloc(id).SetName("need.dyn.alloc") + b.If(needAlloc, allocBlk, beginBlk) + b.SetBlock(allocBlk) + frameSize := b.CoSizeI64().SetName("frame.size") + frame := b.AllocZ(frameSize).SetName("frame") + b.Jump(beginBlk) + b.SetBlock(beginBlk) + phi := b.Phi(b.Prog.VoidPtr()) + phi.SetName("frame") + phi.AddIncoming(b, []BasicBlock{entryBlk, allocBlk}, func(i int, blk BasicBlock) Expr { + if i == 0 { + return null + } + return frame + }) + hdl := b.CoBegin(id, phi.Expr) + hdl.SetName("hdl") + b.Store(promise, hdl) + + b.SetBlock(cleanBlk) + b.CoFree(id, hdl) + b.Jump(suspdBlk) + + b.SetBlock(suspdBlk) + b.CoEnd(hdl, b.Prog.BoolVal(false), b.Prog.TokenNone()) + b.Return(promise) + + b.onSuspBlk = func(nextBlk BasicBlock) (BasicBlock, BasicBlock, BasicBlock) { + return suspdBlk, nextBlk, cleanBlk + } +} + +// ----------------------------------------------------------------------------- + // declare void @llvm.coro.destroy(ptr ) func (b Builder) CoDestroy(hdl Expr) { fn := b.Pkg.cFunc("llvm.coro.destroy", b.Prog.tyCoDestroy()) @@ -335,11 +476,20 @@ func (b Builder) CoResume(hdl Expr) { } // declare i1 @llvm.coro.done(ptr ) -func (b Builder) CoDone(hdl Expr) Expr { +func (b Builder) coDone(hdl Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.done", b.Prog.tyCoDone()) return b.Call(fn, hdl) } +// return c.Char +func (b Builder) CoDone(hdl Expr) Expr { + bvar := b.coDone(hdl) + // TODO(lijie): inefficient + // %6 = zext i1 %5 to i64 + // %7 = trunc i64 %6 to i8 + return b.valFromData(b.Prog.Byte(), bvar.impl) +} + // declare ptr @llvm.coro.promise(ptr , i32 , i1 ) func (b Builder) CoPromise(ptr, align, from Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.promise", b.Prog.tyCoPromise()) @@ -402,12 +552,21 @@ func (b Builder) CoFrame() Expr { // declare token @llvm.coro.id(i32 , ptr , ptr , ptr ) func (b Builder) CoID(align Expr, promise, coroAddr, fnAddrs Expr) Expr { + if align.Type != b.Prog.Int32() { + panic("align must be i32") + } fn := b.Pkg.cFunc("llvm.coro.id", b.Prog.tyCoID()) return b.Call(fn, align, promise, coroAddr, fnAddrs) } // declare token @llvm.coro.id.async(i32 , i32 , ptr , ptr ) func (b Builder) CoIDAsync(contextSize, align, contextArg, asyncFnPtr Expr) Expr { + if contextSize.Type != b.Prog.Int32() { + panic("contextSize must be i32") + } + if align.Type != b.Prog.Int32() { + panic("align must be i32") + } fn := b.Pkg.cFunc("llvm.coro.id.async", b.Prog.tyCoIDAsync()) return b.Call(fn, contextSize, align, contextArg, asyncFnPtr) } @@ -439,16 +598,44 @@ func (b Builder) CoEndResults(args []Expr) Expr { // declare i1 @llvm.coro.end.async(ptr , i1 , ...) func (b Builder) CoEndAsync(handle, unwind Expr, args ...Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.end.async", b.Prog.tyCoEndAsync()) - args = append([]Expr{handle, unwind}, args...) - return b.Call(fn, args...) + vargs := append([]Expr{handle, unwind}, args...) + return b.Call(fn, vargs...) } // declare i8 @llvm.coro.suspend(token , i1 ) -func (b Builder) CoSuspend(save, final Expr) Expr { +func (b Builder) coSuspend(save, final Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.suspend", b.Prog.tyCoSuspend()) return b.Call(fn, save, final) } +func (b Builder) CoSuspend(save, final Expr) { + if !b.async { + panic(fmt.Errorf("suspend %v not in async block", b.Func.Name())) + } + ret := b.coSuspend(save, final) + // add resume block + b.Func.MakeBlock("") + nextBlk := b.Func.Block(b.blk.idx + 1) + susp, next, clean := b.onSuspBlk(nextBlk) + swt := b.Switch(ret, susp) + swt.Case(b.Const(constant.MakeInt64(0), b.Prog.Byte()), next) + swt.Case(b.Const(constant.MakeInt64(1), b.Prog.Byte()), clean) + swt.End(b) + b.SetBlock(nextBlk) +} + +func (b Builder) CoReturn(args ...Expr) { + if !b.async { + panic(fmt.Errorf("return %v not in async block", b.Func.Name())) + } + + b.Func.MakeBlock("") + nextBlk := b.Func.Block(b.blk.idx + 1) + _, _, cleanBlk := b.onSuspBlk(nextBlk) + b.Jump(cleanBlk) + b.SetBlock(nextBlk) +} + // declare token @llvm.coro.save(ptr ) func (b Builder) CoSave(hdl Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.save", b.Prog.tyCoSave()) @@ -484,3 +671,11 @@ func (b Builder) CoAwaitSuspendHandle(awaiter, handle, f Expr) { fn := b.Pkg.cFunc("llvm.coro.await.suspend.handle", b.Prog.tyCoAwaitSuspendHandle()) b.Call(fn, awaiter, handle, f) } + +func (b Builder) CoYield(setValueFn Function, value Expr) { + if !b.async { + panic(fmt.Errorf("yield %v not in async block", b.Func.Name())) + } + b.Call(setValueFn.Expr, b.promise, value) + b.CoSuspend(b.AsyncToken(), b.Prog.BoolVal(false)) +} diff --git a/ssa/decl.go b/ssa/decl.go index 6bdde0fd..ceae3e72 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -180,11 +180,11 @@ type Function = *aFunction // NewFunc creates a new function. func (p Package) NewFunc(name string, sig *types.Signature, bg Background) Function { - return p.NewFuncEx(name, sig, bg, false) + return p.NewFuncEx(name, sig, bg, false, false) } // NewFuncEx creates a new function. -func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, hasFreeVars bool) Function { +func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, hasFreeVars, async bool) Function { if v, ok := p.fns[name]; ok { return v } @@ -193,6 +193,9 @@ func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, has log.Println("NewFunc", name, t.raw.Type, "hasFreeVars:", hasFreeVars) } fn := llvm.AddFunction(p.mod, name, t.ll) + if async { + fn.AddFunctionAttr(p.Prog.ctx.CreateStringAttribute("presplitcoroutine", "")) + } ret := newFunction(fn, t, p, p.Prog, hasFreeVars) p.fns[name] = ret return ret @@ -268,7 +271,7 @@ func (p Function) NewBuilder() Builder { b := prog.ctx.NewBuilder() // TODO(xsw): Finalize may cause panic, so comment it. // b.Finalize() - return &aBuilder{b, nil, p, p.Pkg, prog} + return &aBuilder{impl: b, blk: nil, Func: p, Pkg: p.Pkg, Prog: prog} } // HasBody reports whether the function has a body. diff --git a/ssa/memory.go b/ssa/memory.go index d14fd71b..3f49be6c 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -232,6 +232,13 @@ func (b Builder) ArrayAlloca(telem Type, n Expr) (ret Expr) { return } +func (b Builder) OffsetPtr(ptr, offset Expr) Expr { + if debugInstr { + log.Printf("OffsetPtr %v, %v\n", ptr.impl, offset.impl) + } + return Expr{llvm.CreateGEP(b.impl, ptr.Type.ll, ptr.impl, []llvm.Value{offset.impl}), ptr.Type} +} + /* TODO(xsw): // ArrayAlloc allocates zero initialized space for an array of n elements of type telem. func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) { diff --git a/ssa/package.go b/ssa/package.go index 7be6d901..9ce5f227 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -481,6 +481,11 @@ func (p Program) Token() Type { return p.tokenTy } +func (p Program) TokenNone() Expr { + impl := llvm.ConstNull(p.Token().ll) + return Expr{impl: impl, Type: p.Token()} +} + /* // Eface returns the empty interface type. // It is equivalent to Any. diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index 15546ccc..c2b7cff4 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -63,6 +63,12 @@ type aBuilder struct { Func Function Pkg Package Prog Program + + async bool + asyncToken Expr + promise Expr + onSuspBlk func(blk BasicBlock) (susp BasicBlock, next BasicBlock, clean BasicBlock) + blkOffset int } // Builder represents a builder for creating instructions in a function. @@ -288,7 +294,7 @@ func (b Builder) Times(n Expr, loop func(i Expr)) { } // ----------------------------------------------------------------------------- -/* + type caseStmt struct { v llvm.Value blk llvm.BasicBlock @@ -326,7 +332,7 @@ func (b Builder) Switch(v Expr, defb BasicBlock) Switch { } return &aSwitch{v.impl, defb.first, nil} } -*/ + // ----------------------------------------------------------------------------- // Phi represents a phi node. diff --git a/x/async/_demo/asyncdemo/async.go b/x/async/_demo/asyncdemo/async.go deleted file mode 100644 index a0d49fd9..00000000 --- a/x/async/_demo/asyncdemo/async.go +++ /dev/null @@ -1,623 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "strings" - "time" - - "github.com/goplus/llgo/x/async" - "github.com/goplus/llgo/x/async/naive" - "github.com/goplus/llgo/x/tuple" -) - -// ----------------------------------------------------------------------------- - -func http(method string, url string, callback func(resp *Response, err error)) { - go func() { - body := "" - if strings.HasPrefix(url, "http://example.com/user/") { - name := url[len("http://example.com/user/"):] - body = `{"name":"` + name + `"}` - } else if strings.HasPrefix(url, "http://example.com/score/") { - body = "99.5" - } - time.Sleep(200 * time.Millisecond) - resp := &Response{StatusCode: 200, Body: body} - callback(resp, nil) - }() -} - -// ----------------------------------------------------------------------------- - -type Response struct { - StatusCode int - - Body string -} - -func (r *Response) Text() (co *async.Promise[tuple.Tuple2[string, error]]) { - co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) - return -} - -func (r *Response) TextCompiled() *naive.PromiseImpl[tuple.Tuple2[string, error]] { - co := &naive.PromiseImpl[tuple.Tuple2[string, error]]{} - co.Debug = "Text" - co.Func = func() { - switch co.Next { - case 0: - co.Next = -1 - co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) - return - default: - panic("Promise already done") - } - } - return co -} - -// async AsyncHttpGet(url string) (resp *Response, err error) { -// http("GET", url, func(resp *Response, err error) { -// return resp, err -// }) -// } -func AsyncHttpGet(url string) *async.Promise[tuple.Tuple2[*Response, error]] { - co := &async.Promise[tuple.Tuple2[*Response, error]]{} - http("GET", url, func(resp *Response, err error) { - co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) - }) - co.Suspend() - return co -} - -func AsyncHttpGetCompiled(url string) *naive.PromiseImpl[tuple.Tuple2[*Response, error]] { - co := &naive.PromiseImpl[tuple.Tuple2[*Response, error]]{} - co.Debug = "HttpGet" - co.Func = func() { - switch co.Next { - case 0: - co.Next = -1 - http("GET", url, func(resp *Response, err error) { - co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) - }) - co.Suspend() - return - default: - panic("Promise already done") - } - } - return co -} - -func AsyncHttpPost(url string) *async.Promise[tuple.Tuple2[*Response, error]] { - co := &async.Promise[tuple.Tuple2[*Response, error]]{} - http("POST", url, func(resp *Response, err error) { - co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) - }) - co.Suspend() - return co -} - -func AsyncHttpPostCompiled(url string) *naive.PromiseImpl[tuple.Tuple2[*Response, error]] { - P := &naive.PromiseImpl[tuple.Tuple2[*Response, error]]{} - P.Debug = "HttpPost" - P.Func = func() { - switch P.Next { - case 0: - P.Next = -1 - http("POST", url, func(resp *Response, err error) { - P.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) - }) - return - default: - panic("Promise already done") - } - } - return P -} - -// ----------------------------------------------------------------------------- - -type User struct { - Name string -} - -func GetUser(name string) (co *naive.PromiseImpl[tuple.Tuple2[User, error]]) { - resp, err := AsyncHttpGet("http://example.com/user/" + name).Await().Values() - if err != nil { - // return User{}, err - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - - if resp.StatusCode != 200 { - // return User{}, fmt.Errorf("http status code: %d", resp.StatusCode) - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) - return - } - - body, err := resp.Text().Await().Values() - if err != nil { - // return User{}, err - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - user := User{} - if err := json.Unmarshal([]byte(body), &user); err != nil { - // return User{}, err - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - - // return user, nil - co.Return(tuple.Tuple2[User, error]{V1: user, V2: nil}) - return -} - -func GetUserCompiled(name string) (co *naive.PromiseImpl[tuple.Tuple2[User, error]]) { - var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] - var state2 *naive.PromiseImpl[tuple.Tuple2[string, error]] - - co = &naive.PromiseImpl[tuple.Tuple2[User, error]]{} - co.Debug = "GetUser" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - state1 = AsyncHttpGetCompiled("http://example.com/user/" + name) - state1.Exec = co.Exec - state1.Parent = co - state1.Call() - return - case 1: - co.Next = 2 - resp, err := state1.Value().Values() - log.Printf("resp: %v, err: %v\n", resp, err) - if err != nil { - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - - if resp.StatusCode != 200 { - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) - return - } - - state2 = resp.TextCompiled() - state2.Exec = co.Exec - state2.Parent = co - state2.Call() - return - case 2: - co.Next = -1 - body, err := state2.Value().Values() - if err != nil { - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - user := User{} - log.Printf("body: %v\n", body) - if err := json.Unmarshal([]byte(body), &user); err != nil { - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - - log.Printf("resolve user: %+v\n", user) - co.Return(tuple.Tuple2[User, error]{V1: user, V2: nil}) - return - default: - panic(fmt.Errorf("Promise already done, %+v", co)) - } - } - return -} - -func GetScore() (co *naive.PromiseImpl[tuple.Tuple2[float64, error]]) { - resp, err := AsyncHttpGet("http://example.com/score/").Await().Values() - if err != nil { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - if resp.StatusCode != 200 { - // return 0, fmt.Errorf("http status code: %d", resp.StatusCode) - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) - return - } - - body, err := resp.Text().Await().Values() - if err != nil { - // return 0, err - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - score := 0.0 - if _, err := fmt.Sscanf(body, "%f", &score); err != nil { - // return 0, err - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - // return score, nil - co.Return(tuple.Tuple2[float64, error]{V1: score, V2: nil}) - return -} - -func GetScoreCompiled() *naive.PromiseImpl[tuple.Tuple2[float64, error]] { - var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] - var state2 *naive.PromiseImpl[tuple.Tuple2[string, error]] - - co := &naive.PromiseImpl[tuple.Tuple2[float64, error]]{} - co.Debug = "GetScore" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - state1 = AsyncHttpGetCompiled("http://example.com/score/") - state1.Exec = co.Exec - state1.Parent = co - state1.Call() - return - case 1: - co.Next = 2 - - resp, err := state1.Value().Values() - if err != nil { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - if resp.StatusCode != 200 { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) - return - } - - state2 = resp.TextCompiled() - state2.Exec = co.Exec - state2.Parent = co - state2.Call() - - return - case 2: - co.Next = -1 - body, err := state2.Value().Values() - if err != nil { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - score := 0.0 - if _, err := fmt.Sscanf(body, "%f", &score); err != nil { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - co.Return(tuple.Tuple2[float64, error]{V1: score, V2: nil}) - return - default: - panic("Promise already done") - } - } - return co -} - -func DoUpdate(op string) (co *naive.PromiseImpl[error]) { - resp, err := AsyncHttpPost("http://example.com/update/" + op).Await().Values() - if err != nil { - co.Return(err) - return - } - - if resp.StatusCode != 200 { - co.Return(fmt.Errorf("http status code: %d", resp.StatusCode)) - } - - co.Return(nil) - return -} - -func DoUpdateCompiled(op string) *naive.PromiseImpl[error] { - var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] - - co := &naive.PromiseImpl[error]{} - co.Debug = "DoUpdate" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - state1 = AsyncHttpPostCompiled("http://example.com/update/" + op) - state1.Exec = co.Exec - state1.Parent = co - state1.Call() - return - case 1: - co.Next = -1 - resp, err := state1.Value().Values() - if err != nil { - co.Return(err) - return - } - - if resp.StatusCode != 200 { - co.Return(fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - co.Return(nil) - return - default: - panic("Promise already done") - } - } - return co -} - -func GenInts() (co *naive.PromiseImpl[int]) { - co.Yield(3) - co.Yield(2) - co.Yield(5) - return -} - -func GenIntsCompiled() *naive.PromiseImpl[int] { - co := &naive.PromiseImpl[int]{} - co.Debug = "GenInts" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - co.Yield(3) - return - case 1: - co.Next = 2 - co.Yield(2) - return - case 2: - co.Next = 3 - co.Yield(5) - return - case 3: - co.Next = -1 - default: - panic("Generator already done") - } - } - return co -} - -// Generator with async calls and panic -func GenUsers() (co *naive.PromiseImpl[User]) { - u, err := GetUser("Alice").Await().Values() - if err != nil { - panic(err) - } - co.Yield(u) - u, err = GetUser("Bob").Await().Values() - if err != nil { - panic(err) - } - co.Yield(u) - u, err = GetUser("Cindy").Await().Values() - if err != nil { - panic(err) - } - co.Yield(u) - log.Printf("genUsers done\n") - return -} - -func GenUsersCompiled() (resolve *naive.PromiseImpl[User]) { - var state1, state2, state3 *naive.PromiseImpl[tuple.Tuple2[User, error]] - - co := &naive.PromiseImpl[User]{} - co.Debug = "GenUsers" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - state1 = GetUserCompiled("Alice") - state1.Exec = co.Exec - state1.Parent = co - state1.Call() - return - case 1: - co.Next = 2 - u, err := state1.Value().Values() - if err != nil { - panic(err) - } else { - co.Yield(u) - } - return - case 2: - co.Next = 3 - state2 = GetUserCompiled("Bob") - state2.Exec = co.Exec - state2.Parent = co - state2.Call() - return - case 3: - co.Next = 4 - u, err := state2.Value().Values() - if err != nil { - panic(err) - } else { - co.Yield(u) - } - return - case 4: - co.Next = 5 - state3 = GetUserCompiled("Cindy") - state3.Exec = co.Exec - state3.Parent = co - state3.Call() - return - case 5: - co.Next = 6 - u, err := state3.Value().Values() - if err != nil { - panic(err) - } else { - co.Yield(u) - } - return - case 6: - co.Next = -1 - default: - panic("Generator already done") - } - } - return co -} - -func Demo() (co *async.Promise[async.Void]) { - user, err := GetUser("1").Await().Values() - log.Println(user, err) - - user, err = naive.Race[tuple.Tuple2[User, error]](GetUser("2"), GetUser("3"), GetUser("4")).Value().Values() - log.Println(user, err) - - users := naive.All[tuple.Tuple2[User, error]]([]naive.AsyncCall[tuple.Tuple2[User, error]]{GetUser("5"), GetUser("6"), GetUser("7")}).Value() - log.Println(users, err) - - user, score, _ := naive.Await3Compiled[User, float64, async.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")).Value().Values() - log.Println(user, score, err) - - // for loop with generator - g := GenInts() - for { - g.Call() - if g.Done() { - break - } - log.Println("genInt:", g.Value(), g.Done()) - } - - // for loop with async generator - // for u, err := range GenUsers() {...} - g1 := GenUsers() - for { - g1.Call() - u := g1.Await() - if g1.Done() { - break - } - log.Println("genUser:", u, err) - } - - // TODO(lijie): select from multiple promises without channel - // select { - // case user := <-GetUser("123").Chan(): - // log.Println("user:", user) - // case score := <-GetScore().Chan(): - // log.Println("score:", score) - // case <-async.Timeout(5 * time.Second).Chan(): - // log.Println("timeout") - // } - - log.Println("Demo done") - co.Return(async.Void{}) - return -} - -func DemoCompiled() *naive.PromiseImpl[async.Void] { - var state1 *naive.PromiseImpl[tuple.Tuple2[User, error]] - var state2 *naive.PromiseImpl[tuple.Tuple2[User, error]] - var state3 *naive.PromiseImpl[[]tuple.Tuple2[User, error]] - var state4 *naive.PromiseImpl[tuple.Tuple3[tuple.Tuple2[User, error], tuple.Tuple2[float64, error], error]] - var g1 *naive.PromiseImpl[int] - var g2 *naive.PromiseImpl[User] - - P := &naive.PromiseImpl[async.Void]{} - P.Debug = "Demo" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - state1 = GetUserCompiled("1") - state1.Exec = P.Exec - state1.Parent = P - state1.Call() - return - case 1: - P.Next = 2 - user, err := state1.Value().Values() - log.Printf("user: %+v, err: %v\n", user, err) - - state2 = naive.Race[tuple.Tuple2[User, error]](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) - state2.Exec = P.Exec - state2.Parent = P - state2.Call() - return - case 2: - P.Next = 3 - user, err := state2.Value().Values() - log.Printf("race user: %+v, err: %v\n", user, err) - - state3 = naive.All[tuple.Tuple2[User, error]]([]naive.AsyncCall[tuple.Tuple2[User, error]]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) - state3.Exec = P.Exec - state3.Parent = P - state3.Call() - return - case 3: - - P.Next = 4 - users := state3.Value() - log.Println(users) - - state4 = naive.Await3Compiled[tuple.Tuple2[User, error], tuple.Tuple2[float64, error], error](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) - state4.Exec = P.Exec - state4.Parent = P - state4.Call() - return - case 4: - P.Next = 5 - user, score, _ := state4.Value().Values() - log.Println(user, score) - - g1 = GenIntsCompiled() - for { - g1.Call() - if g1.Done() { - break - } - - log.Printf("genInt: %+v, done: %v\n", g1.Value(), g1.Done()) - } - - g2 = GenUsersCompiled() - g2.Exec = P.Exec - g2.Parent = P - g2.Call() - return - case 5: - g2.Call() - if g2.Done() { - P.Next = -1 - log.Printf("Demo done\n") - P.Return(async.Void{}) - return - } - log.Printf("genUser: %+v, done: %v\n", g2.Value(), g2.Done()) - return - default: - panic("Promise already done") - } - } - return P -} - -func main() { - log.SetFlags(log.Lshortfile | log.LstdFlags) - log.Printf("=========== Run Naive Demo ===========\n") - v := naive.RunImpl[async.Void](DemoCompiled()) - log.Println(v) - log.Printf("=========== Run Naive Demo finished ===========\n") - - log.Printf("=========== Run Demo ===========\n") - v1 := Demo() - log.Println(v1) - log.Printf("=========== Run Demo finished ===========\n") -} diff --git a/x/async/_demo/asyncdemo/asyncdemo.go b/x/async/_demo/asyncdemo/asyncdemo.go new file mode 100644 index 00000000..45e0f959 --- /dev/null +++ b/x/async/_demo/asyncdemo/asyncdemo.go @@ -0,0 +1,229 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "strings" + "time" + + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/tuple" +) + +// ----------------------------------------------------------------------------- + +func http(method string, url string, callback func(resp *Response, err error)) { + go func() { + body := "" + if strings.HasPrefix(url, "http://example.com/user/") { + name := url[len("http://example.com/user/"):] + body = `{"name":"` + name + `"}` + } else if strings.HasPrefix(url, "http://example.com/score/") { + body = "99.5" + } + time.Sleep(200 * time.Millisecond) + resp := &Response{StatusCode: 200, Body: body} + callback(resp, nil) + }() +} + +// ----------------------------------------------------------------------------- + +type Response struct { + StatusCode int + + Body string +} + +func (r *Response) Text() (co async.Promise[tuple.Tuple2[string, error]]) { + co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) + return +} + +// async AsyncHttpGet(url string) (resp *Response, err error) { +// http("GET", url, func(resp *Response, err error) { +// return resp, err +// }) +// } +func AsyncHttpGet(url string) (co async.Promise[tuple.Tuple2[*Response, error]]) { + return co.Async(func(resolve func(tuple.Tuple2[*Response, error])) { + http("GET", url, func(resp *Response, err error) { + resolve(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) + }) + }) +} + +func AsyncHttpPost(url string) (co async.Promise[tuple.Tuple2[*Response, error]]) { + http("POST", url, func(resp *Response, err error) { + // co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) + }) + co.Suspend() + return +} + +// ----------------------------------------------------------------------------- + +type User struct { + Name string +} + +func GetUser(name string) (co async.Promise[tuple.Tuple2[User, error]]) { + resp, err := AsyncHttpGet("http://example.com/user/" + name).Await().Values() + if err != nil { + // return User{}, err + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) + return + } + + if resp.StatusCode != 200 { + // return User{}, fmt.Errorf("http status code: %d", resp.StatusCode) + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) + return + } + + body, err := resp.Text().Await().Values() + if err != nil { + // return User{}, err + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) + return + } + user := User{} + if err := json.Unmarshal([]byte(body), &user); err != nil { + // return User{}, err + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) + return + } + + // return user, nil + co.Return(tuple.Tuple2[User, error]{V1: user, V2: nil}) + return +} + +func GetScore() (co *async.Promise[tuple.Tuple2[float64, error]]) { + resp, err := AsyncHttpGet("http://example.com/score/").Await().Values() + if err != nil { + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) + return + } + + if resp.StatusCode != 200 { + // return 0, fmt.Errorf("http status code: %d", resp.StatusCode) + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) + return + } + + body, err := resp.Text().Await().Values() + if err != nil { + // return 0, err + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) + return + } + + score := 0.0 + if _, err := fmt.Sscanf(body, "%f", &score); err != nil { + // return 0, err + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) + return + } + + // return score, nil + co.Return(tuple.Tuple2[float64, error]{V1: score, V2: nil}) + return +} + +func DoUpdate(op string) (co *async.Promise[error]) { + resp, err := AsyncHttpPost("http://example.com/update/" + op).Await().Values() + if err != nil { + co.Return(err) + return + } + + if resp.StatusCode != 200 { + co.Return(fmt.Errorf("http status code: %d", resp.StatusCode)) + } + + co.Return(nil) + return +} + +func GenInts() (co *async.Promise[int]) { + co.Yield(3) + co.Yield(2) + co.Yield(5) + return +} + +// Generator with async calls and panic +func GenUsers() (co *async.Promise[User]) { + u, err := GetUser("Alice").Await().Values() + if err != nil { + panic(err) + } + co.Yield(u) + u, err = GetUser("Bob").Await().Values() + if err != nil { + panic(err) + } + co.Yield(u) + u, err = GetUser("Cindy").Await().Values() + if err != nil { + panic(err) + } + co.Yield(u) + log.Printf("genUsers done\n") + return +} + +func Demo() (co *async.Promise[async.Void]) { + user, err := GetUser("1").Await().Values() + log.Println(user, err) + + // user, err = naive.Race[tuple.Tuple2[User, error]](GetUser("2"), GetUser("3"), GetUser("4")).Value().Values() + // log.Println(user, err) + + // users := naive.All[tuple.Tuple2[User, error]]([]naive.AsyncCall[tuple.Tuple2[User, error]]{GetUser("5"), GetUser("6"), GetUser("7")}).Value() + // log.Println(users, err) + + // user, score, _ := naive.Await3Compiled[User, float64, async.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")).Value().Values() + // log.Println(user, score, err) + + // for loop with generator + g := GenInts() + for !g.Done() { + log.Println("genInt:", g.Value(), g.Done()) + g.Resume() + } + + // for loop with async generator + // for u, err := range GenUsers() {...} + g1 := GenUsers() + for !g1.Done() { + u := g1.Value() + log.Println("genUser:", u) + g1.Resume() + } + + // TODO(lijie): select from multiple promises without channel + // select { + // case user := <-GetUser("123").Chan(): + // log.Println("user:", user) + // case score := <-GetScore().Chan(): + // log.Println("score:", score) + // case <-async.Timeout(5 * time.Second).Chan(): + // log.Println("timeout") + // } + + log.Println("Demo done") + co.Return(async.Void{}) + return +} + +func main() { + log.SetFlags(log.Lshortfile | log.LstdFlags) + + log.Printf("=========== Run Demo ===========\n") + v1 := Demo() + log.Println(v1) + log.Printf("=========== Run Demo finished ===========\n") +} diff --git a/x/async/_demo/gendemo/gendemo.go b/x/async/_demo/gendemo/gendemo.go new file mode 100644 index 00000000..b58b3e27 --- /dev/null +++ b/x/async/_demo/gendemo/gendemo.go @@ -0,0 +1,21 @@ +package main + +import "github.com/goplus/llgo/x/async" + +func GenInts() (co *async.Promise[int]) { + print("1") + co.Yield(1) + print("2") + co.Yield(2) + print("3") + co.Yield(3) + print("4") + return +} + +func main() { + co := GenInts() + for !co.Done() { + print(co.Next()) + } +} diff --git a/x/async/async.go b/x/async/async.go index 83b4af40..0060a959 100644 --- a/x/async/async.go +++ b/x/async/async.go @@ -25,12 +25,8 @@ const ( LLGoPackage = "decl" ) -const debugAsync = false - type Void = [0]byte -type AsyncCall[TOut any] interface{} - // ----------------------------------------------------------------------------- type Promise[TOut any] struct { @@ -38,36 +34,45 @@ type Promise[TOut any] struct { value TOut } -// llgo:link PromiseImpl llgo.coAwait +// // llgo:link (*Promise).Await llgo.coAwait func (p *Promise[TOut]) Await() TOut { panic("should not executed") } -// llgo:link Return llgo.coReturn func (p *Promise[TOut]) Return(v TOut) { - panic("should not executed") + p.value = v + coReturn(p.hdl) } -// llgo:link Yield llgo.coYield -func (p *Promise[TOut]) Yield(v TOut) { - panic("should not executed") -} +// llgo:link (*Promise).Yield llgo.coYield +func (p *Promise[TOut]) Yield(v TOut) {} -// llgo:link Suspend llgo.coSuspend -func (p *Promise[TOut]) Suspend() { - panic("should not executed") -} +// llgo:link (*Promise).Suspend llgo.coSuspend +func (p *Promise[TOut]) Suspend() {} -// llgo:link Resume llgo.coResume func (p *Promise[TOut]) Resume() { - panic("should not executed") + coResume(p.hdl) +} + +func (p *Promise[TOut]) Next() TOut { + coResume(p.hdl) + return p.value +} + +// TODO(lijie): should merge to Yield() +// call by llgo.coYield +func (p *Promise[TOut]) setValue(v TOut) { + p.value = v } func (p *Promise[TOut]) Value() TOut { return p.value } -// llgo:link Run llgo.coRun +func (p *Promise[TOut]) Done() bool { + return coDone(p.hdl) != 0 +} + func Run[TOut any](f func() TOut) TOut { panic("should not executed") } diff --git a/x/async/coro.go b/x/async/coro.go new file mode 100644 index 00000000..62393d4e --- /dev/null +++ b/x/async/coro.go @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package async + +import ( + "unsafe" + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +// llgo:link coDone llgo.coDone +func coDone(hdl unsafe.Pointer) c.Char { + panic("should not executed") +} + +// llgo:link coResume llgo.coResume +func coResume(hdl unsafe.Pointer) { + panic("should not executed") +} + +// llgo:link coReturn llgo.coReturn +func coReturn(hdl unsafe.Pointer) { + panic("should not executed") +} diff --git a/x/async/naive/extra.go b/x/async/naive/extra.go deleted file mode 100644 index 21c00af3..00000000 --- a/x/async/naive/extra.go +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package naive - -import ( - "log" - "sync" - "time" - _ "unsafe" - - "github.com/goplus/llgo/x/async" - "github.com/goplus/llgo/x/tuple" -) - -// ----------------------------------------------------------------------------- - -func TimeoutCompiled(d time.Duration) *PromiseImpl[async.Void] { - P := &PromiseImpl[async.Void]{} - P.Debug = "Timeout" - P.Func = func() { - go func() { - time.Sleep(d) - P.Return(async.Void{}) - }() - } - return P -} - -type Result[T any] struct { - V T - Err error -} - -func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { - if len(acs) == 0 { - panic("race: no promise") - } - ps := make([]*PromiseImpl[OutT], len(acs)) - for idx, ac := range acs { - ps[idx] = ac.(*PromiseImpl[OutT]) - } - remaining := len(acs) - returned := false - P := &PromiseImpl[OutT]{} - P.Debug = "Race" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - for _, p := range ps { - p.Exec = P.Exec - p.Parent = P - p.Call() - } - return - case 1: - remaining-- - if remaining < 0 { - log.Fatalf("race: remaining < 0: %+v\n", remaining) - } - if returned { - return - } - - for _, p := range ps { - if p.Done() { - if debugAsync { - log.Printf("async.Race done: %+v won the race\n", p) - } - returned = true - P.Return(p.value) - return - } - } - log.Fatalf("no promise done: %+v\n", ps) - return - default: - panic("unreachable") - } - } - return P -} - -func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]OutT] { - ps := make([]*PromiseImpl[OutT], len(acs)) - for idx, ac := range acs { - ps[idx] = ac.(*PromiseImpl[OutT]) - } - done := 0 - P := &PromiseImpl[[]OutT]{} - P.Debug = "All" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - for _, p := range ps { - p.Exec = P.Exec - p.Parent = P - p.Call() - } - return - case 1: - done++ - if done < len(acs) { - return - } - P.Next = -1 - - for _, p := range ps { - if !p.Done() { - log.Fatalf("async.All: not done: %+v\n", p) - } - } - - ret := make([]OutT, len(acs)) - for idx, p := range ps { - ret[idx] = p.value - } - if debugAsync { - log.Printf("async.All done: %+v\n", ret) - } - P.Return(ret) - return - default: - panic("unreachable") - } - } - return P -} - -// llgo:link Await2 llgo.await -func Await2Compiled[OutT1, OutT2 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], - timeout ...time.Duration) (ret *PromiseImpl[tuple.Tuple3[OutT1, OutT2, error]]) { - p1 := ac1.(*PromiseImpl[OutT1]) - p2 := ac2.(*PromiseImpl[OutT2]) - remaining := 2 - P := &PromiseImpl[tuple.Tuple3[OutT1, OutT2, error]]{} - P.Debug = "Await2" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - p1.Exec = P.Exec - p1.Parent = P - p1.Call() - - p2.Exec = P.Exec - p2.Parent = P - p2.Call() - return - case 1: - remaining-- - if remaining > 0 { - return - } - P.Next = -1 - if !p1.Done() || !p2.Done() { - log.Fatalf("async.Await2: not done: %+v, %+v\n", p1, p2) - } - - P.Return(tuple.Tuple3[OutT1, OutT2, error]{ - V1: p1.value, - V2: p2.value, - V3: nil, - }) - return - default: - panic("unreachable") - } - } - return P -} - -// llgo:link Await2 llgo.await -func Await3Compiled[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], - timeout ...time.Duration) *PromiseImpl[tuple.Tuple3[OutT1, OutT2, OutT3]] { - p1 := ac1.(*PromiseImpl[OutT1]) - p2 := ac2.(*PromiseImpl[OutT2]) - p3 := ac3.(*PromiseImpl[OutT3]) - remaining := 3 - P := &PromiseImpl[tuple.Tuple3[OutT1, OutT2, OutT3]]{} - P.Debug = "Await3" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - p1.Exec = P.Exec - p1.Parent = P - p1.Call() - - p2.Exec = P.Exec - p2.Parent = P - p2.Call() - - p3.Exec = P.Exec - p3.Parent = P - p3.Call() - return - case 1: - remaining-- - if remaining > 0 { - return - } - P.Next = -1 - // TODO(lijie): return every error? - if !p1.Done() || !p2.Done() || !p3.Done() { - log.Fatalf("async.Await3: not done: %+v, %+v, %+v\n", p1, p2, p3) - } - - P.Return(tuple.Tuple3[OutT1, OutT2, OutT3]{ - V1: p1.value, - V2: p2.value, - V3: p3.value, - }) - return - default: - panic("unreachable") - } - } - return P -} - -func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[[]OutT] { - P := &PromiseImpl[[]OutT]{} - P.Debug = "Parallel" - P.Func = func() { - ret := make([]OutT, len(acs)) - wg := sync.WaitGroup{} - for idx, ac := range acs { - idx := idx - ac := ac - wg.Add(1) - go func(ac AsyncCall[OutT]) { - v := RunImpl[OutT](ac) - ret[idx] = v - wg.Done() - }(ac) - } - wg.Wait() - P.Return(ret) - } - return P -} - -func PAwait3Compiled[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) *PromiseImpl[tuple.Tuple4[OutT1, OutT2, OutT3, error]] { - P := &PromiseImpl[tuple.Tuple4[OutT1, OutT2, OutT3, error]]{} - P.Debug = "PAwait3" - P.Func = func() { - ret := tuple.Tuple4[OutT1, OutT2, OutT3, error]{} - wg := sync.WaitGroup{} - wg.Add(3) - go func() { - ret.V1 = RunImpl[OutT1](ac1) - wg.Done() - }() - go func() { - ret.V2 = RunImpl[OutT2](ac2) - wg.Done() - }() - go func() { - ret.V3 = RunImpl[OutT3](ac3) - wg.Done() - }() - wg.Wait() - P.Return(ret) - } - return P -} diff --git a/x/async/naive/naive.go b/x/async/naive/naive.go deleted file mode 100644 index a8de2bd9..00000000 --- a/x/async/naive/naive.go +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package naive - -import ( - "log" - "sync" -) - -const debugAsync = false - -// ----------------------------------------------------------------------------- - -type asyncCall interface { - parent() asyncCall - Resume() - Call() - Done() bool -} - -type AsyncCall[OutT any] interface { - Resume() -} - -type executor struct { - acs []asyncCall - mu sync.Mutex - cond *sync.Cond -} - -func newExecutor() *executor { - e := &executor{} - e.cond = sync.NewCond(&e.mu) - return e -} - -func (e *executor) schedule(ac asyncCall) { - e.mu.Lock() - e.acs = append(e.acs, ac) - e.mu.Unlock() - e.cond.Signal() -} - -func RunImpl[OutT any](ac AsyncCall[OutT]) OutT { - e := newExecutor() - p := ac.(*PromiseImpl[OutT]) - p.Exec = e - var rootAc asyncCall = p - e.schedule(rootAc) - - for { - e.mu.Lock() - for len(e.acs) == 0 { - e.cond.Wait() - } - e.mu.Unlock() - ac := e.acs[0] - e.acs = e.acs[1:] - ac.Call() - if ac.Done() && ac == rootAc { - return p.value - } - } -} - -// ----------------------------------------------------------------------------- - -type PromiseImpl[TOut any] struct { - Debug string - Next int - Exec *executor - Parent asyncCall - - Func func() - value TOut - c chan TOut -} - -func (p *PromiseImpl[TOut]) parent() asyncCall { - return p.Parent -} - -func (p *PromiseImpl[TOut]) Resume() { - if debugAsync { - log.Printf("Resume task: %+v\n", p) - } - p.Exec.schedule(p) -} - -func (p *PromiseImpl[TOut]) Done() bool { - return p.Next == -1 -} - -func (p *PromiseImpl[TOut]) Call() { - p.Func() -} - -func (p *PromiseImpl[TOut]) Suspend() { - -} - -func (p *PromiseImpl[TOut]) Return(v TOut) { - // TODO(lijie): panic if already resolved - p.value = v - if p.c != nil { - p.c <- v - } - if debugAsync { - log.Printf("Return task: %+v\n", p) - } - if p.Parent != nil { - p.Parent.Resume() - } -} - -func (p *PromiseImpl[TOut]) Yield(v TOut) { - p.value = v - if debugAsync { - log.Printf("Yield task: %+v\n", p) - } - if p.Parent != nil { - p.Parent.Resume() - } -} - -func (p *PromiseImpl[TOut]) Value() TOut { - return p.value -} - -func (p *PromiseImpl[TOut]) Chan() <-chan TOut { - if p.c == nil { - p.c = make(chan TOut, 1) - p.Func() - } - return p.c -} - -func (p *PromiseImpl[TOut]) Await() (ret TOut) { - panic("should not called") -}