Compare commits

...

29 Commits

Author SHA1 Message Date
xushiwei
ee0e0a5c51 Merge pull request #666 from cpunion/async-functions
Async functions
2024-08-06 20:38:34 +08:00
Li Jie
7b2747ce0c asyncio: merge promise and coro frame allocation 2024-08-06 14:44:16 +08:00
Li Jie
578bc165c4 test: fix cl and ssa tests 2024-08-05 21:19:19 +08:00
Li Jie
a1d46e905b asyncio: demo & test 2024-08-05 20:36:02 +08:00
Li Jie
9102ca6b1e ssa: workaround for LLVM attribute compilation errors 2024-08-05 20:35:58 +08:00
Li Jie
835d6fee1e cl: test async function compiling 2024-08-05 18:10:57 +08:00
Li Jie
294abb5126 ssa: test Builder.Switch 2024-08-05 18:00:14 +08:00
Li Jie
5fca8ebd4e ssa: test coroutine builders 2024-08-05 17:25:13 +08:00
Li Jie
d3df782fca asyncic: coReturn 2024-08-05 17:25:13 +08:00
Li Jie
b04ac8df30 ssa: comment unused coroutine builders 2024-08-05 16:43:36 +08:00
Li Jie
bb03df7059 ssa: refactor Builder.BeginAsync 2024-08-05 16:42:51 +08:00
Li Jie
98072f3f4b asyncio: fix unreasonable return type of promise.Next() 2024-08-05 15:21:14 +08:00
Li Jie
3bf0780a67 asyncio: trap on unexpected resume 2024-08-05 15:19:42 +08:00
Li Jie
a1fdc05e26 cl: fix CoYield 2024-08-05 12:37:34 +08:00
Li Jie
93bff83e3d ssa: fix @llvm.coro.size.i64 doc 2024-08-05 10:41:39 +08:00
Li Jie
e470bc2069 Merge commit 'd06146ed970f52d564521ff0be7d56839c85e497' into async-functions
* commit 'd06146ed970f52d564521ff0be7d56839c85e497': (152 commits)
  perf(lib/sync): avoid using `defer`
  refactor(c-libuv): Added TODO(uid) comment & adjusted the position of Handle, Stream, Req, Write, Connect
  README: io/ioutil
  library: io/ioutil
  c/openssl: bio, pem, rsa
  refactor(c/libuv): Adapt libuv.Fs struct
  Revert "fix(c/libuv): Add libuv fs struct new func"
  fix(c/libuv): Fix async_fs demo return 255 error & pointer not allocated error
  fix(c/libuv): Add libuv fs struct new func
  doc/c:refine symbol visibility description
  README: math/big
  library: math/big.Int (mini-impl for _cmptest/bigintdemo)
  doc/c:update implicit destructors description
  c/openssl: bignum, rsa
  library: crypto
  library: crypto/{sha1, sha256, sha512}
  doc/c:fix incorrect usage in construtors
  doc/c:update destructor usage
  delete sum
  fix test error
  ...

# Conflicts:
#	ssa/eh.go
2024-08-04 10:26:27 +08:00
Li Jie
efa771f3ff cl: compile async functions 2024-08-04 10:22:22 +08:00
Li Jie
806193fc6e ssa: set expr name for debug readable 2024-08-03 20:57:38 +08:00
Li Jie
cdfa0166bd ssa: make block with label name for debug readable 2024-08-03 20:51:43 +08:00
Li Jie
0687efaec6 ssa: add coroution intrinsics 2024-07-31 21:57:32 +08:00
Li Jie
0d3e78ad94 asyncio: x/io -> x/async 2024-07-29 23:17:21 +08:00
Li Jie
375b2b579e asyncio: refactor 2024-07-29 15:42:00 +08:00
Li Jie
a578155dcb asyncio: multi return types promise/generator 2024-07-29 10:46:14 +08:00
Li Jie
df5f1afb74 asyncio: demo comments 2024-07-27 15:26:02 +08:00
Li Jie
b2a2b2f29d asyncio: generator 2024-07-27 15:08:09 +08:00
Li Jie
91df9957f5 asyncio: parallel and concurrent extra functions 2024-07-26 10:15:41 +08:00
Li Jie
08e0ace9a2 asyncio: improve schedule 2024-07-25 11:04:40 +08:00
Li Jie
a4286dbd4b asyncio: improve performance by scheduling last frame 2024-07-25 11:02:02 +08:00
Li Jie
f2e15a6846 asyncio: poc 2024-07-25 08:50:49 +08:00
25 changed files with 2019 additions and 486 deletions

27
cl/_testdata/async/in.go Normal file
View File

@@ -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
}

201
cl/_testdata/async/out.ll Normal file
View File

@@ -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)

119
cl/async.go Normal file
View File

@@ -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")
}

View File

@@ -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) log.Println("==> NewFunc", name, "type:", sig.Recv(), sig, "ftype:", ftype)
} }
} }
async := isAsyncFunc(f.Signature)
if fn == nil { if fn == nil {
if name == "main" { if name == "main" {
argc := types.NewParam(token.NoPos, pkgTypes, "", types.Typ[types.Int32]) 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) results := types.NewTuple(ret)
sig = types.NewSignatureType(nil, nil, nil, params, results, false) 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 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 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.inits = append(p.inits, func() {
p.fn = fn p.fn = fn
@@ -249,6 +261,10 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
log.Println("==> FuncBody", name) log.Println("==> FuncBody", name)
} }
b := fn.NewBuilder() 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) p.bvals = make(map[ssa.Value]llssa.Expr)
off := make([]int, len(f.Blocks)) off := make([]int, len(f.Blocks))
for i, block := range 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 pkg = p.pkg
var fn = p.fn var fn = p.fn
var instrs = block.Instrs[n:] var instrs = block.Instrs[n:]
var ret = fn.Block(block.Index) var ret = fn.Block(block.Index + b.BlockOffset())
b.SetBlock(ret) b.SetBlock(ret)
if doModInit { if doModInit {
if pyModInit = p.pyMod != ""; pyModInit { 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 modPtr := pkg.PyNewModVar(modName, true).Expr
mod := b.Load(modPtr) mod := b.Load(modPtr)
cond := b.BinOp(token.NEQ, mod, prog.Nil(mod.Type)) cond := b.BinOp(token.NEQ, mod, prog.Nil(mod.Type))
newBlk := fn.MakeBlock() newBlk := fn.MakeBlock("")
b.If(cond, jumpTo, newBlk) b.If(cond, jumpTo, newBlk)
b.SetBlockEx(newBlk, llssa.AtEnd, false) b.SetBlockEx(newBlk, llssa.AtEnd, false)
b.Store(modPtr, b.PyImportMod(modPath)) 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 = make([]llssa.Expr, 1)
results[0] = p.prog.IntVal(0, p.prog.CInt()) results[0] = p.prog.IntVal(0, p.prog.CInt())
} }
b.Return(results...) if b.Async() {
b.EndAsync()
} else {
b.Return(results...)
}
case *ssa.If: case *ssa.If:
fn := p.fn fn := p.fn
cond := p.compileValue(b, v.Cond) cond := p.compileValue(b, v.Cond)

View File

@@ -53,7 +53,7 @@ func TestFromTestrt(t *testing.T) {
} }
func TestFromTestdata(t *testing.T) { func TestFromTestdata(t *testing.T) {
cltest.FromDir(t, "", "./_testdata", false) cltest.FromDir(t, "", "./_testdata", true)
} }
func TestFromTestpymath(t *testing.T) { 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)
`)
}

View File

@@ -412,7 +412,16 @@ const (
llgoAtomicUMax = llgoAtomicOpBase + llssa.OpUMax llgoAtomicUMax = llgoAtomicOpBase + llssa.OpUMax
llgoAtomicUMin = llgoAtomicOpBase + llssa.OpUMin 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) { func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, string, int) {

View File

@@ -237,6 +237,14 @@ var llgoInstrs = map[string]int{
"atomicMin": int(llgoAtomicMin), "atomicMin": int(llgoAtomicMin),
"atomicUMax": int(llgoAtomicUMax), "atomicUMax": int(llgoAtomicUMax),
"atomicUMin": int(llgoAtomicUMin), "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. // 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 return nil, nil, ignoredFunc
} }
sig := fn.Signature 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 return
@@ -390,6 +399,20 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon
ret = p.funcAddr(b, args) ret = p.funcAddr(b, args)
case llgoUnreachable: // func unreachable() case llgoUnreachable: // func unreachable()
b.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: default:
if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast { if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast {
ret = p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args) ret = p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args)

View File

@@ -50,7 +50,7 @@ func TestFromTestrt(t *testing.T) {
} }
func TestFromTestdata(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) { func TestMakeInterface(t *testing.T) {

690
ssa/coro.go Normal file
View File

@@ -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 <handle>)
// declare void @llvm.coro.resume(ptr <handle>)
// declare i1 @llvm.coro.done(ptr <handle>)
// declare ptr @llvm.coro.promise(ptr <ptr>, i32 <alignment>, i1 <from>)
// 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 <id>, ptr <mem>)
// declare ptr @llvm.coro.free(token %id, ptr <frame>)
// declare i1 @llvm.coro.alloc(token <id>)
// declare ptr @llvm.coro.noop()
// declare ptr @llvm.coro.frame()
// declare token @llvm.coro.id(i32 <align>, ptr <promise>, ptr <coroaddr>, ptr <fnaddrs>)
// declare token @llvm.coro.id.async(i32 <context size>, i32 <align>, ptr <context arg>, ptr <async function pointer>)
// declare token @llvm.coro.id.retcon(i32 <size>, i32 <align>, ptr <buffer>, ptr <continuation prototype>, ptr <alloc>, ptr <dealloc>)
// declare token @llvm.coro.id.retcon.once(i32 <size>, i32 <align>, ptr <buffer>, ptr <prototype>, ptr <alloc>, ptr <dealloc>)
// declare i1 @llvm.coro.end(ptr <handle>, i1 <unwind>, token <result.token>)
// declare token @llvm.coro.end.results(...)
// declare i1 @llvm.coro.end.async(ptr <handle>, i1 <unwind>, ...)
// declare i8 @llvm.coro.suspend(token <save>, i1 <final>)
// declare token @llvm.coro.save(ptr <handle>)
// declare {ptr, ptr, ptr} @llvm.coro.suspend.async(ptr <resume function>, ptr <context projection function>, ... <function to call> ... <arguments to function>)
// declare ptr @llvm.coro.prepare.async(ptr <coroutine function>)
// declare i1 @llvm.coro.suspend.retcon(...)
// declare void @await_suspend_function(ptr %awaiter, ptr %hdl)
// declare void @llvm.coro.await.suspend.void(ptr <awaiter>, ptr <handle>, ptr <await_suspend_function>)
// declare i1 @llvm.coro.await.suspend.bool(ptr <awaiter>, ptr <handle>, ptr <await_suspend_function>)
// declare void @llvm.coro.await.suspend.handle(ptr <awaiter>, ptr <handle>, ptr <await_suspend_function>)
// -----------------------------------------------------------------------------
// declare void @llvm.coro.destroy(ptr <handle>)
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 <handle>)
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 <handle>)
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 <ptr>, i32 <alignment>, i1 <from>)
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 <id>, ptr <mem>)
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 <frame>)
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 <id>)
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 <align>, ptr <promise>, ptr <coroaddr>, ptr <fnaddrs>)
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 <context size>, i32 <align>, ptr <context arg>, ptr <async function pointer>)
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 <size>, i32 <align>, ptr <buffer>, ptr <continuation prototype>, ptr <alloc>, ptr <dealloc>)
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 <size>, i32 <align>, ptr <buffer>, ptr <prototype>, ptr <alloc>, ptr <dealloc>)
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 <handle>, i1 <unwind>, token <result.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 <handle>, i1 <unwind>, ...)
func (p Program) tyCoEndAsync() *types.Signature {
panic("not implemented")
}
*/
// declare i8 @llvm.coro.suspend(token <save>, i1 <final>)
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 <handle>)
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 <resume function>, ptr <context projection function>, ... <function to call> ... <arguments to function>)
func (p Program) tyCoSuspendAsync() *types.Signature {
panic("not implemented")
}
// declare ptr @llvm.coro.prepare.async(ptr <coroutine function>)
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 <awaiter>, ptr <handle>, ptr <await_suspend_function>)
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 <awaiter>, ptr <handle>, ptr <await_suspend_function>)
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 <awaiter>, ptr <handle>, ptr <await_suspend_function>)
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 <handle>)
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 <handle>)
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 <handle>)
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 <ptr>, i32 <alignment>, i1 <from>)
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 <id>, ptr <mem>)
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 <frame>)
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 <id>)
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 <align>, ptr <promise>, ptr <coroaddr>, ptr <fnaddrs>)
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 <context size>, i32 <align>, ptr <context arg>, ptr <async function pointer>)
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 <size>, i32 <align>, ptr <buffer>, ptr <continuation prototype>, ptr <alloc>, ptr <dealloc>)
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 <size>, i32 <align>, ptr <buffer>, ptr <prototype>, ptr <alloc>, ptr <dealloc>)
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 <handle>, i1 <unwind>, token <result.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 <handle>, i1 <unwind>, ...)
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 <save>, i1 <final>)
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 <handle>)
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 <coroutine function>)
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 <awaiter>, ptr <handle>, ptr <await_suspend_function>)
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 <awaiter>, ptr <handle>, ptr <await_suspend_function>)
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 <awaiter>, ptr <handle>, ptr <await_suspend_function>)
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)
}
*/

View File

@@ -180,11 +180,11 @@ type Function = *aFunction
// NewFunc creates a new function. // NewFunc creates a new function.
func (p Package) NewFunc(name string, sig *types.Signature, bg Background) 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. // 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 { if v, ok := p.fns[name]; ok {
return v 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) log.Println("NewFunc", name, t.raw.Type, "hasFreeVars:", hasFreeVars)
} }
fn := llvm.AddFunction(p.mod, name, t.ll) 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) ret := newFunction(fn, t, p, p.Prog, hasFreeVars)
p.fns[name] = ret p.fns[name] = ret
return ret return ret
@@ -268,7 +271,7 @@ func (p Function) NewBuilder() Builder {
b := prog.ctx.NewBuilder() b := prog.ctx.NewBuilder()
// TODO(xsw): Finalize may cause panic, so comment it. // TODO(xsw): Finalize may cause panic, so comment it.
// b.Finalize() // 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. // 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) p.blks = make([]BasicBlock, 0, nblk)
} }
for i := 0; i < nblk; i++ { for i := 0; i < nblk; i++ {
p.addBlock(n + i) p.addBlock(n+i, "")
} }
return p.blks[n:] return p.blks[n:]
} }
func (p Function) addBlock(idx int) BasicBlock { func (p Function) addBlock(idx int, label string) BasicBlock {
label := "_llgo_" + strconv.Itoa(idx) if label == "" {
label = "_llgo_" + strconv.Itoa(idx)
}
blk := llvm.AddBasicBlock(p.impl, label) blk := llvm.AddBasicBlock(p.impl, label)
ret := &aBasicBlock{blk, blk, p, idx} ret := &aBasicBlock{blk, blk, p, idx}
p.blks = append(p.blks, ret) 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. // MakeBlock creates a new basic block for the function.
func (p Function) MakeBlock() BasicBlock { func (p Function) MakeBlock(label string) BasicBlock {
return p.addBlock(len(p.blks)) return p.addBlock(len(p.blks), label)
} }
// Block returns the ith basic block of the function. // Block returns the ith basic block of the function.

View File

@@ -55,6 +55,13 @@ func (p Program) tySiglongjmp() *types.Signature {
return p.sigljmpTy 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 { func (b Builder) AllocaSigjmpBuf() Expr {
prog := b.Prog prog := b.Prog
n := unsafe.Sizeof(sigjmpbuf{}) n := unsafe.Sizeof(sigjmpbuf{})
@@ -77,6 +84,11 @@ func (b Builder) Siglongjmp(jb, retval Expr) {
// b.Unreachable() // b.Unreachable()
} }
func (b Builder) LLVMTrap() {
fn := b.Pkg.cFunc("llvm.trap", b.Prog.tyLLVMTrap())
b.Call(fn)
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const ( const (
@@ -166,7 +178,7 @@ func (b Builder) getDefer(kind DoAction) *aDefer {
czero := prog.IntVal(0, prog.CInt()) czero := prog.IntVal(0, prog.CInt())
retval := b.Sigsetjmp(jb, czero) retval := b.Sigsetjmp(jb, czero)
if kind != DeferAlways { if kind != DeferAlways {
panicBlk = self.MakeBlock() panicBlk = self.MakeBlock("")
} else { } else {
blks = self.MakeBlocks(2) blks = self.MakeBlocks(2)
next, panicBlk = blks[0], blks[1] 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. // RunDefers emits instructions to run deferred instructions.
func (b Builder) RunDefers() { func (b Builder) RunDefers() {
self := b.getDefer(DeferInCond) self := b.getDefer(DeferInCond)
blk := b.Func.MakeBlock() blk := b.Func.MakeBlock("")
self.rundsNext = append(self.rundsNext, blk) self.rundsNext = append(self.rundsNext, blk)
b.Store(self.rundPtr, blk.Addr()) b.Store(self.rundPtr, blk.Addr())

View File

@@ -59,6 +59,11 @@ func (v Expr) SetOrdering(ordering AtomicOrdering) Expr {
return v return v
} }
func (v Expr) SetName(name string) Expr {
v.impl.SetName(name)
return v
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
type builtinTy struct { type builtinTy struct {

View File

@@ -232,6 +232,13 @@ func (b Builder) ArrayAlloca(telem Type, n Expr) (ret Expr) {
return 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): /* TODO(xsw):
// ArrayAlloc allocates zero initialized space for an array of n elements of type telem. // ArrayAlloc allocates zero initialized space for an array of n elements of type telem.
func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) { func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) {

View File

@@ -19,8 +19,10 @@ package ssa
import ( import (
"go/token" "go/token"
"go/types" "go/types"
"regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"unsafe" "unsafe"
"github.com/goplus/llgo/ssa/abi" "github.com/goplus/llgo/ssa/abi"
@@ -136,6 +138,8 @@ type aProgram struct {
rtMapTy llvm.Type rtMapTy llvm.Type
rtChanTy llvm.Type rtChanTy llvm.Type
tokenType llvm.Type
anyTy Type anyTy Type
voidTy Type voidTy Type
voidPtr Type voidPtr Type
@@ -166,6 +170,8 @@ type aProgram struct {
deferTy Type deferTy Type
deferPtr Type deferPtr Type
tokenTy Type
pyImpTy *types.Signature pyImpTy *types.Signature
pyNewList *types.Signature pyNewList *types.Signature
pyListSetI *types.Signature pyListSetI *types.Signature
@@ -189,6 +195,41 @@ type aProgram struct {
sigsetjmpTy *types.Signature sigsetjmpTy *types.Signature
sigljmpTy *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 paramObjPtr_ *types.Var
ptrSize int ptrSize int
@@ -441,6 +482,18 @@ func (p Program) Any() Type {
return p.anyTy 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. // Eface returns the empty interface type.
// It is equivalent to Any. // It is equivalent to Any.
@@ -649,9 +702,41 @@ func (p Package) Path() string {
return p.abi.Pkg 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. // String returns a string representation of the package.
func (p Package) String() string { 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. // SetPatch sets a patch function.

View File

@@ -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) { func TestUnOp(t *testing.T) {
prog := NewProgram(nil) prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar") 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(<null operand!>, 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)
`)
}

View File

@@ -63,6 +63,13 @@ type aBuilder struct {
Func Function Func Function
Pkg Package Pkg Package
Prog Program 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. // 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 impl := b.impl
next = b.Func.MakeBlock() next = b.Func.MakeBlock("")
impl.SetInsertPointAtEnd(next.last) impl.SetInsertPointAtEnd(next.last)
impl.Insert(last) impl.Insert(last)
@@ -288,7 +295,7 @@ func (b Builder) Times(n Expr, loop func(i Expr)) {
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
/*
type caseStmt struct { type caseStmt struct {
v llvm.Value v llvm.Value
blk llvm.BasicBlock blk llvm.BasicBlock
@@ -326,7 +333,7 @@ func (b Builder) Switch(v Expr, defb BasicBlock) Switch {
} }
return &aSwitch{v.impl, defb.first, nil} return &aSwitch{v.impl, defb.first, nil}
} }
*/
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Phi represents a phi node. // Phi represents a phi node.

View File

@@ -58,6 +58,7 @@ const (
vkIface vkIface
vkStruct vkStruct
vkChan vkChan
vkToken
) )
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -282,6 +283,13 @@ func (p Program) tyInt() llvm.Type {
return p.intType 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 { func llvmIntType(ctx llvm.Context, size int) llvm.Type {
if size <= 4 { if size <= 4 {
return ctx.Int32Type() return ctx.Int32Type()
@@ -362,6 +370,9 @@ func (p Program) toType(raw types.Type) Type {
return &aType{p.rtString(), typ, vkString} return &aType{p.rtString(), typ, vkString}
case types.UnsafePointer: case types.UnsafePointer:
return &aType{p.tyVoidPtr(), typ, vkPtr} 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: case *types.Pointer:
elem := p.rawType(t.Elem()) elem := p.rawType(t.Elem())
@@ -444,7 +455,8 @@ func (p Program) toLLVMTypes(t *types.Tuple, n int) (ret []llvm.Type) {
if n > 0 { if n > 0 {
ret = make([]llvm.Type, n) ret = make([]llvm.Type, n)
for i := 0; i < n; i++ { 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 return

View File

@@ -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")
}

View File

@@ -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")
}

77
x/async/async.go Normal file
View File

@@ -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")
}

39
x/async/coro.go Normal file
View File

@@ -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")
}

View File

@@ -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())
}

View File

@@ -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")
}
// -----------------------------------------------------------------------------

57
x/tuple/tuple.go Normal file
View File

@@ -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
}