diff --git a/cl/_testdata/async/in.go b/cl/_testdata/async/in.go new file mode 100644 index 00000000..d83cdae8 --- /dev/null +++ b/cl/_testdata/async/in.go @@ -0,0 +1,27 @@ +package async + +import ( + "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() { + v := co.Value() + r += v + co.Next() + } + return r +} diff --git a/cl/_testdata/async/out.ll b/cl/_testdata/async/out.ll new file mode 100644 index 00000000..3f76ea07 --- /dev/null +++ b/cl/_testdata/async/out.ll @@ -0,0 +1,201 @@ +; ModuleID = 'async' +source_filename = "async" + +%"github.com/goplus/llgo/x/async.Promise[int]" = type { ptr, i64 } + +@"async.init$guard" = global i1 false, align 1 + +define ptr @async.GenInts() presplitcoroutine { +entry: + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %frame.size = call i64 @llvm.coro.size.i64() + %alloc.size = add i64 16, %frame.size + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %alloc.size) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_5 + +alloc: ; preds = %entry + %0 = getelementptr ptr, ptr %promise, i64 16 + br label %_llgo_5 + +clean: ; preds = %_llgo_8, %_llgo_7, %_llgo_6, %_llgo_5 + %1 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %_llgo_8, %_llgo_7, %_llgo_6, %_llgo_5, %clean + %2 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +trap: ; preds = %_llgo_8 + call void @llvm.trap() + unreachable + +_llgo_5: ; preds = %alloc, %entry + %frame = phi ptr [ null, %entry ], [ %0, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame) + store ptr %hdl, ptr %promise, align 8 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) + %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 2) + %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 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 3) + %5 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %5, label %suspend [ + i8 0, label %_llgo_8 + i8 1, label %clean + ] + +_llgo_8: ; preds = %_llgo_7 + %6 = call i8 @llvm.coro.suspend(token %id, i1 true) + switch i8 %6, label %suspend [ + i8 0, label %trap + i8 1, label %clean + ] +} + +define i64 @async.UseGenInts() { +_llgo_0: + %0 = call ptr @async.WrapGenInts() + br label %_llgo_3 + +_llgo_1: ; preds = %_llgo_3 + %1 = call i64 @"github.com/goplus/llgo/x/async.(*Promise).Value[int]"(ptr %0) + %2 = add i64 %3, %1 + call void @"github.com/goplus/llgo/x/async.(*Promise).Next[int]"(ptr %0) + 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 @async.WrapGenInts() presplitcoroutine { +entry: + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %frame.size = call i64 @llvm.coro.size.i64() + %alloc.size = add i64 16, %frame.size + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %alloc.size) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_5 + +alloc: ; preds = %entry + %0 = getelementptr ptr, ptr %promise, i64 16 + br label %_llgo_5 + +clean: ; preds = %_llgo_5 + %1 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %_llgo_5, %clean + %2 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +trap: ; preds = %_llgo_5 + call void @llvm.trap() + unreachable + +_llgo_5: ; preds = %alloc, %entry + %frame = phi ptr [ null, %entry ], [ %0, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame) + store ptr %hdl, ptr %promise, align 8 + %3 = call ptr @async.GenInts() + %4 = call i8 @llvm.coro.suspend(token %id, i1 true) + switch i8 %4, label %suspend [ + i8 0, label %trap + i8 1, label %clean + ] +} + +define void @async.init() { +_llgo_0: + %0 = load i1, ptr @"async.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"async.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.size.i64() + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +; Function Attrs: nounwind +declare i1 @llvm.coro.alloc(token) + +; Function Attrs: nounwind +declare ptr @llvm.coro.begin(token, ptr writeonly) + +; Function Attrs: nounwind memory(argmem: read) +declare ptr @llvm.coro.free(token, ptr nocapture readonly) + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(ptr, i1, token) + +; Function Attrs: cold noreturn nounwind memory(inaccessiblemem: write) +declare void @llvm.trap() + +define void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %0, i64 %1) { +_llgo_0: + %2 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 1 + store i64 %1, ptr %2, align 4 + ret void +} + +; Function Attrs: nounwind +declare i8 @llvm.coro.suspend(token, i1) + +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).Value[int]"(ptr %0) { +_llgo_0: + %1 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 1 + %2 = load i64, ptr %1, align 4 + ret i64 %2 +} + +define void @"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) + ret void +} + +; Function Attrs: nounwind memory(argmem: readwrite) +declare i1 @llvm.coro.done(ptr nocapture readonly) + +declare void @llvm.coro.resume(ptr) + diff --git a/cl/async.go b/cl/async.go new file mode 100644 index 00000000..bb81e01d --- /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/constant" + "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, nil) +} + +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) getSetValueFunc(fn *ssa.Function) llssa.Function { + typ := fn.Signature.Recv().Type() + mthds := p.goProg.MethodSets.MethodSet(typ) + 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[") { + setValueFn, _, _ := p.compileFunction(ssaMthd) + return setValueFn + } + } + } + panic("method setValue not found on type " + typ.String()) +} + +func (p *context) coReturn(b llssa.Builder, fn *ssa.Function, args []ssa.Value) { + setValueFn := p.getSetValueFunc(fn) + value := p.compileValue(b, args[1]) + b.CoReturn(setValueFn, value) +} + +func (p *context) coYield(b llssa.Builder, fn *ssa.Function, args []ssa.Value) { + setValueFn := p.getSetValueFunc(fn) + value := p.compileValue(b, args[1]) + // TODO(lijie): find whether the co.Yield/co.Return is the last instruction + final := b.Const(constant.MakeBool(false), b.Prog.Bool()) + b.CoYield(setValueFn, value, final) +} + +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 ea011b05..3eb98bf1 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -218,6 +218,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]) @@ -227,13 +228,24 @@ 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 { - fn.MakeBlocks(nblk) // to set fn.HasBody() = true + var entryBlk, allocBlk, cleanBlk, suspdBlk, trapBlk, beginBlk llssa.BasicBlock + if async { + nBlkOff = 5 + entryBlk = fn.MakeBlock("entry") + allocBlk = fn.MakeBlock("alloc") + cleanBlk = fn.MakeBlock("clean") + suspdBlk = fn.MakeBlock("suspend") + trapBlk = fn.MakeBlock("trap") + } + fn.MakeBlocks(nblk) // to set fn.HasBody() = true + beginBlk = fn.Block(nBlkOff) if f.Recover != nil { // set recover block - fn.SetRecover(fn.Block(f.Recover.Index)) + // TODO(lijie): fix this for async function because of the block offset increase + fn.SetRecover(fn.Block(f.Recover.Index + nBlkOff)) } p.inits = append(p.inits, func() { p.fn = fn @@ -249,6 +261,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, entryBlk, allocBlk, cleanBlk, suspdBlk, trapBlk, beginBlk) + } p.bvals = make(map[ssa.Value]llssa.Expr) off := make([]int, len(f.Blocks)) for i, block := range f.Blocks { @@ -284,7 +300,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 { @@ -322,7 +338,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do modPtr := pkg.PyNewModVar(modName, true).Expr mod := b.Load(modPtr) cond := b.BinOp(token.NEQ, mod, prog.Nil(mod.Type)) - newBlk := fn.MakeBlock() + newBlk := fn.MakeBlock("") b.If(cond, jumpTo, newBlk) b.SetBlockEx(newBlk, llssa.AtEnd, false) b.Store(modPtr, b.PyImportMod(modPath)) @@ -654,7 +670,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/compile_test.go b/cl/compile_test.go index fa77673c..1fbbd086 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -53,7 +53,7 @@ func TestFromTestrt(t *testing.T) { } func TestFromTestdata(t *testing.T) { - cltest.FromDir(t, "", "./_testdata", false) + cltest.FromDir(t, "", "./_testdata", true) } func TestFromTestpymath(t *testing.T) { @@ -124,3 +124,114 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 } `) } + +func TestAsyncFunc(t *testing.T) { + testCompile(t, `package foo + +import "github.com/goplus/llgo/x/async" + +func GenInts() (co *async.Promise[int]) { + co.Yield(1) + co.Yield(2) + return +} +`, `; ModuleID = 'foo' +source_filename = "foo" + +@"foo.init$guard" = global i1 false, align 1 + +define ptr @foo.GenInts() presplitcoroutine { +entry: + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %frame.size = call i64 @llvm.coro.size.i64() + %alloc.size = add i64 16, %frame.size + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %alloc.size) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_5 + +alloc: ; preds = %entry + %0 = getelementptr ptr, ptr %promise, i64 16 + br label %_llgo_5 + +clean: ; preds = %_llgo_7, %_llgo_6, %_llgo_5 + %1 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %_llgo_7, %_llgo_6, %_llgo_5, %clean + %2 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +trap: ; preds = %_llgo_7 + call void @llvm.trap() + unreachable + +_llgo_5: ; preds = %alloc, %entry + %frame = phi ptr [ null, %entry ], [ %0, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame) + store ptr %hdl, ptr %promise, align 8 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) + %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 2) + %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 + %5 = call i8 @llvm.coro.suspend(token %id, i1 true) + switch i8 %5, label %suspend [ + i8 0, label %trap + i8 1, label %clean + ] +} + +define void @foo.init() { +_llgo_0: + %0 = load i1, ptr @"foo.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"foo.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.size.i64() + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +; Function Attrs: nounwind +declare i1 @llvm.coro.alloc(token) + +; Function Attrs: nounwind +declare ptr @llvm.coro.begin(token, ptr writeonly) + +; Function Attrs: nounwind memory(argmem: read) +declare ptr @llvm.coro.free(token, ptr nocapture readonly) + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(ptr, i1, token) + +; Function Attrs: cold noreturn nounwind memory(inaccessiblemem: write) +declare void @llvm.trap() + +declare void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr, i64) + +; Function Attrs: nounwind +declare i8 @llvm.coro.suspend(token, i1) + +`) +} diff --git a/cl/import.go b/cl/import.go index 6729b7cf..65e426f0 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..50cfc22f 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, cv, 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/cl_test.go b/ssa/cl_test.go index 837a990a..4ba7132d 100644 --- a/ssa/cl_test.go +++ b/ssa/cl_test.go @@ -50,7 +50,7 @@ func TestFromTestrt(t *testing.T) { } func TestFromTestdata(t *testing.T) { - cltest.FromDir(t, "", "../cl/_testdata", false) + cltest.FromDir(t, "", "../cl/_testdata", true) } func TestMakeInterface(t *testing.T) { diff --git a/ssa/coro.go b/ssa/coro.go new file mode 100644 index 00000000..955a2b72 --- /dev/null +++ b/ssa/coro.go @@ -0,0 +1,690 @@ +/* + * 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 ssa + +import ( + "fmt" + "go/constant" + "go/token" + "go/types" +) + +// 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 i64 @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) + params := types.NewTuple(i8Ptr) + p.coDestroyTy = types.NewSignatureType(nil, nil, nil, params, nil, false) + } + return p.coDestroyTy +} + +// 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) + params := types.NewTuple(i8Ptr) + p.coResumeTy = types.NewSignatureType(nil, nil, nil, params, nil, false) + } + return p.coResumeTy +} + +// declare i1 @llvm.coro.done(ptr ) +func (p Program) tyCoDone() *types.Signature { + if p.coDoneTy == nil { + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i8Ptr) + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", p.Bool().raw.Type)) + p.coDoneTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coDoneTy +} + +// declare ptr @llvm.coro.promise(ptr , i32 , i1 ) +func (p Program) tyCoPromise() *types.Signature { + if p.coPromiseTy == nil { + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + i32 := types.NewParam(token.NoPos, nil, "", p.Int32().raw.Type) + boolParam := types.NewParam(token.NoPos, nil, "", p.Bool().raw.Type) + params := types.NewTuple(i8Ptr, i32, boolParam) + results := types.NewTuple(i8Ptr) + p.coPromiseTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coPromiseTy +} + +// declare i32 @llvm.coro.size.i32() +func (p Program) tyCoSizeI32() *types.Signature { + if p.coSizeI32Ty == nil { + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", p.Int32().raw.Type)) + p.coSizeI32Ty = types.NewSignatureType(nil, nil, nil, nil, results, false) + } + return p.coSizeI32Ty +} + +// declare i64 @llvm.coro.size.i64() +func (p Program) tyCoSizeI64() *types.Signature { + if p.coSizeI64Ty == nil { + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", p.Int64().raw.Type)) + p.coSizeI64Ty = types.NewSignatureType(nil, nil, nil, nil, results, false) + } + return p.coSizeI64Ty +} + +// declare i32 @llvm.coro.align.i32() +func (p Program) tyCoAlignI32() *types.Signature { + if p.coAlignI32Ty == nil { + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", p.Int32().raw.Type)) + p.coAlignI32Ty = types.NewSignatureType(nil, nil, nil, nil, results, false) + } + return p.coAlignI32Ty +} + +// declare i64 @llvm.coro.align.i64() +func (p Program) tyCoAlignI64() *types.Signature { + if p.coAlignI64Ty == nil { + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", p.Int64().raw.Type)) + p.coAlignI64Ty = types.NewSignatureType(nil, nil, nil, nil, results, false) + } + return p.coAlignI64Ty +} + +// 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) + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(tokenParam, i8Ptr) + results := types.NewTuple(i8Ptr) + p.coBeginTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coBeginTy +} + +// 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) + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(tokenParam, i8Ptr) + results := types.NewTuple(i8Ptr) + p.coFreeTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coFreeTy +} + +// 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) + params := types.NewTuple(tokenParam) + boolParam := types.NewParam(token.NoPos, nil, "", p.Bool().raw.Type) + results := types.NewTuple(boolParam) + p.coAllocTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coAllocTy +} + +// declare ptr @llvm.coro.noop() +func (p Program) tyCoNoop() *types.Signature { + if p.coNoopTy == nil { + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)) + p.coNoopTy = types.NewSignatureType(nil, nil, nil, nil, results, false) + } + return p.coNoopTy +} + +// declare ptr @llvm.coro.frame() +func (p Program) tyCoFrame() *types.Signature { + if p.coFrameTy == nil { + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)) + p.coFrameTy = types.NewSignatureType(nil, nil, nil, nil, results, false) + } + return p.coFrameTy +} + +// 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) + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i32, i8Ptr, i8Ptr, i8Ptr) + tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) + results := types.NewTuple(tokenParam) + p.coIDTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coIDTy +} + +/* +// declare token @llvm.coro.id.async(i32 , i32 , ptr , ptr ) +func (p Program) tyCoIDAsync() *types.Signature { + if p.coIDAsyncTy == nil { + i32 := types.NewParam(token.NoPos, nil, "", p.Int32().raw.Type) + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i32, i32, i8Ptr, i8Ptr) + tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) + results := types.NewTuple(tokenParam) + p.coIDAsyncTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coIDAsyncTy +} + +// declare token @llvm.coro.id.retcon(i32 , i32 , ptr , ptr , ptr , ptr ) +func (p Program) tyCoIDRetcon() *types.Signature { + if p.coIDRetconTy == nil { + i32 := types.NewParam(token.NoPos, nil, "", p.Int32().raw.Type) + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i32, i32, i8Ptr, i8Ptr, i8Ptr, i8Ptr) + tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) + results := types.NewTuple(tokenParam) + p.coIDRetconTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coIDRetconTy +} + +// declare token @llvm.coro.id.retcon.once(i32 , i32 , ptr , ptr , ptr , ptr ) +func (p Program) tyCoIDRetconOnce() *types.Signature { + if p.coIDRetconOnceTy == nil { + i32 := types.NewParam(token.NoPos, nil, "", p.Int32().raw.Type) + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i32, i32, i8Ptr, i8Ptr, i8Ptr, i8Ptr) + tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) + results := types.NewTuple(tokenParam) + p.coIDRetconOnceTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coIDRetconOnceTy +} +*/ + +// 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) + boolParam := types.NewParam(token.NoPos, nil, "", p.Bool().raw.Type) + tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) + params := types.NewTuple(i8Ptr, boolParam, tokenParam) + results := types.NewTuple(boolParam) + p.coEndTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coEndTy +} + +/* +// TODO(lijie): varargs +// declare token @llvm.coro.end.results(...) +func (p Program) tyCoEndResults() *types.Signature { + panic("not implemented") +} + +// TODO(lijie): varargs +// declare i1 @llvm.coro.end.async(ptr , i1 , ...) +func (p Program) tyCoEndAsync() *types.Signature { + panic("not implemented") +} +*/ + +// declare i8 @llvm.coro.suspend(token , i1 ) +func (p Program) tyCoSuspend() *types.Signature { + if p.coSuspendTy == nil { + tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) + boolParam := types.NewParam(token.NoPos, nil, "", p.Bool().raw.Type) + params := types.NewTuple(tokenParam, boolParam) + paramByte := types.NewParam(token.NoPos, nil, "", p.Byte().raw.Type) + results := types.NewTuple(paramByte) + p.coSuspendTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coSuspendTy +} + +/* +// declare token @llvm.coro.save(ptr ) +func (p Program) tyCoSave() *types.Signature { + if p.coSaveTy == nil { + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i8Ptr) + tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) + results := types.NewTuple(tokenParam) + p.coSaveTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coSaveTy +} + +// TODO(lijie): varargs +// declare {ptr, ptr, ptr} @llvm.coro.suspend.async(ptr , ptr , ... ... ) +func (p Program) tyCoSuspendAsync() *types.Signature { + panic("not implemented") +} + +// declare ptr @llvm.coro.prepare.async(ptr ) +func (p Program) tyCoPrepareAsync() *types.Signature { + if p.coPrepareAsyncTy == nil { + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i8Ptr) + results := types.NewTuple(i8Ptr) + p.coPrepareAsyncTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coPrepareAsyncTy +} + +// declare i1 @llvm.coro.suspend.retcon(...) +func (p Program) tyCoSuspendRetcon() *types.Signature { + panic("not implemented") +} + +// declare void @await_suspend_function(ptr %awaiter, ptr %hdl) +func (p Program) tyCoAwaitSuspendFunction() *types.Signature { + if p.coAwaitSuspendFunctionTy == nil { + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i8Ptr, i8Ptr) + p.coAwaitSuspendFunctionTy = types.NewSignatureType(nil, nil, nil, params, nil, false) + } + return p.coAwaitSuspendFunctionTy +} + +// declare void @llvm.coro.await.suspend.void(ptr , ptr , ptr ) +func (p Program) tyCoAwaitSuspendVoid() *types.Signature { + if p.coAwaitSuspendVoidTy == nil { + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i8Ptr, i8Ptr, i8Ptr) + p.coAwaitSuspendVoidTy = types.NewSignatureType(nil, nil, nil, params, nil, false) + } + return p.coAwaitSuspendVoidTy +} + +// declare i1 @llvm.coro.await.suspend.bool(ptr , ptr , ptr ) +func (p Program) tyCoAwaitSuspendBool() *types.Signature { + if p.coAwaitSuspendBoolTy == nil { + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i8Ptr, i8Ptr, i8Ptr) + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", p.Bool().raw.Type)) + p.coAwaitSuspendBoolTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.coAwaitSuspendBoolTy +} + +// declare void @llvm.coro.await.suspend.handle(ptr , ptr , ptr ) +func (p Program) tyCoAwaitSuspendHandle() *types.Signature { + if p.coAwaitSuspendHandleTy == nil { + i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(i8Ptr, i8Ptr, i8Ptr) + p.coAwaitSuspendHandleTy = types.NewSignatureType(nil, nil, nil, params, nil, false) + } + return p.coAwaitSuspendHandleTy +} +*/ + +// ----------------------------------------------------------------------------- + +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) EndAsync() { + b.onReturn() +} + +/* + pesudo code: + +retPtr := malloc(sizeof(Promise)) +id := @llvm.coro.id(0, null, null, null) +promiseSize := sizeof(Promise[T]) +frameSize := @llvm.coro.size.i64() +allocSize := promiseSize + frameSize +promise := malloc(allocSize) +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, entryBlk, allocBlk, cleanBlk, suspdBlk, trapBlk, beginBlk BasicBlock) { + 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 + + b.SetBlock(entryBlk) + 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 + promiseSize := b.Const(constant.MakeUint64(b.Prog.SizeOf(promiseTy)), b.Prog.Int64()).SetName("alloc.size") + frameSize := b.CoSizeI64().SetName("frame.size") + allocSize := b.BinOp(token.ADD, promiseSize, frameSize).SetName("alloc.size") + promise := b.AllocZ(allocSize).SetName("promise") + b.promise = promise + promise.Type = b.Prog.Pointer(promiseTy) + needAlloc := b.CoAlloc(id).SetName("need.dyn.alloc") + b.If(needAlloc, allocBlk, beginBlk) + + b.SetBlock(allocBlk) + frame := b.OffsetPtr(promise, promiseSize) + 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.SetBlock(trapBlk) + b.LLVMTrap() + b.Unreachable() + + b.onSuspBlk = func(nextBlk BasicBlock) (BasicBlock, BasicBlock, BasicBlock) { + if nextBlk == nil { + nextBlk = trapBlk + } + return suspdBlk, nextBlk, cleanBlk + } + b.onReturn = func() { + b.CoSuspend(b.asyncToken, b.Prog.BoolVal(true), trapBlk) + } +} + +// ----------------------------------------------------------------------------- + +// declare void @llvm.coro.destroy(ptr ) +func (b Builder) CoDestroy(hdl Expr) { + fn := b.Pkg.cFunc("llvm.coro.destroy", b.Prog.tyCoDestroy()) + b.Call(fn, hdl) +} + +// declare void @llvm.coro.resume(ptr ) +func (b Builder) CoResume(hdl Expr) { + fn := b.Pkg.cFunc("llvm.coro.resume", b.Prog.tyCoResume()) + b.Call(fn, hdl) +} + +// declare i1 @llvm.coro.done(ptr ) +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()) + return b.Call(fn, ptr, align, from) +} + +// declare i32 @llvm.coro.size.i32() +func (b Builder) CoSizeI32() Expr { + fn := b.Pkg.cFunc("llvm.coro.size.i32", b.Prog.tyCoSizeI32()) + return b.Call(fn) +} + +// declare i64 @llvm.coro.size.i64() +func (b Builder) CoSizeI64() Expr { + fn := b.Pkg.cFunc("llvm.coro.size.i64", b.Prog.tyCoSizeI64()) + return b.Call(fn) +} + +// declare i32 @llvm.coro.align.i32() +func (b Builder) CoAlignI32() Expr { + fn := b.Pkg.cFunc("llvm.coro.align.i32", b.Prog.tyCoAlignI32()) + return b.Call(fn) +} + +// declare i64 @llvm.coro.align.i64() +func (b Builder) CoAlignI64() Expr { + fn := b.Pkg.cFunc("llvm.coro.align.i64", b.Prog.tyCoAlignI64()) + return b.Call(fn) +} + +// declare ptr @llvm.coro.begin(token , ptr ) +func (b Builder) CoBegin(id, mem Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.begin", b.Prog.tyCoBegin()) + return b.Call(fn, id, mem) +} + +// declare ptr @llvm.coro.free(token %id, ptr ) +func (b Builder) CoFree(id, frame Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.free", b.Prog.tyCoFree()) + return b.Call(fn, id, frame) +} + +// declare i1 @llvm.coro.alloc(token ) +func (b Builder) CoAlloc(id Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.alloc", b.Prog.tyCoAlloc()) + return b.Call(fn, id) +} + +// declare ptr @llvm.coro.noop() +func (b Builder) CoNoop() Expr { + fn := b.Pkg.cFunc("llvm.coro.noop", b.Prog.tyCoNoop()) + return b.Call(fn) +} + +// declare ptr @llvm.coro.frame() +func (b Builder) CoFrame() Expr { + fn := b.Pkg.cFunc("llvm.coro.frame", b.Prog.tyCoFrame()) + return b.Call(fn) +} + +// 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) +} + +// declare token @llvm.coro.id.retcon(i32 , i32 , ptr , ptr , ptr , ptr ) +func (b Builder) CoIDRetcon(size, align, buffer, contProto, alloc, dealloc Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.id.retcon", b.Prog.tyCoIDRetcon()) + return b.Call(fn, size, align, buffer, contProto, alloc, dealloc) +} + +// declare token @llvm.coro.id.retcon.once(i32 , i32 , ptr , ptr , ptr , ptr ) +func (b Builder) CoIDRetconOnce(size, align, buffer, prototype, alloc, dealloc Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.id.retcon.once", b.Prog.tyCoIDRetconOnce()) + return b.Call(fn, size, align, buffer, prototype, alloc, dealloc) +} +*/ + +// declare i1 @llvm.coro.end(ptr , i1 , token ) +func (b Builder) CoEnd(hdl Expr, unwind Expr, resultToken Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.end", b.Prog.tyCoEnd()) + return b.Call(fn, hdl, unwind, resultToken) +} + +/* +// declare token @llvm.coro.end.results(...) +func (b Builder) CoEndResults(args []Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.end.results", b.Prog.tyCoEndResults()) + return b.Call(fn, args...) +} + +// 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()) + 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 { + fn := b.Pkg.cFunc("llvm.coro.suspend", b.Prog.tyCoSuspend()) + return b.Call(fn, save, final) +} + +func (b Builder) CoSuspend(save, final Expr, nextBlk BasicBlock) { + if !b.async { + panic(fmt.Errorf("suspend %v not in async block", b.Func.Name())) + } + if nextBlk == nil { + b.Func.MakeBlock("") + nextBlk = b.Func.Block(b.blk.idx + 1) + } + ret := b.coSuspend(save, final) + 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(setValueFn Function, value Expr) { + if !b.async { + panic(fmt.Errorf("return %v not in async block", b.Func.Name())) + } + b.Call(setValueFn.Expr, b.promise, value) + _, _, cleanBlk := b.onSuspBlk(nil) + b.Jump(cleanBlk) +} + +func (b Builder) CoYield(setValueFn Function, value Expr, final 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(), final, nil) +} + +/* +// declare token @llvm.coro.save(ptr ) +func (b Builder) CoSave(hdl Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.save", b.Prog.tyCoSave()) + return b.Call(fn, hdl) +} + +// declare ptr @llvm.coro.prepare.async(ptr ) +func (b Builder) CoPrepareAsync(f Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.prepare.async", b.Prog.tyCoPrepareAsync()) + return b.Call(fn, f) +} + +// declare i1 @llvm.coro.suspend.retcon(...) +func (b Builder) CoSuspendRetcon(args []Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.suspend.retcon", b.Prog.tyCoSuspendRetcon()) + return b.Call(fn, args...) +} + +// declare void @llvm.coro.await.suspend.void(ptr , ptr , ptr ) +func (b Builder) CoAwaitSuspendVoid(awaiter, handle, f Expr) { + fn := b.Pkg.cFunc("llvm.coro.await.suspend.void", b.Prog.tyCoAwaitSuspendVoid()) + b.Call(fn, awaiter, handle, f) +} + +// declare i1 @llvm.coro.await.suspend.bool(ptr , ptr , ptr ) +func (b Builder) CoAwaitSuspendBool(awaiter, handle, f Expr) Expr { + fn := b.Pkg.cFunc("llvm.coro.await.suspend.bool", b.Prog.tyCoAwaitSuspendBool()) + return b.Call(fn, awaiter, handle, f) +} + +// declare void @llvm.coro.await.suspend.handle(ptr , ptr , ptr ) +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) +} +*/ diff --git a/ssa/decl.go b/ssa/decl.go index 89128409..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. @@ -293,13 +296,15 @@ func (p Function) MakeBlocks(nblk int) []BasicBlock { p.blks = make([]BasicBlock, 0, nblk) } for i := 0; i < nblk; i++ { - p.addBlock(n + i) + p.addBlock(n+i, "") } return p.blks[n:] } -func (p Function) addBlock(idx int) BasicBlock { - label := "_llgo_" + strconv.Itoa(idx) +func (p Function) addBlock(idx int, label string) BasicBlock { + if label == "" { + label = "_llgo_" + strconv.Itoa(idx) + } blk := llvm.AddBasicBlock(p.impl, label) ret := &aBasicBlock{blk, blk, p, idx} p.blks = append(p.blks, ret) @@ -307,8 +312,8 @@ func (p Function) addBlock(idx int) BasicBlock { } // MakeBlock creates a new basic block for the function. -func (p Function) MakeBlock() BasicBlock { - return p.addBlock(len(p.blks)) +func (p Function) MakeBlock(label string) BasicBlock { + return p.addBlock(len(p.blks), label) } // Block returns the ith basic block of the function. diff --git a/ssa/eh.go b/ssa/eh.go index 7b097005..650a1070 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -55,6 +55,13 @@ func (p Program) tySiglongjmp() *types.Signature { return p.sigljmpTy } +func (p Program) tyLLVMTrap() *types.Signature { + if p.llvmTrapTy == nil { + p.llvmTrapTy = types.NewSignatureType(nil, nil, nil, nil, nil, false) + } + return p.llvmTrapTy +} + func (b Builder) AllocaSigjmpBuf() Expr { prog := b.Prog n := unsafe.Sizeof(sigjmpbuf{}) @@ -77,6 +84,11 @@ func (b Builder) Siglongjmp(jb, retval Expr) { // b.Unreachable() } +func (b Builder) LLVMTrap() { + fn := b.Pkg.cFunc("llvm.trap", b.Prog.tyLLVMTrap()) + b.Call(fn) +} + // ----------------------------------------------------------------------------- const ( @@ -166,7 +178,7 @@ func (b Builder) getDefer(kind DoAction) *aDefer { czero := prog.IntVal(0, prog.CInt()) retval := b.Sigsetjmp(jb, czero) if kind != DeferAlways { - panicBlk = self.MakeBlock() + panicBlk = self.MakeBlock("") } else { blks = self.MakeBlocks(2) next, panicBlk = blks[0], blks[1] @@ -240,7 +252,7 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) { // RunDefers emits instructions to run deferred instructions. func (b Builder) RunDefers() { self := b.getDefer(DeferInCond) - blk := b.Func.MakeBlock() + blk := b.Func.MakeBlock("") self.rundsNext = append(self.rundsNext, blk) b.Store(self.rundPtr, blk.Addr()) diff --git a/ssa/expr.go b/ssa/expr.go index 391f66e3..7bbb8293 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -59,6 +59,11 @@ func (v Expr) SetOrdering(ordering AtomicOrdering) Expr { return v } +func (v Expr) SetName(name string) Expr { + v.impl.SetName(name) + return v +} + // ----------------------------------------------------------------------------- type builtinTy struct { 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 d394eb87..35306624 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -19,8 +19,10 @@ package ssa import ( "go/token" "go/types" + "regexp" "runtime" "strconv" + "strings" "unsafe" "github.com/goplus/llgo/ssa/abi" @@ -136,6 +138,8 @@ type aProgram struct { rtMapTy llvm.Type rtChanTy llvm.Type + tokenType llvm.Type + anyTy Type voidTy Type voidPtr Type @@ -166,6 +170,8 @@ type aProgram struct { deferTy Type deferPtr Type + tokenTy Type + pyImpTy *types.Signature pyNewList *types.Signature pyListSetI *types.Signature @@ -189,6 +195,41 @@ type aProgram struct { sigsetjmpTy *types.Signature sigljmpTy *types.Signature + llvmTrapTy *types.Signature + + // coroutine manipulation intrinsics (ordered by LLVM coroutine doc) + coDestroyTy *types.Signature + coResumeTy *types.Signature + coDoneTy *types.Signature + coPromiseTy *types.Signature + + // coroutine structure intrinsics (ordered by LLVM coroutine doc) + coSizeI32Ty *types.Signature + coSizeI64Ty *types.Signature + coAlignI32Ty *types.Signature + coAlignI64Ty *types.Signature + coBeginTy *types.Signature + coFreeTy *types.Signature + coAllocTy *types.Signature + coNoopTy *types.Signature + coFrameTy *types.Signature + coIDTy *types.Signature + // coIDAsyncTy *types.Signature + // coIDRetconTy *types.Signature + // coIDRetconOnceTy *types.Signature + coEndTy *types.Signature + // coEndResultsTy *types.Signature + // coEndAsyncTy *types.Signature + coSuspendTy *types.Signature + // coSaveTy *types.Signature + // coSuspendAsyncTy *types.Signature + // coPrepareAsyncTy *types.Signature + // coSuspendRetconTy *types.Signature + // coAwaitSuspendFunctionTy *types.Signature + // coAwaitSuspendVoidTy *types.Signature + // coAwaitSuspendBoolTy *types.Signature + // coAwaitSuspendHandleTy *types.Signature + paramObjPtr_ *types.Var ptrSize int @@ -441,6 +482,18 @@ func (p Program) Any() Type { return p.anyTy } +func (p Program) Token() Type { + if p.tokenTy == nil { + p.tokenTy = &aType{p.tyToken(), rawType{types.Typ[types.Invalid]}, vkInvalid} + } + 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. @@ -649,9 +702,41 @@ func (p Package) Path() string { return p.abi.Pkg } +// Find presplitcoroutine attribute and replace attribute tag with it +// e.g. attributes #0 = { noinline nounwind readnone "presplitcoroutine" } +// replace #0 with presplitcoroutine +// and also remove all other attributes +func removeLLVMAttributes(ll string) string { + attrRe := regexp.MustCompile(`^attributes (#\d+) = {[^}]*}$`) + attrRe2 := regexp.MustCompile(`(\) #\d+ {|\) #\d+)$`) + lines := strings.Split(ll, "\n") + newLines := make([]string, 0, len(lines)) + presplitcoroutine := "" + for _, line := range lines { + if m := attrRe.FindStringSubmatch(line); m != nil { + if strings.Contains(line, "\"presplitcoroutine\"") { + presplitcoroutine = " " + m[1] + " " + } + } else { + newLines = append(newLines, line) + } + } + + for i, line := range newLines { + if presplitcoroutine != "" { + line = strings.Replace(line, presplitcoroutine, " presplitcoroutine ", 1) + } + line = attrRe2.ReplaceAllString(line, ")") + newLines[i] = line + } + + return strings.Join(newLines, "\n") +} + // String returns a string representation of the package. func (p Package) String() string { - return p.mod.String() + // TODO(lijie): workaround for compiling errors of LLVM attributes + return removeLLVMAttributes(p.mod.String()) } // SetPatch sets a patch function. diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 4946869d..e6ae0eab 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -469,6 +469,47 @@ _llgo_0: `) } +func TestSwitch(t *testing.T) { + prog := NewProgram(nil) + pkg := prog.NewPackage("bar", "foo/bar") + params := types.NewTuple(types.NewVar(0, nil, "a", types.Typ[types.Int])) + rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])) + sig := types.NewSignatureType(nil, nil, nil, params, rets, false) + fn := pkg.NewFunc("fn", sig, InGo) + b := fn.MakeBody(4) + cond := fn.Param(0) + case1 := fn.Block(1) + case2 := fn.Block(2) + defb := fn.Block(3) + swc := b.Switch(cond, defb) + swc.Case(prog.Val(1), case1) + swc.Case(prog.Val(2), case2) + swc.End(b) + b.SetBlock(case1).Return(prog.Val(3)) + b.SetBlock(case2).Return(prog.Val(4)) + b.SetBlock(defb).Return(prog.Val(5)) + assertPkg(t, pkg, `; ModuleID = 'foo/bar' +source_filename = "foo/bar" + +define i64 @fn(i64 %0) { +_llgo_0: + switch i64 %0, label %_llgo_3 [ + i64 1, label %_llgo_1 + i64 2, label %_llgo_2 + ] + +_llgo_1: ; preds = %_llgo_0 + ret i64 3 + +_llgo_2: ; preds = %_llgo_0 + ret i64 4 + +_llgo_3: ; preds = %_llgo_0 + ret i64 5 +} +`) +} + func TestUnOp(t *testing.T) { prog := NewProgram(nil) pkg := prog.NewPackage("bar", "foo/bar") @@ -552,3 +593,194 @@ _llgo_0: } `) } + +func TestPointerOffset(t *testing.T) { + prog := NewProgram(nil) + pkg := prog.NewPackage("bar", "foo/bar") + params := types.NewTuple( + types.NewVar(0, nil, "p", types.NewPointer(types.Typ[types.Int])), + types.NewVar(0, nil, "offset", types.Typ[types.Int]), + ) + rets := types.NewTuple(types.NewVar(0, nil, "", types.NewPointer(types.Typ[types.Int]))) + sig := types.NewSignatureType(nil, nil, nil, params, rets, false) + fn := pkg.NewFunc("fn", sig, InGo) + b := fn.MakeBody(1) + ptr := fn.Param(0) + offset := fn.Param(1) + result := b.OffsetPtr(ptr, offset) + b.Return(result) + assertPkg(t, pkg, `; ModuleID = 'foo/bar' +source_filename = "foo/bar" + +define ptr @fn(ptr %0, i64 %1) { +_llgo_0: + %2 = getelementptr ptr, ptr %0, i64 %1 + ret ptr %2 +} +`) +} + +func TestLLVMTrap(t *testing.T) { + prog := NewProgram(nil) + pkg := prog.NewPackage("bar", "foo/bar") + b := pkg.NewFunc("fn", NoArgsNoRet, InGo).MakeBody(1) + b.LLVMTrap() + b.Unreachable() + assertPkg(t, pkg, `; ModuleID = 'foo/bar' +source_filename = "foo/bar" + +define void @fn() { +_llgo_0: + call void @llvm.trap() + unreachable +} + +; Function Attrs: cold noreturn nounwind memory(inaccessiblemem: write) +declare void @llvm.trap() + +`) +} + +func TestCoroFuncs(t *testing.T) { + prog := NewProgram(nil) + pkg := prog.NewPackage("bar", "foo/bar") + fn := pkg.NewFunc("fn", NoArgsNoRet, InGo) + entryBlk := fn.MakeBlock("entry") + suspdBlk := fn.MakeBlock("suspend") + cleanBlk := fn.MakeBlock("clean") + b := fn.NewBuilder() + b.async = true + + b.SetBlock(entryBlk) + align := b.Const(constant.MakeInt64(0), prog.Int32()) + align8 := b.Const(constant.MakeInt64(8), prog.Int32()) + null := b.Const(nil, b.Prog.CIntPtr()) + b.promise = null + id := b.CoID(align, null, null, null) + bf := b.Const(constant.MakeBool(false), prog.Bool()) + b.CoSizeI32() + size := b.CoSizeI64() + b.CoAlignI32() + b.CoAlignI64() + b.CoAlloc(id) + frame := b.Alloca(size) + hdl := b.CoBegin(id, frame) + + b.SetBlock(cleanBlk) + b.CoFree(id, frame) + b.Jump(suspdBlk) + + b.SetBlock(suspdBlk) + b.CoEnd(hdl, bf, prog.TokenNone()) + // b.Return(b.promise) + + b.SetBlock(entryBlk) + + b.CoResume(hdl) + b.CoDone(hdl) + b.CoDestroy(hdl) + + b.CoPromise(null, align8, bf) + b.CoNoop() + b.CoFrame() + setArgs := types.NewTuple(types.NewVar(0, nil, "value", types.Typ[types.Int])) + setSig := types.NewSignatureType(nil, nil, nil, setArgs, nil, false) + setFn := pkg.NewFunc("setValue", setSig, InGo) + one := b.Const(constant.MakeInt64(1), prog.Int()) + + b.onSuspBlk = func(next BasicBlock) (BasicBlock, BasicBlock, BasicBlock) { + return suspdBlk, next, cleanBlk + } + b.CoYield(setFn, one, bf) + b.CoReturn(setFn, one) + + assertPkg(t, pkg, `; ModuleID = 'foo/bar' +source_filename = "foo/bar" + +define void @fn() { +entry: + %0 = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %1 = call i32 @llvm.coro.size.i32() + %2 = call i64 @llvm.coro.size.i64() + %3 = call i32 @llvm.coro.align.i32() + %4 = call i64 @llvm.coro.align.i64() + %5 = call i1 @llvm.coro.alloc(token %0) + %6 = alloca i8, i64 %2, align 1 + %7 = call ptr @llvm.coro.begin(token %0, ptr %6) + call void @llvm.coro.resume(ptr %7) + %8 = call i1 @llvm.coro.done(ptr %7) + %9 = zext i1 %8 to i64 + %10 = trunc i64 %9 to i8 + call void @llvm.coro.destroy(ptr %7) + %11 = call ptr @llvm.coro.promise(ptr null, i32 8, i1 false) + %12 = call ptr @llvm.coro.noop() + %13 = call ptr @llvm.coro.frame() + call void @setValue(ptr null, i64 1) + %14 = call i8 @llvm.coro.suspend(, i1 false) + switch i8 %14, label %suspend [ + i8 0, label %suspend + i8 1, label %clean + ] + +suspend: ; preds = %entry, %entry, %clean + %15 = call i1 @llvm.coro.end(ptr %7, i1 false, token none) + call void @setValue(ptr null, i64 1) + br label %clean + +clean: ; preds = %suspend, %entry + %16 = call ptr @llvm.coro.free(token %0, ptr %6) + br label %suspend + +_llgo_3: ; No predecessors! +} + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) + +; Function Attrs: nounwind memory(none) +declare i32 @llvm.coro.size.i32() + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.size.i64() + +; Function Attrs: nounwind memory(none) +declare i32 @llvm.coro.align.i32() + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.align.i64() + +; Function Attrs: nounwind +declare i1 @llvm.coro.alloc(token) + +; Function Attrs: nounwind +declare ptr @llvm.coro.begin(token, ptr writeonly) + +; Function Attrs: nounwind memory(argmem: read) +declare ptr @llvm.coro.free(token, ptr nocapture readonly) + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(ptr, i1, token) + +declare void @llvm.coro.resume(ptr) + +; Function Attrs: nounwind memory(argmem: readwrite) +declare i1 @llvm.coro.done(ptr nocapture readonly) + +declare void @llvm.coro.destroy(ptr) + +; Function Attrs: nounwind memory(none) +declare ptr @llvm.coro.promise(ptr nocapture, i32, i1) + +; Function Attrs: nounwind memory(none) +declare ptr @llvm.coro.noop() + +; Function Attrs: nounwind memory(none) +declare ptr @llvm.coro.frame() + +declare void @setValue(i64) + +; Function Attrs: nounwind +declare i8 @llvm.coro.suspend(token, i1) + +`) +} diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index f837c3fd..ed228b17 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -63,6 +63,13 @@ 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) + onReturn func() + blkOffset int } // Builder represents a builder for creating instructions in a function. @@ -94,7 +101,7 @@ func (b Builder) setBlockMoveLast(blk BasicBlock) (next BasicBlock) { impl := b.impl - next = b.Func.MakeBlock() + next = b.Func.MakeBlock("") impl.SetInsertPointAtEnd(next.last) impl.Insert(last) @@ -288,7 +295,7 @@ func (b Builder) Times(n Expr, loop func(i Expr)) { } // ----------------------------------------------------------------------------- -/* + type caseStmt struct { v llvm.Value blk llvm.BasicBlock @@ -326,7 +333,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/ssa/type.go b/ssa/type.go index 5e2dcf88..9ab79f18 100644 --- a/ssa/type.go +++ b/ssa/type.go @@ -58,6 +58,7 @@ const ( vkIface vkStruct vkChan + vkToken ) // ----------------------------------------------------------------------------- @@ -282,6 +283,13 @@ func (p Program) tyInt() llvm.Type { return p.intType } +func (p Program) tyToken() llvm.Type { + if p.tokenType.IsNil() { + p.tokenType = p.ctx.TokenType() + } + return p.tokenType +} + func llvmIntType(ctx llvm.Context, size int) llvm.Type { if size <= 4 { return ctx.Int32Type() @@ -362,6 +370,9 @@ func (p Program) toType(raw types.Type) Type { return &aType{p.rtString(), typ, vkString} case types.UnsafePointer: return &aType{p.tyVoidPtr(), typ, vkPtr} + // TODO(lijie): temporary solution, should be replaced by a proper type + case types.Invalid: + return &aType{p.tyToken(), typ, vkInvalid} } case *types.Pointer: elem := p.rawType(t.Elem()) @@ -444,7 +455,8 @@ func (p Program) toLLVMTypes(t *types.Tuple, n int) (ret []llvm.Type) { if n > 0 { ret = make([]llvm.Type, n) for i := 0; i < n; i++ { - ret[i] = p.rawType(t.At(i).Type()).ll + pt := t.At(i) + ret[i] = p.rawType(pt.Type()).ll } } return diff --git a/x/io/README.md b/x/async/README.md similarity index 100% rename from x/io/README.md rename to x/async/README.md 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..e835faf1 --- /dev/null +++ b/x/async/_demo/gendemo/gendemo.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llgo/x/async" +) + +func GenInts() (co *async.Promise[int]) { + println("gen: 1") + co.Yield(1) + println("gen: 2") + co.Yield(2) + println("gen: 3") + co.Yield(3) + return +} + +func main() { + co := GenInts() + for !co.Done() { + fmt.Printf("got: %v\n", co.Value()) + co.Next() + } + fmt.Printf("done\n") +} diff --git a/x/async/async.go b/x/async/async.go new file mode 100644 index 00000000..d572e3e0 --- /dev/null +++ b/x/async/async.go @@ -0,0 +1,77 @@ +/* + * 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" +) + +const ( + LLGoPackage = "decl" +) + +type Void = [0]byte + +// ----------------------------------------------------------------------------- + +type Promise[TOut any] struct { + hdl unsafe.Pointer + value TOut +} + +// // llgo:link (*Promise).Await llgo.coAwait +func (p *Promise[TOut]) Await() TOut { + panic("should not executed") +} + +func (p *Promise[TOut]) Return(v TOut) { + p.value = v + coReturn(p.hdl) +} + +// llgo:link (*Promise).Yield llgo.coYield +func (p *Promise[TOut]) Yield(v TOut) {} + +// llgo:link (*Promise).Suspend llgo.coSuspend +func (p *Promise[TOut]) Suspend() {} + +func (p *Promise[TOut]) Resume() { + coResume(p.hdl) +} + +func (p *Promise[TOut]) Next() { + coResume(p.hdl) +} + +// 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 +} + +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/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go deleted file mode 100644 index 02b4475a..00000000 --- a/x/io/_demo/asyncdemo/async.go +++ /dev/null @@ -1,290 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "time" - - "github.com/goplus/llgo/x/io" -) - -// ----------------------------------------------------------------------------- - -type Response struct { - StatusCode int - - mockBody string -} - -func (r *Response) mock(body string) { - r.mockBody = body -} - -func (r *Response) Text() (resolve io.Promise[string]) { - resolve(r.mockBody, nil) - return -} - -func (r *Response) TextCompiled() *io.PromiseImpl[string] { - P := &io.PromiseImpl[string]{} - P.Func = func(resolve func(string, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - resolve(r.mockBody, nil) - P.Next = -1 - return - default: - panic("Promise already done") - } - } - } - return P -} - -func HttpGet(url string, callback func(resp *Response, err error)) { - resp := &Response{StatusCode: 200} - callback(resp, nil) -} - -func AsyncHttpGet(url string) (resolve io.Promise[*Response]) { - HttpGet(url, resolve) - return -} - -func AsyncHttpGetCompiled(url string) *io.PromiseImpl[*Response] { - P := &io.PromiseImpl[*Response]{} - P.Func = func(resolve func(*Response, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - HttpGet(url, resolve) - P.Next = -1 - return - default: - panic("Promise already done") - } - } - } - return P -} - -// ----------------------------------------------------------------------------- - -type User struct { - Name string -} - -func GetUser(uid string) (resolve io.Promise[User]) { - resp, err := AsyncHttpGet("http://example.com/user/" + uid).Await() - if err != nil { - resolve(User{}, err) - return - } - - if resp.StatusCode != 200 { - resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - resp.mock(`{"name":"Alice"}`) - - body, err := resp.Text().Await() - if err != nil { - resolve(User{}, err) - return - } - user := User{} - if err := json.Unmarshal([]byte(body), &user); err != nil { - resolve(User{}, err) - return - } - - resolve(user, nil) - return -} - -func GetUserCompiled(uid string) *io.PromiseImpl[User] { - var state1 *io.PromiseImpl[*Response] - var state2 *io.PromiseImpl[string] - - P := &io.PromiseImpl[User]{} - P.Func = func(resolve func(User, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - state1 = AsyncHttpGetCompiled("http://example.com/user/" + uid) - P.Next = 1 - return - case 1: - state1.EnsureDone() - resp, err := state1.Value, state1.Err - if err != nil { - resolve(User{}, err) - return - } - - if resp.StatusCode != 200 { - resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - resp.mock(`{"name":"Alice"}`) - - state2 = resp.TextCompiled() - P.Next = 2 - return - case 2: - state2.EnsureDone() - body, err := state2.Value, state2.Err - if err != nil { - resolve(User{}, err) - return - } - user := User{} - if err := json.Unmarshal([]byte(body), &user); err != nil { - resolve(User{}, err) - return - } - - resolve(user, nil) - P.Next = -1 - return - default: - panic("Promise already done") - } - } - } - return P -} - -func GetScore() *io.Promise[float64] { - panic("todo: GetScore") -} - -func GetScoreCompiled() *io.PromiseImpl[float64] { - P := &io.PromiseImpl[float64]{} - P.Func = func(resolve func(float64, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - panic("todo: GetScore") - default: - panic("Promise already done") - } - } - } - return P -} - -func DoUpdate(op string) *io.Promise[io.Void] { - panic("todo: DoUpdate") -} - -func DoUpdateCompiled(op string) *io.PromiseImpl[io.Void] { - P := &io.PromiseImpl[io.Void]{} - P.Func = func(resolve func(io.Void, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - panic("todo: DoUpdate") - default: - panic("Promise already done") - } - } - } - return P -} - -func Demo() (resolve io.Promise[io.Void]) { - user, err := GetUser("123").Await() - log.Println(user, err) - - user, err = io.Race[User](GetUser("123"), GetUser("456"), GetUser("789")).Await() - log.Println(user, err) - - users, err := io.All[User]([]io.AsyncCall[User]{GetUser("123"), GetUser("456"), GetUser("789")}).Await() - log.Println(users, err) - - user, score, _, err := io.Await3[User, float64, io.Void](GetUser("123"), GetScore(), DoUpdate("update sth.")) - log.Println(user, score, 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 <-io.Timeout(5 * time.Second).Chan(): - log.Println("timeout") - } - return -} - -func DemoCompiled() *io.PromiseImpl[io.Void] { - var state1 *io.PromiseImpl[User] - var state2 *io.PromiseImpl[User] - var state3 *io.PromiseImpl[[]User] - var state4 *io.PromiseImpl[io.Await3Result[User, float64, io.Void]] - - P := &io.PromiseImpl[io.Void]{} - P.Func = func(resolve func(io.Void, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - state1 = GetUserCompiled("123") - P.Next = 1 - return - case 1: - state1.EnsureDone() - user, err := state1.Value, state1.Err - log.Printf("user: %v, err: %v\n", user, err) - - state2 = io.Race[User](GetUserCompiled("123"), GetUserCompiled("456"), GetUserCompiled("789")) - P.Next = 2 - return - case 2: - state2.EnsureDone() - user, err := state2.Value, state2.Err - log.Println(user, err) - - state3 = io.All[User]([]io.AsyncCall[User]{GetUserCompiled("123"), GetUserCompiled("456"), GetUserCompiled("789")}) - P.Next = 3 - return - case 3: - state3.EnsureDone() - users, err := state3.Value, state3.Err - log.Println(users, err) - - state4 = io.Await3Compiled[User, float64, io.Void](GetUserCompiled("123"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) - P.Next = 4 - return - case 4: - state4.EnsureDone() - user, score, _, err := state4.Value.V1, state4.Value.V2, state4.Value.V3, state4.Value.Err - log.Println(user, score, err) - - select { - case user := <-GetUserCompiled("123").Chan(): - log.Println("user:", user) - case score := <-GetScoreCompiled().Chan(): - log.Println("score:", score) - case <-io.TimeoutCompiled(5 * time.Second).Chan(): - log.Println("timeout") - } - P.Next = -1 - return - default: - panic("Promise already done") - } - } - } - return P -} - -func main() { - log.SetFlags(log.Lshortfile | log.LstdFlags) - // io.Run(Demo()) - io.Run(DemoCompiled()) -} diff --git a/x/io/io.go b/x/io/io.go deleted file mode 100644 index f4b4c18b..00000000 --- a/x/io/io.go +++ /dev/null @@ -1,170 +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 io - -import ( - _ "unsafe" - - "time" -) - -const ( - LLGoPackage = "decl" -) - -type Void = [0]byte - -// ----------------------------------------------------------------------------- - -type AsyncCall[OutT any] interface { - Await(timeout ...time.Duration) (ret OutT, err error) - Chan() <-chan OutT - EnsureDone() -} - -// llgo:link AsyncCall.Await llgo.await -func Await[OutT any](call AsyncCall[OutT], timeout ...time.Duration) (ret OutT, err error) { - return -} - -//go:linkname Timeout llgo.timeout -func Timeout(time.Duration) (ret AsyncCall[Void]) - -func TimeoutCompiled(d time.Duration) *PromiseImpl[Void] { - P := &PromiseImpl[Void]{} - P.Func = func(resolve func(Void, error)) { - go func() { - time.Sleep(d) - resolve(Void{}, nil) - }() - } - return P -} - -// llgo:link Race llgo.race -func Race[OutT any](acs ...AsyncCall[OutT]) (ret *PromiseImpl[OutT]) { - return -} - -func All[OutT any](acs []AsyncCall[OutT]) (ret *PromiseImpl[[]OutT]) { - return nil -} - -// llgo:link Await2 llgo.await -func Await2[OutT1, OutT2 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], - timeout ...time.Duration) (ret1 OutT1, ret2 OutT2, err error) { - return -} - -type Await2Result[T1 any, T2 any] struct { - V1 T1 - V2 T2 - Err error -} - -func Await2Compiled[OutT1, OutT2 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], - timeout ...time.Duration) (ret *PromiseImpl[Await2Result[OutT1, OutT2]]) { - return -} - -// llgo:link Await3 llgo.await -func Await3[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], - timeout ...time.Duration) (ret1 OutT1, ret2 OutT2, ret3 OutT3, err error) { - return -} - -type Await3Result[T1 any, T2 any, T3 any] struct { - V1 T1 - V2 T2 - V3 T3 - Err error -} - -func Await3Compiled[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], - timeout ...time.Duration) (ret *PromiseImpl[Await3Result[OutT1, OutT2, OutT3]]) { - return -} - -func Run(ac AsyncCall[Void]) { - p := ac.(*PromiseImpl[Void]) - p.Resume() - <-ac.Chan() -} - -// ----------------------------------------------------------------------------- - -type Promise[OutT any] func(OutT, error) - -// llgo:link Promise.Await llgo.await -func (p Promise[OutT]) Await(timeout ...time.Duration) (ret OutT, err error) { - return -} - -func (p Promise[OutT]) Chan() <-chan OutT { - return nil -} - -func (p Promise[OutT]) EnsureDone() { - -} - -// ----------------------------------------------------------------------------- - -type PromiseImpl[TOut any] struct { - Func func(resolve func(TOut, error)) - Value TOut - Err error - Prev int - Next int - - c chan TOut -} - -func (p *PromiseImpl[TOut]) Resume() { - p.Func(func(v TOut, err error) { - p.Value = v - p.Err = err - }) -} - -func (p *PromiseImpl[TOut]) EnsureDone() { - if p.Next == -1 { - panic("Promise already done") - } -} - -func (p *PromiseImpl[TOut]) Chan() <-chan TOut { - if p.c == nil { - p.c = make(chan TOut, 1) - p.Func(func(v TOut, err error) { - p.Value = v - p.Err = err - p.c <- v - }) - } - return p.c -} - -func (p *PromiseImpl[TOut]) Await(timeout ...time.Duration) (ret TOut, err error) { - panic("should not called") -} - -// ----------------------------------------------------------------------------- diff --git a/x/tuple/tuple.go b/x/tuple/tuple.go new file mode 100644 index 00000000..4ebd3071 --- /dev/null +++ b/x/tuple/tuple.go @@ -0,0 +1,57 @@ +/* + * 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 tuple + +// ----------------------------------------------------------------------------- + +type Tuple1[T any] struct { + V1 T +} + +func (r Tuple1[T]) Values() T { + return r.V1 +} + +type Tuple2[T1 any, T2 any] struct { + V1 T1 + V2 T2 +} + +func (r Tuple2[T1, T2]) Values() (T1, T2) { + return r.V1, r.V2 +} + +type Tuple3[T1 any, T2 any, T3 any] struct { + V1 T1 + V2 T2 + V3 T3 +} + +func (r Tuple3[T1, T2, T3]) Values() (T1, T2, T3) { + return r.V1, r.V2, r.V3 +} + +type Tuple4[T1 any, T2 any, T3 any, T4 any] struct { + V1 T1 + V2 T2 + V3 T3 + V4 T4 +} + +func (r Tuple4[T1, T2, T3, T4]) Values() (T1, T2, T3, T4) { + return r.V1, r.V2, r.V3, r.V4 +}