From f2e15a68461b8337c053c426628d68fff71ec9ae Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 25 Jul 2024 08:50:49 +0800 Subject: [PATCH 01/27] asyncio: poc --- x/io/_demo/asyncdemo/async.go | 324 ++++++++++++++++++++++++++-------- x/io/extra.go | 177 +++++++++++++++++++ x/io/io.go | 153 ++++++++-------- 3 files changed, 499 insertions(+), 155 deletions(-) create mode 100644 x/io/extra.go diff --git a/x/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go index 02b4475a..519f74fb 100644 --- a/x/io/_demo/asyncdemo/async.go +++ b/x/io/_demo/asyncdemo/async.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "log" + "strings" "time" "github.com/goplus/llgo/x/io" @@ -28,12 +29,13 @@ func (r *Response) Text() (resolve io.Promise[string]) { func (r *Response) TextCompiled() *io.PromiseImpl[string] { P := &io.PromiseImpl[string]{} + P.Debug = "Text" P.Func = func(resolve func(string, error)) { for { switch P.Prev = P.Next; P.Prev { case 0: - resolve(r.mockBody, nil) P.Next = -1 + resolve(r.mockBody, nil) return default: panic("Promise already done") @@ -43,24 +45,58 @@ func (r *Response) TextCompiled() *io.PromiseImpl[string] { return P } -func HttpGet(url string, callback func(resp *Response, err error)) { - resp := &Response{StatusCode: 200} - callback(resp, nil) +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, mockBody: body} + callback(resp, nil) + }() } func AsyncHttpGet(url string) (resolve io.Promise[*Response]) { - HttpGet(url, resolve) + Http("GET", url, resolve) return } func AsyncHttpGetCompiled(url string) *io.PromiseImpl[*Response] { P := &io.PromiseImpl[*Response]{} + P.Debug = "HttpGet" P.Func = func(resolve func(*Response, error)) { for { switch P.Prev = P.Next; P.Prev { case 0: - HttpGet(url, resolve) P.Next = -1 + Http("GET", url, resolve) + return + default: + panic("Promise already done") + } + } + } + return P +} + +func AsyncHttpPost(url string) (resolve io.Promise[*Response]) { + Http("POST", url, resolve) + return +} + +func AsyncHttpPostCompiled(url string) *io.PromiseImpl[*Response] { + P := &io.PromiseImpl[*Response]{} + P.Debug = "HttpPost" + P.Func = func(resolve func(*Response, error)) { + for { + switch P.Prev = P.Next; P.Prev { + case 0: + P.Next = -1 + Http("POST", url, resolve) return default: panic("Promise already done") @@ -76,8 +112,8 @@ type User struct { Name string } -func GetUser(uid string) (resolve io.Promise[User]) { - resp, err := AsyncHttpGet("http://example.com/user/" + uid).Await() +func GetUser(name string) (resolve io.Promise[User]) { + resp, err := AsyncHttpGet("http://example.com/user/" + name).Await() if err != nil { resolve(User{}, err) return @@ -88,8 +124,6 @@ func GetUser(uid string) (resolve io.Promise[User]) { return } - resp.mock(`{"name":"Alice"}`) - body, err := resp.Text().Await() if err != nil { resolve(User{}, err) @@ -105,21 +139,29 @@ func GetUser(uid string) (resolve io.Promise[User]) { return } -func GetUserCompiled(uid string) *io.PromiseImpl[User] { +func GetUserCompiled(name string) *io.PromiseImpl[User] { var state1 *io.PromiseImpl[*Response] var state2 *io.PromiseImpl[string] P := &io.PromiseImpl[User]{} + P.Debug = "GetUser" 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 + state1 = AsyncHttpGetCompiled("http://example.com/user/" + name) + state1.Exec = P.Exec + state1.Call() return case 1: - state1.EnsureDone() + if !state1.Done() { + state1.Call() + return + } + P.Next = 2 resp, err := state1.Value, state1.Err + log.Printf("resp: %v, err: %v\n", resp, err) if err != nil { resolve(User{}, err) return @@ -130,26 +172,121 @@ func GetUserCompiled(uid string) *io.PromiseImpl[User] { return } - resp.mock(`{"name":"Alice"}`) - state2 = resp.TextCompiled() - P.Next = 2 + state2.Exec = P.Exec + state2.Call() + log.Printf("TextCompiled state2: %v\n", state2) return case 2: - state2.EnsureDone() + if !state2.Done() { + state2.Call() + return + } + P.Next = -1 body, err := state2.Value, state2.Err if err != nil { resolve(User{}, err) return } user := User{} + log.Printf("body: %v\n", body) if err := json.Unmarshal([]byte(body), &user); err != nil { resolve(User{}, err) return } resolve(user, nil) + return + default: + panic(fmt.Sprintf("Promise already done, %+v", P)) + } + } + } + return P +} + +func GetScore() (resolve io.Promise[float64]) { + resp, err := AsyncHttpGet("http://example.com/score/").Await() + if err != nil { + resolve(0, err) + return + } + + if resp.StatusCode != 200 { + resolve(0, fmt.Errorf("http status code: %d", resp.StatusCode)) + return + } + + body, err := resp.Text().Await() + if err != nil { + resolve(0, err) + return + } + + score := 0.0 + if _, err := fmt.Sscanf(body, "%f", &score); err != nil { + resolve(0, err) + return + } + resolve(score, nil) + return +} + +func GetScoreCompiled() *io.PromiseImpl[float64] { + var state1 *io.PromiseImpl[*Response] + var state2 *io.PromiseImpl[string] + + P := &io.PromiseImpl[float64]{} + P.Debug = "GetScore" + P.Func = func(resolve func(float64, error)) { + for { + switch P.Prev = P.Next; P.Prev { + case 0: + P.Next = 1 + state1 = AsyncHttpGetCompiled("http://example.com/score/") + state1.Exec = P.Exec + state1.Call() + return + case 1: + if !state1.Done() { + state1.Call() + return + } + P.Next = 2 + resp, err := state1.Value, state1.Err + if err != nil { + resolve(0, err) + return + } + + if resp.StatusCode != 200 { + resolve(0, fmt.Errorf("http status code: %d", resp.StatusCode)) + return + } + + state2 = resp.TextCompiled() + state2.Exec = P.Exec + state2.Call() + + return + case 2: + if !state2.Done() { + state2.Call() + return + } P.Next = -1 + body, err := state2.Value, state2.Err + if err != nil { + resolve(0, err) + return + } + + score := 0.0 + if _, err := fmt.Sscanf(body, "%f", &score); err != nil { + resolve(0, err) + return + } + resolve(score, nil) return default: panic("Promise already done") @@ -159,36 +296,55 @@ func GetUserCompiled(uid string) *io.PromiseImpl[User] { 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") - } - } +func DoUpdate(op string) (resolve io.Promise[io.Void]) { + resp, err := AsyncHttpPost("http://example.com/update/" + op).Await() + if err != nil { + resolve(io.Void{}, err) + return } - return P -} -func DoUpdate(op string) *io.Promise[io.Void] { - panic("todo: DoUpdate") + if resp.StatusCode != 200 { + resolve(io.Void{}, fmt.Errorf("http status code: %d", resp.StatusCode)) + return + } + + resolve(io.Void{}, nil) + return } func DoUpdateCompiled(op string) *io.PromiseImpl[io.Void] { + var state1 *io.PromiseImpl[*Response] + P := &io.PromiseImpl[io.Void]{} + P.Debug = "DoUpdate" P.Func = func(resolve func(io.Void, error)) { for { switch P.Prev = P.Next; P.Prev { case 0: - panic("todo: DoUpdate") + P.Next = 1 + state1 = AsyncHttpPostCompiled("http://example.com/update/" + op) + state1.Exec = P.Exec + state1.Call() + return + case 1: + if !state1.Done() { + state1.Call() + return + } + P.Next = -1 + resp, err := state1.Value, state1.Err + if err != nil { + resolve(io.Void{}, err) + return + } + + if resp.StatusCode != 200 { + resolve(io.Void{}, fmt.Errorf("http status code: %d", resp.StatusCode)) + return + } + + resolve(io.Void{}, nil) + return default: panic("Promise already done") } @@ -198,82 +354,103 @@ func DoUpdateCompiled(op string) *io.PromiseImpl[io.Void] { } func Demo() (resolve io.Promise[io.Void]) { - user, err := GetUser("123").Await() + user, err := GetUser("1").Await() log.Println(user, err) - user, err = io.Race[User](GetUser("123"), GetUser("456"), GetUser("789")).Await() + user, err = io.Race[User](GetUser("2"), GetUser("3"), GetUser("4")).Await() log.Println(user, err) - users, err := io.All[User]([]io.AsyncCall[User]{GetUser("123"), GetUser("456"), GetUser("789")}).Await() + users, err := io.All[User]([]io.AsyncCall[User]{GetUser("5"), GetUser("6"), GetUser("7")}).Await() log.Println(users, err) - user, score, _, err := io.Await3[User, float64, io.Void](GetUser("123"), GetScore(), DoUpdate("update sth.")) + user, score, _, err := io.Await3[User, float64, io.Void](GetUser("8"), 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") - } + // 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 state3 *io.PromiseImpl[[]io.Result[User]] var state4 *io.PromiseImpl[io.Await3Result[User, float64, io.Void]] P := &io.PromiseImpl[io.Void]{} + P.Debug = "Demo" P.Func = func(resolve func(io.Void, error)) { for { switch P.Prev = P.Next; P.Prev { case 0: - state1 = GetUserCompiled("123") P.Next = 1 + state1 = GetUserCompiled("1") + state1.Exec = P.Exec + state1.Call() return case 1: - state1.EnsureDone() + if !state1.Done() { + state1.Call() + return + } + P.Next = 2 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 + state2 = io.Race[User](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) + log.Printf("state2: %v\n", state2) + state2.Exec = P.Exec + state2.Call() 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")}) + if !state2.Done() { + state2.Call() + return + } + log.Printf("case 2, state2: %+v\n", state2) + P.Next = 3 + user, err := state2.Value, state2.Err + log.Printf("race user: %v, err: %v\n", user, err) + + state3 = io.All[User]([]io.AsyncCall[User]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) + log.Printf("state3: %v\n", state3) + state3.Exec = P.Exec + state3.Call() return case 3: - state3.EnsureDone() + if !state3.Done() { + state3.Call() + return + } + + P.Next = 4 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 + state4 = io.Await3Compiled[User, float64, io.Void](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) + log.Printf("state4: %v\n", state4) + state4.Exec = P.Exec + state4.Call() return case 4: - state4.EnsureDone() + if !state4.Done() { + state4.Call() + return + } + + P.Next = -1 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 + resolve(io.Void{}, nil) return default: panic("Promise already done") @@ -286,5 +463,6 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { func main() { log.SetFlags(log.Lshortfile | log.LstdFlags) // io.Run(Demo()) - io.Run(DemoCompiled()) + v, err := io.Run[io.Void](DemoCompiled()) + log.Println(v, err) } diff --git a/x/io/extra.go b/x/io/extra.go new file mode 100644 index 00000000..afd0d72c --- /dev/null +++ b/x/io/extra.go @@ -0,0 +1,177 @@ +/* + * 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 ( + "log" + "sync" + "time" + _ "unsafe" +) + +// ----------------------------------------------------------------------------- + +// 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.Debug = "Timeout" + P.Func = func(resolve func(Void, error)) { + go func() { + time.Sleep(d) + resolve(Void{}, nil) + }() + } + return P +} + +type Result[T any] struct { + V T + Err error +} + +// llgo:link Race llgo.race +func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { + P := &PromiseImpl[OutT]{} + P.Debug = "Race" + P.Func = func(resolve func(OutT, error)) { + P.Next = -1 + rc := make(chan Result[OutT], len(acs)) + for _, ac := range acs { + ac := ac + go func(ac AsyncCall[OutT]) { + v, err := Run(ac) + rc <- Result[OutT]{v, err} + }(ac) + } + + v := <-rc + if debugAsync { + log.Printf("io.Race done: %+v won the race\n", v) + } + resolve(v.V, v.Err) + go func() { + count := 1 + for count < len(acs) { + <-rc + count++ + } + close(rc) + }() + } + return P +} + +func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { + P := &PromiseImpl[[]Result[OutT]]{} + P.Debug = "All" + P.Func = func(resolve func([]Result[OutT], error)) { + P.Next = -1 + wg := sync.WaitGroup{} + ret := make([]Result[OutT], len(acs)) + for idx, ac := range acs { + idx := idx + ac := ac + wg.Add(1) + go func(ac AsyncCall[OutT]) { + v, err := Run(ac) + ret[idx] = Result[OutT]{v, err} + wg.Done() + }(ac) + } + + wg.Wait() + if debugAsync { + log.Printf("io.All done: %+v\n", ret) + } + resolve(ret, nil) + } + return P +} + +// 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 +} + +// TODO(lijie): rewrite to unblock and avoid goroutine +func Await3Compiled[OutT1, OutT2, OutT3 any]( + ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], + timeout ...time.Duration) *PromiseImpl[Await3Result[OutT1, OutT2, OutT3]] { + P := &PromiseImpl[Await3Result[OutT1, OutT2, OutT3]]{} + P.Debug = "Await3" + P.Func = func(resolve func(Await3Result[OutT1, OutT2, OutT3], error)) { + P.Next = -1 + + ret := Await3Result[OutT1, OutT2, OutT3]{} + wg := sync.WaitGroup{} + wg.Add(3) + + go func() { + defer wg.Done() + ret.V1, ret.Err = Run(ac1) + }() + go func() { + defer wg.Done() + ret.V2, ret.Err = Run(ac2) + }() + go func() { + defer wg.Done() + ret.V3, ret.Err = Run(ac3) + }() + wg.Wait() + if debugAsync { + log.Printf("Await3 done: %+v\n", ret) + } + resolve(ret, nil) + } + return P +} diff --git a/x/io/io.go b/x/io/io.go index f4b4c18b..f116ad93 100644 --- a/x/io/io.go +++ b/x/io/io.go @@ -17,6 +17,8 @@ package io import ( + "log" + "sync" _ "unsafe" "time" @@ -26,87 +28,62 @@ const ( LLGoPackage = "decl" ) +var debugAsync = false + type Void = [0]byte // ----------------------------------------------------------------------------- +type asyncCall interface { + Resume() + Call() + Done() bool +} + type AsyncCall[OutT any] interface { + Call() Await(timeout ...time.Duration) (ret OutT, err error) Chan() <-chan OutT - EnsureDone() + Done() bool } -// llgo:link AsyncCall.Await llgo.await -func Await[OutT any](call AsyncCall[OutT], timeout ...time.Duration) (ret OutT, err error) { - return +type executor struct { + ac asyncCall + mu sync.Mutex + cond *sync.Cond + susp bool } -//go:linkname Timeout llgo.timeout -func Timeout(time.Duration) (ret AsyncCall[Void]) +func newExecutor() *executor { + e := &executor{} + e.cond = sync.NewCond(&e.mu) + return e +} -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) - }() +func (e *executor) Resume() { + e.mu.Lock() + defer e.mu.Unlock() + e.susp = false + e.cond.Signal() +} + +func Run[OutT any](ac AsyncCall[OutT]) (OutT, error) { + e := newExecutor() + p := ac.(*PromiseImpl[OutT]) + p.Exec = e + + for { + e.mu.Lock() + for e.susp { + e.cond.Wait() + } + e.mu.Unlock() + e.susp = true + if ac.Done() { + return p.Value, p.Err + } + ac.Call() } - 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() } // ----------------------------------------------------------------------------- @@ -118,37 +95,49 @@ func (p Promise[OutT]) Await(timeout ...time.Duration) (ret OutT, err error) { return } +func (p Promise[OutT]) Call() { + +} + func (p Promise[OutT]) Chan() <-chan OutT { return nil } -func (p Promise[OutT]) EnsureDone() { - +func (p Promise[OutT]) Done() bool { + return false } // ----------------------------------------------------------------------------- type PromiseImpl[TOut any] struct { - Func func(resolve func(TOut, error)) - Value TOut - Err error Prev int Next int + Exec *executor + Debug string - c chan TOut + Func func(resolve func(TOut, error)) + Err error + Value TOut + c chan TOut } func (p *PromiseImpl[TOut]) Resume() { - p.Func(func(v TOut, err error) { - p.Value = v - p.Err = err - }) + p.Exec.Resume() } -func (p *PromiseImpl[TOut]) EnsureDone() { - if p.Next == -1 { - panic("Promise already done") - } +func (p *PromiseImpl[TOut]) Done() bool { + return p.Next == -1 +} + +func (p *PromiseImpl[TOut]) Call() { + p.Func(func(v TOut, err error) { + if debugAsync { + log.Printf("Resolve task: %+v, %+v, %+v\n", p, v, err) + } + p.Value = v + p.Err = err + p.Resume() + }) } func (p *PromiseImpl[TOut]) Chan() <-chan TOut { From a4286dbd4b96d27f3e8489461718029d852468f0 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 25 Jul 2024 10:49:02 +0800 Subject: [PATCH 02/27] asyncio: improve performance by scheduling last frame --- x/io/_demo/asyncdemo/async.go | 72 ++++++++++------------------------- x/io/extra.go | 10 ++--- x/io/io.go | 49 +++++++++++++++--------- 3 files changed, 56 insertions(+), 75 deletions(-) diff --git a/x/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go index 519f74fb..2fdbfa85 100644 --- a/x/io/_demo/asyncdemo/async.go +++ b/x/io/_demo/asyncdemo/async.go @@ -15,15 +15,11 @@ import ( type Response struct { StatusCode int - mockBody string -} - -func (r *Response) mock(body string) { - r.mockBody = body + Body string } func (r *Response) Text() (resolve io.Promise[string]) { - resolve(r.mockBody, nil) + resolve(r.Body, nil) return } @@ -32,10 +28,10 @@ func (r *Response) TextCompiled() *io.PromiseImpl[string] { P.Debug = "Text" P.Func = func(resolve func(string, error)) { for { - switch P.Prev = P.Next; P.Prev { + switch P.Next { case 0: P.Next = -1 - resolve(r.mockBody, nil) + resolve(r.Body, nil) return default: panic("Promise already done") @@ -55,7 +51,7 @@ func Http(method string, url string, callback func(resp *Response, err error)) { body = "99.5" } time.Sleep(200 * time.Millisecond) - resp := &Response{StatusCode: 200, mockBody: body} + resp := &Response{StatusCode: 200, Body: body} callback(resp, nil) }() } @@ -70,7 +66,7 @@ func AsyncHttpGetCompiled(url string) *io.PromiseImpl[*Response] { P.Debug = "HttpGet" P.Func = func(resolve func(*Response, error)) { for { - switch P.Prev = P.Next; P.Prev { + switch P.Next { case 0: P.Next = -1 Http("GET", url, resolve) @@ -93,7 +89,7 @@ func AsyncHttpPostCompiled(url string) *io.PromiseImpl[*Response] { P.Debug = "HttpPost" P.Func = func(resolve func(*Response, error)) { for { - switch P.Prev = P.Next; P.Prev { + switch P.Next { case 0: P.Next = -1 Http("POST", url, resolve) @@ -147,18 +143,15 @@ func GetUserCompiled(name string) *io.PromiseImpl[User] { P.Debug = "GetUser" P.Func = func(resolve func(User, error)) { for { - switch P.Prev = P.Next; P.Prev { + switch P.Next { case 0: P.Next = 1 state1 = AsyncHttpGetCompiled("http://example.com/user/" + name) state1.Exec = P.Exec + state1.Parent = P state1.Call() return case 1: - if !state1.Done() { - state1.Call() - return - } P.Next = 2 resp, err := state1.Value, state1.Err log.Printf("resp: %v, err: %v\n", resp, err) @@ -174,14 +167,11 @@ func GetUserCompiled(name string) *io.PromiseImpl[User] { state2 = resp.TextCompiled() state2.Exec = P.Exec + state2.Parent = P state2.Call() log.Printf("TextCompiled state2: %v\n", state2) return case 2: - if !state2.Done() { - state2.Call() - return - } P.Next = -1 body, err := state2.Value, state2.Err if err != nil { @@ -240,18 +230,15 @@ func GetScoreCompiled() *io.PromiseImpl[float64] { P.Debug = "GetScore" P.Func = func(resolve func(float64, error)) { for { - switch P.Prev = P.Next; P.Prev { + switch P.Next { case 0: P.Next = 1 state1 = AsyncHttpGetCompiled("http://example.com/score/") state1.Exec = P.Exec + state1.Parent = P state1.Call() return case 1: - if !state1.Done() { - state1.Call() - return - } P.Next = 2 resp, err := state1.Value, state1.Err if err != nil { @@ -266,14 +253,11 @@ func GetScoreCompiled() *io.PromiseImpl[float64] { state2 = resp.TextCompiled() state2.Exec = P.Exec + state2.Parent = P state2.Call() return case 2: - if !state2.Done() { - state2.Call() - return - } P.Next = -1 body, err := state2.Value, state2.Err if err != nil { @@ -319,18 +303,15 @@ func DoUpdateCompiled(op string) *io.PromiseImpl[io.Void] { P.Debug = "DoUpdate" P.Func = func(resolve func(io.Void, error)) { for { - switch P.Prev = P.Next; P.Prev { + switch P.Next { case 0: P.Next = 1 state1 = AsyncHttpPostCompiled("http://example.com/update/" + op) state1.Exec = P.Exec + state1.Parent = P state1.Call() return case 1: - if !state1.Done() { - state1.Call() - return - } P.Next = -1 resp, err := state1.Value, state1.Err if err != nil { @@ -388,18 +369,15 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { P.Debug = "Demo" P.Func = func(resolve func(io.Void, error)) { for { - switch P.Prev = P.Next; P.Prev { + switch P.Next { case 0: P.Next = 1 state1 = GetUserCompiled("1") state1.Exec = P.Exec + state1.Parent = P state1.Call() return case 1: - if !state1.Done() { - state1.Call() - return - } P.Next = 2 user, err := state1.Value, state1.Err log.Printf("user: %v, err: %v\n", user, err) @@ -407,14 +385,10 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { state2 = io.Race[User](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) log.Printf("state2: %v\n", state2) state2.Exec = P.Exec + state2.Parent = P state2.Call() return case 2: - - if !state2.Done() { - state2.Call() - return - } log.Printf("case 2, state2: %+v\n", state2) P.Next = 3 @@ -424,13 +398,10 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { state3 = io.All[User]([]io.AsyncCall[User]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) log.Printf("state3: %v\n", state3) state3.Exec = P.Exec + state3.Parent = P state3.Call() return case 3: - if !state3.Done() { - state3.Call() - return - } P.Next = 4 users, err := state3.Value, state3.Err @@ -439,13 +410,10 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { state4 = io.Await3Compiled[User, float64, io.Void](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) log.Printf("state4: %v\n", state4) state4.Exec = P.Exec + state4.Parent = P state4.Call() return case 4: - if !state4.Done() { - state4.Call() - return - } P.Next = -1 user, score, _, err := state4.Value.V1, state4.Value.V2, state4.Value.V3, state4.Value.Err diff --git a/x/io/extra.go b/x/io/extra.go index afd0d72c..cd09f66a 100644 --- a/x/io/extra.go +++ b/x/io/extra.go @@ -60,7 +60,7 @@ func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { for _, ac := range acs { ac := ac go func(ac AsyncCall[OutT]) { - v, err := Run(ac) + v, err := Run[OutT](ac) rc <- Result[OutT]{v, err} }(ac) } @@ -94,7 +94,7 @@ func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { ac := ac wg.Add(1) go func(ac AsyncCall[OutT]) { - v, err := Run(ac) + v, err := Run[OutT](ac) ret[idx] = Result[OutT]{v, err} wg.Done() }(ac) @@ -157,15 +157,15 @@ func Await3Compiled[OutT1, OutT2, OutT3 any]( go func() { defer wg.Done() - ret.V1, ret.Err = Run(ac1) + ret.V1, ret.Err = Run[OutT1](ac1) }() go func() { defer wg.Done() - ret.V2, ret.Err = Run(ac2) + ret.V2, ret.Err = Run[OutT2](ac2) }() go func() { defer wg.Done() - ret.V3, ret.Err = Run(ac3) + ret.V3, ret.Err = Run[OutT3](ac3) }() wg.Wait() if debugAsync { diff --git a/x/io/io.go b/x/io/io.go index f116ad93..736e5306 100644 --- a/x/io/io.go +++ b/x/io/io.go @@ -35,23 +35,19 @@ type Void = [0]byte // ----------------------------------------------------------------------------- type asyncCall interface { + parent() asyncCall Resume() Call() Done() bool } type AsyncCall[OutT any] interface { - Call() - Await(timeout ...time.Duration) (ret OutT, err error) - Chan() <-chan OutT - Done() bool } type executor struct { - ac asyncCall + acs []asyncCall mu sync.Mutex cond *sync.Cond - susp bool } func newExecutor() *executor { @@ -60,10 +56,10 @@ func newExecutor() *executor { return e } -func (e *executor) Resume() { +func (e *executor) schedule(ac asyncCall) { e.mu.Lock() - defer e.mu.Unlock() - e.susp = false + e.acs = append(e.acs, ac) + e.mu.Unlock() e.cond.Signal() } @@ -71,18 +67,28 @@ func Run[OutT any](ac AsyncCall[OutT]) (OutT, error) { e := newExecutor() p := ac.(*PromiseImpl[OutT]) p.Exec = e + var rootAc asyncCall = p + e.schedule(rootAc) for { e.mu.Lock() - for e.susp { + for len(e.acs) == 0 { e.cond.Wait() } e.mu.Unlock() - e.susp = true + ac := e.acs[0] + e.acs = e.acs[1:] if ac.Done() { - return p.Value, p.Err + if ac == rootAc { + return p.Value, p.Err + } + parent := ac.parent() + if parent != nil { + parent.Resume() + } + } else { + ac.Call() } - ac.Call() } } @@ -110,10 +116,10 @@ func (p Promise[OutT]) Done() bool { // ----------------------------------------------------------------------------- type PromiseImpl[TOut any] struct { - Prev int - Next int - Exec *executor - Debug string + Next int + Exec *executor + Parent asyncCall + Debug string Func func(resolve func(TOut, error)) Err error @@ -121,8 +127,15 @@ type PromiseImpl[TOut any] struct { c chan TOut } +func (p *PromiseImpl[TOut]) parent() asyncCall { + return p.Parent +} + func (p *PromiseImpl[TOut]) Resume() { - p.Exec.Resume() + if debugAsync { + log.Printf("Resume task: %+v\n", p) + } + p.Exec.schedule(p) } func (p *PromiseImpl[TOut]) Done() bool { From 08e0ace9a28455ad9167632bceca4635ab30be70 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 25 Jul 2024 11:04:40 +0800 Subject: [PATCH 03/27] asyncio: improve schedule --- x/io/io.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/x/io/io.go b/x/io/io.go index 736e5306..86ebaef1 100644 --- a/x/io/io.go +++ b/x/io/io.go @@ -78,16 +78,9 @@ func Run[OutT any](ac AsyncCall[OutT]) (OutT, error) { e.mu.Unlock() ac := e.acs[0] e.acs = e.acs[1:] - if ac.Done() { - if ac == rootAc { - return p.Value, p.Err - } - parent := ac.parent() - if parent != nil { - parent.Resume() - } - } else { - ac.Call() + ac.Call() + if ac.Done() && ac == rootAc { + return p.Value, p.Err } } } @@ -149,7 +142,9 @@ func (p *PromiseImpl[TOut]) Call() { } p.Value = v p.Err = err - p.Resume() + if p.Parent != nil { + p.Parent.Resume() + } }) } From 91df9957f50e978774bf0fc2b9aadda07ee8c4e4 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 26 Jul 2024 10:04:31 +0800 Subject: [PATCH 04/27] asyncio: parallel and concurrent extra functions --- x/io/_demo/asyncdemo/async.go | 13 +- x/io/extra.go | 238 +++++++++++++++++++++++++--------- 2 files changed, 186 insertions(+), 65 deletions(-) diff --git a/x/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go index 2fdbfa85..ff75e943 100644 --- a/x/io/_demo/asyncdemo/async.go +++ b/x/io/_demo/asyncdemo/async.go @@ -169,10 +169,11 @@ func GetUserCompiled(name string) *io.PromiseImpl[User] { state2.Exec = P.Exec state2.Parent = P state2.Call() - log.Printf("TextCompiled state2: %v\n", state2) + log.Printf("TextCompiled state2: %+v\n", state2) return case 2: P.Next = -1 + log.Printf("TextCompiled state2: %+v\n", state2) body, err := state2.Value, state2.Err if err != nil { resolve(User{}, err) @@ -380,10 +381,10 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { case 1: P.Next = 2 user, err := state1.Value, state1.Err - log.Printf("user: %v, err: %v\n", user, err) + log.Printf("user: %+v, err: %v\n", user, err) state2 = io.Race[User](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) - log.Printf("state2: %v\n", state2) + log.Printf("state2: %+v\n", state2) state2.Exec = P.Exec state2.Parent = P state2.Call() @@ -393,10 +394,10 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { P.Next = 3 user, err := state2.Value, state2.Err - log.Printf("race user: %v, err: %v\n", user, err) + log.Printf("race user: %+v, err: %v\n", user, err) state3 = io.All[User]([]io.AsyncCall[User]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) - log.Printf("state3: %v\n", state3) + log.Printf("state3: %+v\n", state3) state3.Exec = P.Exec state3.Parent = P state3.Call() @@ -408,7 +409,7 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { log.Println(users, err) state4 = io.Await3Compiled[User, float64, io.Void](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) - log.Printf("state4: %v\n", state4) + log.Printf("state4: %+v\n", state4) state4.Exec = P.Exec state4.Parent = P state4.Call() diff --git a/x/io/extra.go b/x/io/extra.go index cd09f66a..163b20e6 100644 --- a/x/io/extra.go +++ b/x/io/extra.go @@ -52,59 +52,98 @@ type Result[T any] struct { // llgo:link Race llgo.race func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { + if len(acs) == 0 { + panic("face: no promise") + } + ps := make([]*PromiseImpl[OutT], len(acs)) + for idx, ac := range acs { + ps[idx] = ac.(*PromiseImpl[OutT]) + } + remaining := len(acs) + returned := false P := &PromiseImpl[OutT]{} P.Debug = "Race" P.Func = func(resolve func(OutT, error)) { - P.Next = -1 - rc := make(chan Result[OutT], len(acs)) - for _, ac := range acs { - ac := ac - go func(ac AsyncCall[OutT]) { - v, err := Run[OutT](ac) - rc <- Result[OutT]{v, err} - }(ac) - } - - v := <-rc - if debugAsync { - log.Printf("io.Race done: %+v won the race\n", v) - } - resolve(v.V, v.Err) - go func() { - count := 1 - for count < len(acs) { - <-rc - count++ + switch P.Next { + case 0: + P.Next = 1 + for _, p := range ps { + p.Exec = P.Exec + p.Parent = P + p.Call() } - close(rc) - }() + return + case 1: + remaining-- + if remaining < 0 { + log.Fatalf("race: remaining < 0: %+v\n", remaining) + } + if returned { + return + } + + for _, p := range ps { + if p.Done() { + if debugAsync { + log.Printf("io.Race done: %+v won the race\n", p) + } + returned = true + resolve(p.Value, p.Err) + return + } + } + log.Fatalf("no promise done: %+v\n", ps) + return + default: + panic("unreachable") + } } return P } func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { + ps := make([]*PromiseImpl[OutT], len(acs)) + for idx, ac := range acs { + ps[idx] = ac.(*PromiseImpl[OutT]) + } + done := 0 P := &PromiseImpl[[]Result[OutT]]{} P.Debug = "All" P.Func = func(resolve func([]Result[OutT], error)) { - P.Next = -1 - wg := sync.WaitGroup{} - ret := make([]Result[OutT], len(acs)) - for idx, ac := range acs { - idx := idx - ac := ac - wg.Add(1) - go func(ac AsyncCall[OutT]) { - v, err := Run[OutT](ac) - ret[idx] = Result[OutT]{v, err} - wg.Done() - }(ac) - } + switch P.Next { + case 0: + P.Next = 1 + for _, p := range ps { + p.Exec = P.Exec + p.Parent = P + p.Call() + } + return + case 1: + done++ + if done < len(acs) { + return + } + P.Next = -1 - wg.Wait() - if debugAsync { - log.Printf("io.All done: %+v\n", ret) + for _, p := range ps { + if !p.Done() { + log.Fatalf("io.All: not done: %+v\n", p) + } + } + + ret := make([]Result[OutT], len(acs)) + for idx, p := range ps { + ret[idx] = Result[OutT]{p.Value, p.Err} + } + if debugAsync { + log.Printf("io.All done: %+v\n", ret) + } + resolve(ret, nil) + return + default: + panic("unreachable") } - resolve(ret, nil) } return P } @@ -142,35 +181,116 @@ type Await3Result[T1 any, T2 any, T3 any] struct { Err error } -// TODO(lijie): rewrite to unblock and avoid goroutine func Await3Compiled[OutT1, OutT2, OutT3 any]( ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], timeout ...time.Duration) *PromiseImpl[Await3Result[OutT1, OutT2, OutT3]] { + p1 := ac1.(*PromiseImpl[OutT1]) + p2 := ac2.(*PromiseImpl[OutT2]) + p3 := ac3.(*PromiseImpl[OutT3]) + remaining := 3 P := &PromiseImpl[Await3Result[OutT1, OutT2, OutT3]]{} P.Debug = "Await3" P.Func = func(resolve func(Await3Result[OutT1, OutT2, OutT3], error)) { - P.Next = -1 + switch P.Next { + case 0: + P.Next = 1 + p1.Exec = P.Exec + p1.Parent = P + p1.Call() - ret := Await3Result[OutT1, OutT2, OutT3]{} - wg := sync.WaitGroup{} - wg.Add(3) + p2.Exec = P.Exec + p2.Parent = P + p2.Call() - go func() { - defer wg.Done() - ret.V1, ret.Err = Run[OutT1](ac1) - }() - go func() { - defer wg.Done() - ret.V2, ret.Err = Run[OutT2](ac2) - }() - go func() { - defer wg.Done() - ret.V3, ret.Err = Run[OutT3](ac3) - }() - wg.Wait() - if debugAsync { - log.Printf("Await3 done: %+v\n", ret) + p3.Exec = P.Exec + p3.Parent = P + p3.Call() + return + case 1: + remaining-- + if remaining > 0 { + return + } + P.Next = -1 + // TODO(lijie): return every error? + if !p1.Done() || !p2.Done() || !p3.Done() { + log.Fatalf("io.Await3: not done: %+v, %+v, %+v\n", p1, p2, p3) + } + + var err error + if p1.Err != nil { + err = p1.Err + } else if p2.Err != nil { + err = p2.Err + } else if p3.Err != nil { + err = p3.Err + } + + resolve(Await3Result[OutT1, OutT2, OutT3]{ + V1: p1.Value, V2: p2.Value, V3: p3.Value, + Err: err, + }, err) + return + default: + panic("unreachable") } + } + return P +} + +// / PAll is a parallel version of All. +func PAll[OutT any](acs ...AsyncCall[OutT]) (resolve Promise[[]Result[OutT]]) { + panic("todo: PAll") +} + +func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { + P := &PromiseImpl[[]Result[OutT]]{} + P.Debug = "Parallel" + P.Func = func(resolve func([]Result[OutT], error)) { + ret := make([]Result[OutT], len(acs)) + wg := sync.WaitGroup{} + for idx, ac := range acs { + idx := idx + ac := ac + wg.Add(1) + go func(ac AsyncCall[OutT]) { + v, err := Run[OutT](ac) + ret[idx] = Result[OutT]{v, err} + wg.Done() + }(ac) + } + wg.Wait() + resolve(ret, nil) + } + return P +} + +// / PAwait3 is a parallel version of Await3. +func PAwait3[OutT1, OutT2, OutT3 any](ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) (resolve Promise[Await3Result[OutT1, OutT2, OutT3]]) { + panic("todo: PAwait2") +} + +func PAwait3Compiled[OutT1, OutT2, OutT3 any]( + ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) *PromiseImpl[Await3Result[OutT1, OutT2, OutT3]] { + P := &PromiseImpl[Await3Result[OutT1, OutT2, OutT3]]{} + P.Debug = "Parallel3" + P.Func = func(resolve func(Await3Result[OutT1, OutT2, OutT3], error)) { + ret := Await3Result[OutT1, OutT2, OutT3]{} + wg := sync.WaitGroup{} + wg.Add(3) + go func() { + ret.V1, ret.Err = Run[OutT1](ac1) + wg.Done() + }() + go func() { + ret.V2, ret.Err = Run[OutT2](ac2) + wg.Done() + }() + go func() { + ret.V3, ret.Err = Run[OutT3](ac3) + wg.Done() + }() + wg.Wait() resolve(ret, nil) } return P From b2a2b2f29d53b2299a885ff73f60e6f3c31bc6d7 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 27 Jul 2024 15:08:09 +0800 Subject: [PATCH 05/27] asyncio: generator --- x/io/_demo/asyncdemo/async.go | 500 +++++++++++++++++++++------------- x/io/extra.go | 65 ++++- x/io/io.go | 42 ++- 3 files changed, 394 insertions(+), 213 deletions(-) diff --git a/x/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go index ff75e943..17138fc3 100644 --- a/x/io/_demo/asyncdemo/async.go +++ b/x/io/_demo/asyncdemo/async.go @@ -27,15 +27,13 @@ func (r *Response) TextCompiled() *io.PromiseImpl[string] { P := &io.PromiseImpl[string]{} P.Debug = "Text" P.Func = func(resolve func(string, error)) { - for { - switch P.Next { - case 0: - P.Next = -1 - resolve(r.Body, nil) - return - default: - panic("Promise already done") - } + switch P.Next { + case 0: + P.Next = -1 + resolve(r.Body, nil) + return + default: + panic("Promise already done") } } return P @@ -65,15 +63,13 @@ func AsyncHttpGetCompiled(url string) *io.PromiseImpl[*Response] { P := &io.PromiseImpl[*Response]{} P.Debug = "HttpGet" P.Func = func(resolve func(*Response, error)) { - for { - switch P.Next { - case 0: - P.Next = -1 - Http("GET", url, resolve) - return - default: - panic("Promise already done") - } + switch P.Next { + case 0: + P.Next = -1 + Http("GET", url, resolve) + return + default: + panic("Promise already done") } } return P @@ -88,15 +84,13 @@ func AsyncHttpPostCompiled(url string) *io.PromiseImpl[*Response] { P := &io.PromiseImpl[*Response]{} P.Debug = "HttpPost" P.Func = func(resolve func(*Response, error)) { - for { - switch P.Next { - case 0: - P.Next = -1 - Http("POST", url, resolve) - return - default: - panic("Promise already done") - } + switch P.Next { + case 0: + P.Next = -1 + Http("POST", url, resolve) + return + default: + panic("Promise already done") } } return P @@ -142,55 +136,52 @@ func GetUserCompiled(name string) *io.PromiseImpl[User] { P := &io.PromiseImpl[User]{} P.Debug = "GetUser" P.Func = func(resolve func(User, error)) { - for { - switch P.Next { - case 0: - P.Next = 1 - state1 = AsyncHttpGetCompiled("http://example.com/user/" + name) - state1.Exec = P.Exec - state1.Parent = P - state1.Call() + switch P.Next { + case 0: + P.Next = 1 + state1 = AsyncHttpGetCompiled("http://example.com/user/" + name) + state1.Exec = P.Exec + state1.Parent = P + state1.Call() + return + case 1: + P.Next = 2 + resp, err := state1.Value(), state1.Err() + log.Printf("resp: %v, err: %v\n", resp, err) + if err != nil { + resolve(User{}, err) return - case 1: - P.Next = 2 - resp, err := state1.Value, state1.Err - log.Printf("resp: %v, err: %v\n", resp, err) - if err != nil { - resolve(User{}, err) - return - } - - if resp.StatusCode != 200 { - resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - state2 = resp.TextCompiled() - state2.Exec = P.Exec - state2.Parent = P - state2.Call() - log.Printf("TextCompiled state2: %+v\n", state2) - return - case 2: - P.Next = -1 - log.Printf("TextCompiled state2: %+v\n", state2) - body, err := state2.Value, state2.Err - if err != nil { - resolve(User{}, err) - return - } - user := User{} - log.Printf("body: %v\n", body) - if err := json.Unmarshal([]byte(body), &user); err != nil { - resolve(User{}, err) - return - } - - resolve(user, nil) - return - default: - panic(fmt.Sprintf("Promise already done, %+v", P)) } + + if resp.StatusCode != 200 { + resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) + return + } + + state2 = resp.TextCompiled() + state2.Exec = P.Exec + state2.Parent = P + state2.Call() + return + case 2: + P.Next = -1 + body, err := state2.Value(), state2.Err() + if err != nil { + resolve(User{}, err) + return + } + user := User{} + log.Printf("body: %v\n", body) + if err := json.Unmarshal([]byte(body), &user); err != nil { + resolve(User{}, err) + return + } + + log.Printf("resolve user: %+v\n", user) + resolve(user, nil) + return + default: + panic(fmt.Sprintf("Promise already done, %+v", P)) } } return P @@ -230,52 +221,50 @@ func GetScoreCompiled() *io.PromiseImpl[float64] { P := &io.PromiseImpl[float64]{} P.Debug = "GetScore" P.Func = func(resolve func(float64, error)) { - for { - switch P.Next { - case 0: - P.Next = 1 - state1 = AsyncHttpGetCompiled("http://example.com/score/") - state1.Exec = P.Exec - state1.Parent = P - state1.Call() + switch P.Next { + case 0: + P.Next = 1 + state1 = AsyncHttpGetCompiled("http://example.com/score/") + state1.Exec = P.Exec + state1.Parent = P + state1.Call() + return + case 1: + P.Next = 2 + resp, err := state1.Value(), state1.Err() + if err != nil { + resolve(0, err) return - case 1: - P.Next = 2 - resp, err := state1.Value, state1.Err - if err != nil { - resolve(0, err) - return - } - - if resp.StatusCode != 200 { - resolve(0, fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - state2 = resp.TextCompiled() - state2.Exec = P.Exec - state2.Parent = P - state2.Call() - - return - case 2: - P.Next = -1 - body, err := state2.Value, state2.Err - if err != nil { - resolve(0, err) - return - } - - score := 0.0 - if _, err := fmt.Sscanf(body, "%f", &score); err != nil { - resolve(0, err) - return - } - resolve(score, nil) - return - default: - panic("Promise already done") } + + if resp.StatusCode != 200 { + resolve(0, fmt.Errorf("http status code: %d", resp.StatusCode)) + return + } + + state2 = resp.TextCompiled() + state2.Exec = P.Exec + state2.Parent = P + state2.Call() + + return + case 2: + P.Next = -1 + body, err := state2.Value(), state2.Err() + if err != nil { + resolve(0, err) + return + } + + score := 0.0 + if _, err := fmt.Sscanf(body, "%f", &score); err != nil { + resolve(0, err) + return + } + resolve(score, nil) + return + default: + panic("Promise already done") } } return P @@ -303,33 +292,131 @@ func DoUpdateCompiled(op string) *io.PromiseImpl[io.Void] { P := &io.PromiseImpl[io.Void]{} P.Debug = "DoUpdate" P.Func = func(resolve func(io.Void, error)) { - for { - switch P.Next { - case 0: - P.Next = 1 - state1 = AsyncHttpPostCompiled("http://example.com/update/" + op) - state1.Exec = P.Exec - state1.Parent = P - state1.Call() + switch P.Next { + case 0: + P.Next = 1 + state1 = AsyncHttpPostCompiled("http://example.com/update/" + op) + state1.Exec = P.Exec + state1.Parent = P + state1.Call() + return + case 1: + P.Next = -1 + resp, err := state1.Value(), state1.Err() + if err != nil { + resolve(io.Void{}, err) return - case 1: - P.Next = -1 - resp, err := state1.Value, state1.Err - if err != nil { - resolve(io.Void{}, err) - return - } - - if resp.StatusCode != 200 { - resolve(io.Void{}, fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - resolve(io.Void{}, nil) - return - default: - panic("Promise already done") } + + if resp.StatusCode != 200 { + resolve(io.Void{}, fmt.Errorf("http status code: %d", resp.StatusCode)) + return + } + + resolve(io.Void{}, nil) + return + default: + panic("Promise already done") + } + } + return P +} + +func GenInts() (yield io.Promise[int]) { + yield(3, nil) + yield(2, nil) + yield(5, nil) + return +} + +func GenIntsCompiled() *io.PromiseImpl[int] { + P := &io.PromiseImpl[int]{} + P.Debug = "GenInts" + P.Func = func(resolve func(int, error)) { + switch P.Next { + case 0: + P.Next = 1 + resolve(3, nil) + return + case 1: + P.Next = 2 + resolve(2, nil) + return + case 2: + P.Next = 3 + resolve(5, nil) + return + case 3: + P.Next = -1 + resolve(0, fmt.Errorf("stop")) + return + default: + panic("Generator already done") + } + } + return P +} + +func GenUsers() (yield io.Promise[User]) { + u, _ := GetUser("Alice").Await() + yield(u, nil) + u, _ = GetUser("Bob").Await() + yield(u, nil) + u, _ = GetUser("Cindy").Await() + yield(u, nil) + log.Printf("genUsers done\n") + return +} + +func GenUsersCompiled() (resolve *io.PromiseImpl[User]) { + var state1, state2, state3 *io.PromiseImpl[User] + + P := &io.PromiseImpl[User]{} + P.Debug = "GenUsers" + P.Func = func(resolve func(User, error)) { + switch P.Next { + case 0: + P.Next = 1 + state1 = GetUserCompiled("Alice") + state1.Exec = P.Exec + state1.Parent = P + state1.Call() + return + case 1: + P.Next = 2 + u, _ := state1.Value(), state1.Err() + resolve(u, nil) + return + case 2: + P.Next = 3 + state2 = GetUserCompiled("Bob") + state2.Exec = P.Exec + state2.Parent = P + state2.Call() + return + case 3: + P.Next = 4 + u, _ := state2.Value(), state2.Err() + resolve(u, nil) + return + case 4: + P.Next = 5 + state3 = GetUserCompiled("Cindy") + state3.Exec = P.Exec + state3.Parent = P + state3.Call() + return + case 5: + P.Next = 6 + u, _ := state3.Value(), state3.Err() + resolve(u, nil) + return + case 6: + P.Next = -1 + resolve(User{}, fmt.Errorf("stop")) + return + default: + panic("Generator already done") } } return P @@ -348,6 +435,27 @@ func Demo() (resolve io.Promise[io.Void]) { user, score, _, err := io.Await3[User, float64, io.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")) log.Println(user, score, err) + // for loop with generator + g := GenInts() + for { + g.Call() + if g.Done() { + break + } + log.Println("genInt:", g.Value(), g.Done()) + } + + // for loop with async generator + g1 := GenUsers() + for { + g.Call() + u, err := io.Await[int](g) + if g1.Done() { + break + } + log.Println("genUser:", u, err) + } + // TODO(lijie): select from multiple promises without channel // select { // case user := <-GetUser("123").Chan(): @@ -365,65 +473,81 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { var state2 *io.PromiseImpl[User] var state3 *io.PromiseImpl[[]io.Result[User]] var state4 *io.PromiseImpl[io.Await3Result[User, float64, io.Void]] + var g1 *io.PromiseImpl[int] + var g2 *io.PromiseImpl[User] P := &io.PromiseImpl[io.Void]{} P.Debug = "Demo" P.Func = func(resolve func(io.Void, error)) { - for { - switch P.Next { - case 0: - P.Next = 1 - state1 = GetUserCompiled("1") - state1.Exec = P.Exec - state1.Parent = P - state1.Call() - return - case 1: - P.Next = 2 - user, err := state1.Value, state1.Err - log.Printf("user: %+v, err: %v\n", user, err) + switch P.Next { + case 0: + P.Next = 1 + state1 = GetUserCompiled("1") + state1.Exec = P.Exec + state1.Parent = P + state1.Call() + return + case 1: + P.Next = 2 + user, err := state1.Value(), state1.Err() + log.Printf("user: %+v, err: %v\n", user, err) - state2 = io.Race[User](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) - log.Printf("state2: %+v\n", state2) - state2.Exec = P.Exec - state2.Parent = P - state2.Call() - return - case 2: - log.Printf("case 2, state2: %+v\n", state2) + state2 = io.Race[User](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) + state2.Exec = P.Exec + state2.Parent = P + state2.Call() + return + case 2: + P.Next = 3 + user, err := state2.Value(), state2.Err() + log.Printf("race user: %+v, err: %v\n", user, err) - P.Next = 3 - user, err := state2.Value, state2.Err - log.Printf("race user: %+v, err: %v\n", user, err) + state3 = io.All[User]([]io.AsyncCall[User]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) + state3.Exec = P.Exec + state3.Parent = P + state3.Call() + return + case 3: - state3 = io.All[User]([]io.AsyncCall[User]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) - log.Printf("state3: %+v\n", state3) - state3.Exec = P.Exec - state3.Parent = P - state3.Call() - return - case 3: + P.Next = 4 + users, err := state3.Value(), state3.Err() + log.Println(users, err) - P.Next = 4 - users, err := state3.Value, state3.Err - log.Println(users, err) + state4 = io.Await3Compiled[User, float64, io.Void](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) + state4.Exec = P.Exec + state4.Parent = P + state4.Call() + return + case 4: + P.Next = 5 + user, score, _, err := state4.Value().V1, state4.Value().V2, state4.Value().V3, state4.Value().Err + log.Println(user, score, err) - state4 = io.Await3Compiled[User, float64, io.Void](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) - log.Printf("state4: %+v\n", state4) - state4.Exec = P.Exec - state4.Parent = P - state4.Call() - return - case 4: + g1 = GenIntsCompiled() + for { + g1.Call() + if g1.Done() { + break + } + log.Printf("genInt: %+v, done: %v\n", g1.Value(), g1.Done()) + } + + g2 = GenUsersCompiled() + g2.Exec = P.Exec + g2.Parent = P + g2.Call() + return + case 5: + if g2.Err() != nil { P.Next = -1 - user, score, _, err := state4.Value.V1, state4.Value.V2, state4.Value.V3, state4.Value.Err - log.Println(user, score, err) resolve(io.Void{}, nil) return - default: - panic("Promise already done") } + log.Printf("genUser: %+v, done: %v\n", g2.Value(), g2.Done()) + g2.Call() + default: + panic("Promise already done") } } return P diff --git a/x/io/extra.go b/x/io/extra.go index 163b20e6..7ea8df75 100644 --- a/x/io/extra.go +++ b/x/io/extra.go @@ -88,7 +88,7 @@ func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { log.Printf("io.Race done: %+v won the race\n", p) } returned = true - resolve(p.Value, p.Err) + resolve(p.value, p.err) return } } @@ -134,7 +134,7 @@ func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { ret := make([]Result[OutT], len(acs)) for idx, p := range ps { - ret[idx] = Result[OutT]{p.Value, p.Err} + ret[idx] = Result[OutT]{p.value, p.err} } if debugAsync { log.Printf("io.All done: %+v\n", ret) @@ -164,7 +164,50 @@ type Await2Result[T1 any, T2 any] struct { func Await2Compiled[OutT1, OutT2 any]( ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], timeout ...time.Duration) (ret *PromiseImpl[Await2Result[OutT1, OutT2]]) { - return + p1 := ac1.(*PromiseImpl[OutT1]) + p2 := ac2.(*PromiseImpl[OutT2]) + remaining := 2 + P := &PromiseImpl[Await2Result[OutT1, OutT2]]{} + P.Debug = "Await2" + P.Func = func(resolve func(Await2Result[OutT1, OutT2], error)) { + switch P.Next { + case 0: + P.Next = 1 + p1.Exec = P.Exec + p1.Parent = P + p1.Call() + + p2.Exec = P.Exec + p2.Parent = P + p2.Call() + return + case 1: + remaining-- + if remaining > 0 { + return + } + P.Next = -1 + if !p1.Done() || !p2.Done() { + log.Fatalf("io.Await2: not done: %+v, %+v\n", p1, p2) + } + + var err error + if p1.err != nil { + err = p1.err + } else if p2.err != nil { + err = p2.err + } + + resolve(Await2Result[OutT1, OutT2]{ + V1: p1.value, V2: p2.value, + Err: err, + }, err) + return + default: + panic("unreachable") + } + } + return P } // llgo:link Await3 llgo.await @@ -218,16 +261,16 @@ func Await3Compiled[OutT1, OutT2, OutT3 any]( } var err error - if p1.Err != nil { - err = p1.Err - } else if p2.Err != nil { - err = p2.Err - } else if p3.Err != nil { - err = p3.Err + if p1.err != nil { + err = p1.err + } else if p2.err != nil { + err = p2.err + } else if p3.err != nil { + err = p3.err } resolve(Await3Result[OutT1, OutT2, OutT3]{ - V1: p1.Value, V2: p2.Value, V3: p3.Value, + V1: p1.value, V2: p2.value, V3: p3.value, Err: err, }, err) return @@ -273,7 +316,7 @@ func PAwait3[OutT1, OutT2, OutT3 any](ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2] func PAwait3Compiled[OutT1, OutT2, OutT3 any]( ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) *PromiseImpl[Await3Result[OutT1, OutT2, OutT3]] { P := &PromiseImpl[Await3Result[OutT1, OutT2, OutT3]]{} - P.Debug = "Parallel3" + P.Debug = "PAwait3" P.Func = func(resolve func(Await3Result[OutT1, OutT2, OutT3], error)) { ret := Await3Result[OutT1, OutT2, OutT3]{} wg := sync.WaitGroup{} diff --git a/x/io/io.go b/x/io/io.go index 86ebaef1..b82c4fcf 100644 --- a/x/io/io.go +++ b/x/io/io.go @@ -80,7 +80,7 @@ func Run[OutT any](ac AsyncCall[OutT]) (OutT, error) { e.acs = e.acs[1:] ac.Call() if ac.Done() && ac == rootAc { - return p.Value, p.Err + return p.value, p.err } } } @@ -91,32 +91,40 @@ 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 + panic("should not called") } func (p Promise[OutT]) Call() { - + panic("should not called") } func (p Promise[OutT]) Chan() <-chan OutT { - return nil + panic("should not called") } func (p Promise[OutT]) Done() bool { - return false + panic("should not called") +} + +func (p Promise[OutT]) Err() error { + panic("should not called") +} + +func (p Promise[OutT]) Value() OutT { + panic("should not called") } // ----------------------------------------------------------------------------- type PromiseImpl[TOut any] struct { + Debug string Next int Exec *executor Parent asyncCall - Debug string Func func(resolve func(TOut, error)) - Err error - Value TOut + err error + value TOut c chan TOut } @@ -137,23 +145,31 @@ func (p *PromiseImpl[TOut]) Done() bool { func (p *PromiseImpl[TOut]) Call() { p.Func(func(v TOut, err error) { + p.value = v + p.err = err if debugAsync { log.Printf("Resolve task: %+v, %+v, %+v\n", p, v, err) } - p.Value = v - p.Err = err if p.Parent != nil { p.Parent.Resume() } }) } +func (p *PromiseImpl[TOut]) Err() error { + return p.err +} + +func (p *PromiseImpl[TOut]) Value() TOut { + return p.value +} + func (p *PromiseImpl[TOut]) Chan() <-chan TOut { if p.c == nil { p.c = make(chan TOut, 1) p.Func(func(v TOut, err error) { - p.Value = v - p.Err = err + p.value = v + p.err = err p.c <- v }) } @@ -163,5 +179,3 @@ func (p *PromiseImpl[TOut]) Chan() <-chan TOut { func (p *PromiseImpl[TOut]) Await(timeout ...time.Duration) (ret TOut, err error) { panic("should not called") } - -// ----------------------------------------------------------------------------- From df5f1afb740722ac450a1327eba00a7b8e68278c Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 27 Jul 2024 15:26:02 +0800 Subject: [PATCH 06/27] asyncio: demo comments --- x/io/_demo/asyncdemo/async.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/x/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go index 17138fc3..48483ce6 100644 --- a/x/io/_demo/asyncdemo/async.go +++ b/x/io/_demo/asyncdemo/async.go @@ -19,6 +19,7 @@ type Response struct { } func (r *Response) Text() (resolve io.Promise[string]) { + // return r.Body, nil resolve(r.Body, nil) return } @@ -105,26 +106,31 @@ type User struct { func GetUser(name string) (resolve io.Promise[User]) { resp, err := AsyncHttpGet("http://example.com/user/" + name).Await() if err != nil { + // return User{}, err resolve(User{}, err) return } if resp.StatusCode != 200 { + // return User{}, fmt.Errorf("http status code: %d", resp.StatusCode) resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) return } body, err := resp.Text().Await() if err != nil { + // return User{}, err resolve(User{}, err) return } user := User{} if err := json.Unmarshal([]byte(body), &user); err != nil { + // return User{}, err resolve(User{}, err) return } + // return user, nil resolve(user, nil) return } @@ -190,26 +196,32 @@ func GetUserCompiled(name string) *io.PromiseImpl[User] { func GetScore() (resolve io.Promise[float64]) { resp, err := AsyncHttpGet("http://example.com/score/").Await() if err != nil { + // return 0, err resolve(0, err) return } if resp.StatusCode != 200 { + // return 0, fmt.Errorf("http status code: %d", resp.StatusCode) resolve(0, fmt.Errorf("http status code: %d", resp.StatusCode)) return } body, err := resp.Text().Await() if err != nil { + // return 0, err resolve(0, err) return } score := 0.0 if _, err := fmt.Sscanf(body, "%f", &score); err != nil { + // return 0, err resolve(0, err) return } + + // return score, nil resolve(score, nil) return } @@ -273,15 +285,18 @@ func GetScoreCompiled() *io.PromiseImpl[float64] { func DoUpdate(op string) (resolve io.Promise[io.Void]) { resp, err := AsyncHttpPost("http://example.com/update/" + op).Await() if err != nil { + // return err resolve(io.Void{}, err) return } if resp.StatusCode != 200 { + // return fmt.Errorf("http status code: %d", resp.StatusCode) resolve(io.Void{}, fmt.Errorf("http status code: %d", resp.StatusCode)) return } + // return nil resolve(io.Void{}, nil) return } @@ -446,6 +461,7 @@ func Demo() (resolve io.Promise[io.Void]) { } // for loop with async generator + // for u, err := range GenUsers() {...} g1 := GenUsers() for { g.Call() From a578155dcb0e42feab80a996f9525649783df07e Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sun, 28 Jul 2024 17:56:30 +0800 Subject: [PATCH 07/27] asyncio: multi return types promise/generator --- x/io/_demo/asyncdemo/async.go | 465 ++++++++++++++++++---------------- x/io/extra.go | 162 ++++-------- x/io/io.go | 114 +++++---- 3 files changed, 371 insertions(+), 370 deletions(-) diff --git a/x/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go index 48483ce6..4cce61c4 100644 --- a/x/io/_demo/asyncdemo/async.go +++ b/x/io/_demo/asyncdemo/async.go @@ -12,35 +12,7 @@ import ( // ----------------------------------------------------------------------------- -type Response struct { - StatusCode int - - Body string -} - -func (r *Response) Text() (resolve io.Promise[string]) { - // return r.Body, nil - resolve(r.Body, nil) - return -} - -func (r *Response) TextCompiled() *io.PromiseImpl[string] { - P := &io.PromiseImpl[string]{} - P.Debug = "Text" - P.Func = func(resolve func(string, error)) { - switch P.Next { - case 0: - P.Next = -1 - resolve(r.Body, nil) - return - default: - panic("Promise already done") - } - } - return P -} - -func Http(method string, url string, callback func(resp *Response, err error)) { +func http(method string, url string, callback func(resp *Response, err error)) { go func() { body := "" if strings.HasPrefix(url, "http://example.com/user/") { @@ -55,40 +27,86 @@ func Http(method string, url string, callback func(resp *Response, err error)) { }() } -func AsyncHttpGet(url string) (resolve io.Promise[*Response]) { - Http("GET", url, resolve) - return +// ----------------------------------------------------------------------------- + +type Response struct { + StatusCode int + + Body string } -func AsyncHttpGetCompiled(url string) *io.PromiseImpl[*Response] { - P := &io.PromiseImpl[*Response]{} - P.Debug = "HttpGet" - P.Func = func(resolve func(*Response, error)) { - switch P.Next { +func (r *Response) Text() *io.Promise[io.R2[string, error]] { + co := &io.Promise[io.R2[string, error]]{} + co.Func = func() { + co.Return(io.R2[string, error]{V1: r.Body, V2: nil}) + } + return co +} + +func (r *Response) TextCompiled() *io.Promise[io.R2[string, error]] { + co := &io.Promise[io.R2[string, error]]{} + co.Debug = "Text" + co.Func = func() { + switch co.Next { case 0: - P.Next = -1 - Http("GET", url, resolve) + co.Next = -1 + co.Return(io.R2[string, error]{V1: r.Body, V2: nil}) return default: panic("Promise already done") } } - return P + return co } -func AsyncHttpPost(url string) (resolve io.Promise[*Response]) { - Http("POST", url, resolve) - return +func AsyncHttpGet(url string) *io.Promise[io.R2[*Response, error]] { + co := &io.Promise[io.R2[*Response, error]]{} + co.Func = func() { + http("GET", url, func(resp *Response, err error) { + co.Return(io.R2[*Response, error]{V1: resp, V2: nil}) + }) + } + return co } -func AsyncHttpPostCompiled(url string) *io.PromiseImpl[*Response] { - P := &io.PromiseImpl[*Response]{} +func AsyncHttpGetCompiled(url string) *io.Promise[io.R2[*Response, error]] { + co := &io.Promise[io.R2[*Response, error]]{} + co.Debug = "HttpGet" + co.Func = func() { + switch co.Next { + case 0: + co.Next = -1 + http("GET", url, func(resp *Response, err error) { + co.Return(io.R2[*Response, error]{V1: resp, V2: nil}) + }) + return + default: + panic("Promise already done") + } + } + return co +} + +func AsyncHttpPost(url string) *io.Promise[io.R2[*Response, error]] { + co := &io.Promise[io.R2[*Response, error]]{} + co.Func = func() { + http("POST", url, func(resp *Response, err error) { + co.Return(io.R2[*Response, error]{V1: resp, V2: nil}) + }) + } + return co +} + +func AsyncHttpPostCompiled(url string) *io.Promise[io.R2[*Response, error]] { + P := &io.Promise[io.R2[*Response, error]]{} P.Debug = "HttpPost" - P.Func = func(resolve func(*Response, error)) { + P.Func = func() { switch P.Next { case 0: P.Next = -1 - Http("POST", url, resolve) + http("POST", url, func(resp *Response, err error) { + P.Return(io.R2[*Response, error]{V1: resp, V2: nil}) + }) return default: panic("Promise already done") @@ -103,351 +121,365 @@ type User struct { Name string } -func GetUser(name string) (resolve io.Promise[User]) { - resp, err := AsyncHttpGet("http://example.com/user/" + name).Await() +func GetUser(name string) (co *io.Promise[io.R2[User, error]]) { + resp, err := AsyncHttpGet("http://example.com/user/" + name).Await().Values() if err != nil { // return User{}, err - resolve(User{}, err) + co.Return(io.R2[User, error]{V1: User{}, V2: err}) return } if resp.StatusCode != 200 { // return User{}, fmt.Errorf("http status code: %d", resp.StatusCode) - resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) + co.Return(io.R2[User, error]{V1: User{}, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) return } - body, err := resp.Text().Await() + body, err := resp.Text().Await().Values() if err != nil { // return User{}, err - resolve(User{}, err) + co.Return(io.R2[User, error]{V1: User{}, V2: err}) return } user := User{} if err := json.Unmarshal([]byte(body), &user); err != nil { // return User{}, err - resolve(User{}, err) + co.Return(io.R2[User, error]{V1: User{}, V2: err}) return } // return user, nil - resolve(user, nil) + co.Return(io.R2[User, error]{V1: user, V2: nil}) return } -func GetUserCompiled(name string) *io.PromiseImpl[User] { - var state1 *io.PromiseImpl[*Response] - var state2 *io.PromiseImpl[string] +func GetUserCompiled(name string) (co *io.Promise[io.R2[User, error]]) { + var state1 *io.Promise[io.R2[*Response, error]] + var state2 *io.Promise[io.R2[string, error]] - P := &io.PromiseImpl[User]{} - P.Debug = "GetUser" - P.Func = func(resolve func(User, error)) { - switch P.Next { + co = &io.Promise[io.R2[User, error]]{} + co.Debug = "GetUser" + co.Func = func() { + switch co.Next { case 0: - P.Next = 1 + co.Next = 1 state1 = AsyncHttpGetCompiled("http://example.com/user/" + name) - state1.Exec = P.Exec - state1.Parent = P + state1.Exec = co.Exec + state1.Parent = co state1.Call() return case 1: - P.Next = 2 - resp, err := state1.Value(), state1.Err() + co.Next = 2 + resp, err := state1.Value().Values() log.Printf("resp: %v, err: %v\n", resp, err) if err != nil { - resolve(User{}, err) + co.Return(io.R2[User, error]{V1: User{}, V2: err}) return } if resp.StatusCode != 200 { - resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) + co.Return(io.R2[User, error]{V1: User{}, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) return } state2 = resp.TextCompiled() - state2.Exec = P.Exec - state2.Parent = P + state2.Exec = co.Exec + state2.Parent = co state2.Call() return case 2: - P.Next = -1 - body, err := state2.Value(), state2.Err() + co.Next = -1 + body, err := state2.Value().Values() if err != nil { - resolve(User{}, err) + co.Return(io.R2[User, error]{V1: User{}, V2: err}) return } user := User{} log.Printf("body: %v\n", body) if err := json.Unmarshal([]byte(body), &user); err != nil { - resolve(User{}, err) + co.Return(io.R2[User, error]{V1: User{}, V2: err}) return } log.Printf("resolve user: %+v\n", user) - resolve(user, nil) + co.Return(io.R2[User, error]{V1: user, V2: nil}) return default: - panic(fmt.Sprintf("Promise already done, %+v", P)) + panic(fmt.Errorf("Promise already done, %+v", co)) } } - return P + return } -func GetScore() (resolve io.Promise[float64]) { - resp, err := AsyncHttpGet("http://example.com/score/").Await() +func GetScore() (co *io.Promise[io.R2[float64, error]]) { + resp, err := AsyncHttpGet("http://example.com/score/").Await().Values() if err != nil { - // return 0, err - resolve(0, err) + co.Return(io.R2[float64, error]{V1: 0, V2: err}) return } if resp.StatusCode != 200 { // return 0, fmt.Errorf("http status code: %d", resp.StatusCode) - resolve(0, fmt.Errorf("http status code: %d", resp.StatusCode)) + co.Return(io.R2[float64, error]{V1: 0, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) return } - body, err := resp.Text().Await() + body, err := resp.Text().Await().Values() if err != nil { // return 0, err - resolve(0, err) + co.Return(io.R2[float64, error]{V1: 0, V2: err}) return } score := 0.0 if _, err := fmt.Sscanf(body, "%f", &score); err != nil { // return 0, err - resolve(0, err) + co.Return(io.R2[float64, error]{V1: 0, V2: err}) return } // return score, nil - resolve(score, nil) + co.Return(io.R2[float64, error]{V1: score, V2: nil}) return } -func GetScoreCompiled() *io.PromiseImpl[float64] { - var state1 *io.PromiseImpl[*Response] - var state2 *io.PromiseImpl[string] +func GetScoreCompiled() *io.Promise[io.R2[float64, error]] { + var state1 *io.Promise[io.R2[*Response, error]] + var state2 *io.Promise[io.R2[string, error]] - P := &io.PromiseImpl[float64]{} - P.Debug = "GetScore" - P.Func = func(resolve func(float64, error)) { - switch P.Next { + co := &io.Promise[io.R2[float64, error]]{} + co.Debug = "GetScore" + co.Func = func() { + switch co.Next { case 0: - P.Next = 1 + co.Next = 1 state1 = AsyncHttpGetCompiled("http://example.com/score/") - state1.Exec = P.Exec - state1.Parent = P + state1.Exec = co.Exec + state1.Parent = co state1.Call() return case 1: - P.Next = 2 - resp, err := state1.Value(), state1.Err() + co.Next = 2 + + resp, err := state1.Value().Values() if err != nil { - resolve(0, err) + co.Return(io.R2[float64, error]{V1: 0, V2: err}) return } if resp.StatusCode != 200 { - resolve(0, fmt.Errorf("http status code: %d", resp.StatusCode)) + co.Return(io.R2[float64, error]{V1: 0, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) return } state2 = resp.TextCompiled() - state2.Exec = P.Exec - state2.Parent = P + state2.Exec = co.Exec + state2.Parent = co state2.Call() return case 2: - P.Next = -1 - body, err := state2.Value(), state2.Err() + co.Next = -1 + body, err := state2.Value().Values() if err != nil { - resolve(0, err) + co.Return(io.R2[float64, error]{V1: 0, V2: err}) return } score := 0.0 if _, err := fmt.Sscanf(body, "%f", &score); err != nil { - resolve(0, err) + co.Return(io.R2[float64, error]{V1: 0, V2: err}) return } - resolve(score, nil) + co.Return(io.R2[float64, error]{V1: score, V2: nil}) return default: panic("Promise already done") } } - return P + return co } -func DoUpdate(op string) (resolve io.Promise[io.Void]) { - resp, err := AsyncHttpPost("http://example.com/update/" + op).Await() +func DoUpdate(op string) (co *io.Promise[error]) { + resp, err := AsyncHttpPost("http://example.com/update/" + op).Await().Values() if err != nil { - // return err - resolve(io.Void{}, err) + co.Return(err) return } if resp.StatusCode != 200 { - // return fmt.Errorf("http status code: %d", resp.StatusCode) - resolve(io.Void{}, fmt.Errorf("http status code: %d", resp.StatusCode)) - return + co.Return(fmt.Errorf("http status code: %d", resp.StatusCode)) } - // return nil - resolve(io.Void{}, nil) + co.Return(nil) return } -func DoUpdateCompiled(op string) *io.PromiseImpl[io.Void] { - var state1 *io.PromiseImpl[*Response] +func DoUpdateCompiled(op string) *io.Promise[error] { + var state1 *io.Promise[io.R2[*Response, error]] - P := &io.PromiseImpl[io.Void]{} - P.Debug = "DoUpdate" - P.Func = func(resolve func(io.Void, error)) { - switch P.Next { + co := &io.Promise[error]{} + co.Debug = "DoUpdate" + co.Func = func() { + switch co.Next { case 0: - P.Next = 1 + co.Next = 1 state1 = AsyncHttpPostCompiled("http://example.com/update/" + op) - state1.Exec = P.Exec - state1.Parent = P + state1.Exec = co.Exec + state1.Parent = co state1.Call() return case 1: - P.Next = -1 - resp, err := state1.Value(), state1.Err() + co.Next = -1 + resp, err := state1.Value().Values() if err != nil { - resolve(io.Void{}, err) + co.Return(err) return } if resp.StatusCode != 200 { - resolve(io.Void{}, fmt.Errorf("http status code: %d", resp.StatusCode)) + co.Return(fmt.Errorf("http status code: %d", resp.StatusCode)) return } - resolve(io.Void{}, nil) + co.Return(nil) return default: panic("Promise already done") } } - return P + return co } -func GenInts() (yield io.Promise[int]) { - yield(3, nil) - yield(2, nil) - yield(5, nil) +func GenInts() (co *io.Promise[int]) { + co.Yield(3) + co.Yield(2) + co.Yield(5) return } -func GenIntsCompiled() *io.PromiseImpl[int] { - P := &io.PromiseImpl[int]{} - P.Debug = "GenInts" - P.Func = func(resolve func(int, error)) { - switch P.Next { +func GenIntsCompiled() *io.Promise[int] { + co := &io.Promise[int]{} + co.Debug = "GenInts" + co.Func = func() { + switch co.Next { case 0: - P.Next = 1 - resolve(3, nil) + co.Next = 1 + co.Yield(3) return case 1: - P.Next = 2 - resolve(2, nil) + co.Next = 2 + co.Yield(2) return case 2: - P.Next = 3 - resolve(5, nil) + co.Next = 3 + co.Yield(5) return case 3: - P.Next = -1 - resolve(0, fmt.Errorf("stop")) - return + co.Next = -1 default: panic("Generator already done") } } - return P + return co } -func GenUsers() (yield io.Promise[User]) { - u, _ := GetUser("Alice").Await() - yield(u, nil) - u, _ = GetUser("Bob").Await() - yield(u, nil) - u, _ = GetUser("Cindy").Await() - yield(u, nil) +// Generator with async calls and panic +func GenUsers() (co *io.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 GenUsersCompiled() (resolve *io.PromiseImpl[User]) { - var state1, state2, state3 *io.PromiseImpl[User] +func GenUsersCompiled() (resolve *io.Promise[User]) { + var state1, state2, state3 *io.Promise[io.R2[User, error]] - P := &io.PromiseImpl[User]{} - P.Debug = "GenUsers" - P.Func = func(resolve func(User, error)) { - switch P.Next { + co := &io.Promise[User]{} + co.Debug = "GenUsers" + co.Func = func() { + switch co.Next { case 0: - P.Next = 1 + co.Next = 1 state1 = GetUserCompiled("Alice") - state1.Exec = P.Exec - state1.Parent = P + state1.Exec = co.Exec + state1.Parent = co state1.Call() return case 1: - P.Next = 2 - u, _ := state1.Value(), state1.Err() - resolve(u, nil) + co.Next = 2 + u, err := state1.Value().Values() + if err != nil { + panic(err) + } else { + co.Yield(u) + } return case 2: - P.Next = 3 + co.Next = 3 state2 = GetUserCompiled("Bob") - state2.Exec = P.Exec - state2.Parent = P + state2.Exec = co.Exec + state2.Parent = co state2.Call() return case 3: - P.Next = 4 - u, _ := state2.Value(), state2.Err() - resolve(u, nil) + co.Next = 4 + u, err := state2.Value().Values() + if err != nil { + panic(err) + } else { + co.Yield(u) + } return case 4: - P.Next = 5 + co.Next = 5 state3 = GetUserCompiled("Cindy") - state3.Exec = P.Exec - state3.Parent = P + state3.Exec = co.Exec + state3.Parent = co state3.Call() return case 5: - P.Next = 6 - u, _ := state3.Value(), state3.Err() - resolve(u, nil) + co.Next = 6 + u, err := state3.Value().Values() + if err != nil { + panic(err) + } else { + co.Yield(u) + } return case 6: - P.Next = -1 - resolve(User{}, fmt.Errorf("stop")) - return + co.Next = -1 default: panic("Generator already done") } } - return P + return co } -func Demo() (resolve io.Promise[io.Void]) { - user, err := GetUser("1").Await() +func Demo() { + user, err := GetUser("1").Await().Values() log.Println(user, err) - user, err = io.Race[User](GetUser("2"), GetUser("3"), GetUser("4")).Await() + user, err = io.Race[io.R2[User, error]](GetUser("2"), GetUser("3"), GetUser("4")).Value().Values() log.Println(user, err) - users, err := io.All[User]([]io.AsyncCall[User]{GetUser("5"), GetUser("6"), GetUser("7")}).Await() + users := io.All[io.R2[User, error]]([]io.AsyncCall[io.R2[User, error]]{GetUser("5"), GetUser("6"), GetUser("7")}).Value() log.Println(users, err) - user, score, _, err := io.Await3[User, float64, io.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")) + user, score, _ := io.Await3Compiled[User, float64, io.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")).Value().Values() log.Println(user, score, err) // for loop with generator @@ -481,20 +513,19 @@ func Demo() (resolve io.Promise[io.Void]) { // 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[[]io.Result[User]] - var state4 *io.PromiseImpl[io.Await3Result[User, float64, io.Void]] - var g1 *io.PromiseImpl[int] - var g2 *io.PromiseImpl[User] +func DemoCompiled() *io.Promise[io.Void] { + var state1 *io.Promise[io.R2[User, error]] + var state2 *io.Promise[io.R2[User, error]] + var state3 *io.Promise[[]io.R2[User, error]] + var state4 *io.Promise[io.R3[io.R2[User, error], io.R2[float64, error], error]] + var g1 *io.Promise[int] + var g2 *io.Promise[User] - P := &io.PromiseImpl[io.Void]{} + P := &io.Promise[io.Void]{} P.Debug = "Demo" - P.Func = func(resolve func(io.Void, error)) { + P.Func = func() { switch P.Next { case 0: P.Next = 1 @@ -505,20 +536,20 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { return case 1: P.Next = 2 - user, err := state1.Value(), state1.Err() + user, err := state1.Value().Values() log.Printf("user: %+v, err: %v\n", user, err) - state2 = io.Race[User](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) + state2 = io.Race[io.R2[User, error]](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) state2.Exec = P.Exec state2.Parent = P state2.Call() return case 2: P.Next = 3 - user, err := state2.Value(), state2.Err() + user, err := state2.Value().Values() log.Printf("race user: %+v, err: %v\n", user, err) - state3 = io.All[User]([]io.AsyncCall[User]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) + state3 = io.All[io.R2[User, error]]([]io.AsyncCall[io.R2[User, error]]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) state3.Exec = P.Exec state3.Parent = P state3.Call() @@ -526,18 +557,18 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { case 3: P.Next = 4 - users, err := state3.Value(), state3.Err() - log.Println(users, err) + users := state3.Value() + log.Println(users) - state4 = io.Await3Compiled[User, float64, io.Void](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) + state4 = io.Await3Compiled[io.R2[User, error], io.R2[float64, error], error](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) state4.Exec = P.Exec state4.Parent = P state4.Call() return case 4: P.Next = 5 - user, score, _, err := state4.Value().V1, state4.Value().V2, state4.Value().V3, state4.Value().Err - log.Println(user, score, err) + user, score, _ := state4.Value().Values() + log.Println(user, score) g1 = GenIntsCompiled() for { @@ -555,13 +586,15 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { g2.Call() return case 5: - if g2.Err() != nil { + g2.Call() + if g2.Done() { P.Next = -1 - resolve(io.Void{}, nil) + log.Printf("Demo done\n") + P.Return(io.Void{}) return } log.Printf("genUser: %+v, done: %v\n", g2.Value(), g2.Done()) - g2.Call() + return default: panic("Promise already done") } @@ -572,6 +605,6 @@ func DemoCompiled() *io.PromiseImpl[io.Void] { func main() { log.SetFlags(log.Lshortfile | log.LstdFlags) // io.Run(Demo()) - v, err := io.Run[io.Void](DemoCompiled()) - log.Println(v, err) + v := io.Run[io.Void](DemoCompiled()) + log.Println(v) } diff --git a/x/io/extra.go b/x/io/extra.go index 7ea8df75..802a53b1 100644 --- a/x/io/extra.go +++ b/x/io/extra.go @@ -33,13 +33,13 @@ func Await[OutT any](call AsyncCall[OutT], timeout ...time.Duration) (ret OutT, //go:linkname Timeout llgo.timeout func Timeout(time.Duration) (ret AsyncCall[Void]) -func TimeoutCompiled(d time.Duration) *PromiseImpl[Void] { - P := &PromiseImpl[Void]{} +func TimeoutCompiled(d time.Duration) *Promise[Void] { + P := &Promise[Void]{} P.Debug = "Timeout" - P.Func = func(resolve func(Void, error)) { + P.Func = func() { go func() { time.Sleep(d) - resolve(Void{}, nil) + P.Return(Void{}) }() } return P @@ -50,20 +50,19 @@ type Result[T any] struct { Err error } -// llgo:link Race llgo.race -func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { +func Race[OutT any](acs ...AsyncCall[OutT]) *Promise[OutT] { if len(acs) == 0 { - panic("face: no promise") + panic("race: no promise") } - ps := make([]*PromiseImpl[OutT], len(acs)) + ps := make([]*Promise[OutT], len(acs)) for idx, ac := range acs { - ps[idx] = ac.(*PromiseImpl[OutT]) + ps[idx] = ac.(*Promise[OutT]) } remaining := len(acs) returned := false - P := &PromiseImpl[OutT]{} + P := &Promise[OutT]{} P.Debug = "Race" - P.Func = func(resolve func(OutT, error)) { + P.Func = func() { switch P.Next { case 0: P.Next = 1 @@ -88,7 +87,7 @@ func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { log.Printf("io.Race done: %+v won the race\n", p) } returned = true - resolve(p.value, p.err) + P.Return(p.value) return } } @@ -101,15 +100,15 @@ func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { return P } -func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { - ps := make([]*PromiseImpl[OutT], len(acs)) +func All[OutT any](acs []AsyncCall[OutT]) *Promise[[]OutT] { + ps := make([]*Promise[OutT], len(acs)) for idx, ac := range acs { - ps[idx] = ac.(*PromiseImpl[OutT]) + ps[idx] = ac.(*Promise[OutT]) } done := 0 - P := &PromiseImpl[[]Result[OutT]]{} + P := &Promise[[]OutT]{} P.Debug = "All" - P.Func = func(resolve func([]Result[OutT], error)) { + P.Func = func() { switch P.Next { case 0: P.Next = 1 @@ -132,14 +131,14 @@ func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { } } - ret := make([]Result[OutT], len(acs)) + ret := make([]OutT, len(acs)) for idx, p := range ps { - ret[idx] = Result[OutT]{p.value, p.err} + ret[idx] = p.value } if debugAsync { log.Printf("io.All done: %+v\n", ret) } - resolve(ret, nil) + P.Return(ret) return default: panic("unreachable") @@ -149,27 +148,15 @@ func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { } // 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]]) { - p1 := ac1.(*PromiseImpl[OutT1]) - p2 := ac2.(*PromiseImpl[OutT2]) + timeout ...time.Duration) (ret *Promise[R3[OutT1, OutT2, error]]) { + p1 := ac1.(*Promise[OutT1]) + p2 := ac2.(*Promise[OutT2]) remaining := 2 - P := &PromiseImpl[Await2Result[OutT1, OutT2]]{} + P := &Promise[R3[OutT1, OutT2, error]]{} P.Debug = "Await2" - P.Func = func(resolve func(Await2Result[OutT1, OutT2], error)) { + P.Func = func() { switch P.Next { case 0: P.Next = 1 @@ -191,17 +178,11 @@ func Await2Compiled[OutT1, OutT2 any]( log.Fatalf("io.Await2: not done: %+v, %+v\n", p1, p2) } - var err error - if p1.err != nil { - err = p1.err - } else if p2.err != nil { - err = p2.err - } - - resolve(Await2Result[OutT1, OutT2]{ - V1: p1.value, V2: p2.value, - Err: err, - }, err) + P.Return(R3[OutT1, OutT2, error]{ + V1: p1.value, + V2: p2.value, + V3: nil, + }) return default: panic("unreachable") @@ -210,30 +191,17 @@ func Await2Compiled[OutT1, OutT2 any]( return P } -// 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 -} - +// llgo:link Await2 llgo.await func Await3Compiled[OutT1, OutT2, OutT3 any]( ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], - timeout ...time.Duration) *PromiseImpl[Await3Result[OutT1, OutT2, OutT3]] { - p1 := ac1.(*PromiseImpl[OutT1]) - p2 := ac2.(*PromiseImpl[OutT2]) - p3 := ac3.(*PromiseImpl[OutT3]) + timeout ...time.Duration) *Promise[R3[OutT1, OutT2, OutT3]] { + p1 := ac1.(*Promise[OutT1]) + p2 := ac2.(*Promise[OutT2]) + p3 := ac3.(*Promise[OutT3]) remaining := 3 - P := &PromiseImpl[Await3Result[OutT1, OutT2, OutT3]]{} + P := &Promise[R3[OutT1, OutT2, OutT3]]{} P.Debug = "Await3" - P.Func = func(resolve func(Await3Result[OutT1, OutT2, OutT3], error)) { + P.Func = func() { switch P.Next { case 0: P.Next = 1 @@ -260,19 +228,11 @@ func Await3Compiled[OutT1, OutT2, OutT3 any]( log.Fatalf("io.Await3: not done: %+v, %+v, %+v\n", p1, p2, p3) } - var err error - if p1.err != nil { - err = p1.err - } else if p2.err != nil { - err = p2.err - } else if p3.err != nil { - err = p3.err - } - - resolve(Await3Result[OutT1, OutT2, OutT3]{ - V1: p1.value, V2: p2.value, V3: p3.value, - Err: err, - }, err) + P.Return(R3[OutT1, OutT2, OutT3]{ + V1: p1.value, + V2: p2.value, + V3: p3.value, + }) return default: panic("unreachable") @@ -281,60 +241,50 @@ func Await3Compiled[OutT1, OutT2, OutT3 any]( return P } -// / PAll is a parallel version of All. -func PAll[OutT any](acs ...AsyncCall[OutT]) (resolve Promise[[]Result[OutT]]) { - panic("todo: PAll") -} - -func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[[]Result[OutT]] { - P := &PromiseImpl[[]Result[OutT]]{} +func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *Promise[[]OutT] { + P := &Promise[[]OutT]{} P.Debug = "Parallel" - P.Func = func(resolve func([]Result[OutT], error)) { - ret := make([]Result[OutT], len(acs)) + P.Func = func() { + ret := make([]OutT, len(acs)) wg := sync.WaitGroup{} for idx, ac := range acs { idx := idx ac := ac wg.Add(1) go func(ac AsyncCall[OutT]) { - v, err := Run[OutT](ac) - ret[idx] = Result[OutT]{v, err} + v := Run[OutT](ac) + ret[idx] = v wg.Done() }(ac) } wg.Wait() - resolve(ret, nil) + P.Return(ret) } return P } -// / PAwait3 is a parallel version of Await3. -func PAwait3[OutT1, OutT2, OutT3 any](ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) (resolve Promise[Await3Result[OutT1, OutT2, OutT3]]) { - panic("todo: PAwait2") -} - func PAwait3Compiled[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) *PromiseImpl[Await3Result[OutT1, OutT2, OutT3]] { - P := &PromiseImpl[Await3Result[OutT1, OutT2, OutT3]]{} + ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) *Promise[R4[OutT1, OutT2, OutT3, error]] { + P := &Promise[R4[OutT1, OutT2, OutT3, error]]{} P.Debug = "PAwait3" - P.Func = func(resolve func(Await3Result[OutT1, OutT2, OutT3], error)) { - ret := Await3Result[OutT1, OutT2, OutT3]{} + P.Func = func() { + ret := R4[OutT1, OutT2, OutT3, error]{} wg := sync.WaitGroup{} wg.Add(3) go func() { - ret.V1, ret.Err = Run[OutT1](ac1) + ret.V1 = Run[OutT1](ac1) wg.Done() }() go func() { - ret.V2, ret.Err = Run[OutT2](ac2) + ret.V2 = Run[OutT2](ac2) wg.Done() }() go func() { - ret.V3, ret.Err = Run[OutT3](ac3) + ret.V3 = Run[OutT3](ac3) wg.Done() }() wg.Wait() - resolve(ret, nil) + P.Return(ret) } return P } diff --git a/x/io/io.go b/x/io/io.go index b82c4fcf..16ae959f 100644 --- a/x/io/io.go +++ b/x/io/io.go @@ -20,8 +20,6 @@ import ( "log" "sync" _ "unsafe" - - "time" ) const ( @@ -42,6 +40,7 @@ type asyncCall interface { } type AsyncCall[OutT any] interface { + Resume() } type executor struct { @@ -63,9 +62,9 @@ func (e *executor) schedule(ac asyncCall) { e.cond.Signal() } -func Run[OutT any](ac AsyncCall[OutT]) (OutT, error) { +func Run[OutT any](ac AsyncCall[OutT]) OutT { e := newExecutor() - p := ac.(*PromiseImpl[OutT]) + p := ac.(*Promise[OutT]) p.Exec = e var rootAc asyncCall = p e.schedule(rootAc) @@ -80,102 +79,121 @@ func Run[OutT any](ac AsyncCall[OutT]) (OutT, error) { e.acs = e.acs[1:] ac.Call() if ac.Done() && ac == rootAc { - return p.value, p.err + return p.value } } } // ----------------------------------------------------------------------------- -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) { - panic("should not called") +type R1[T any] struct { + V1 T } -func (p Promise[OutT]) Call() { - panic("should not called") +func (r R1[T]) Values() T { + return r.V1 } -func (p Promise[OutT]) Chan() <-chan OutT { - panic("should not called") +type R2[T1 any, T2 any] struct { + V1 T1 + V2 T2 } -func (p Promise[OutT]) Done() bool { - panic("should not called") +func (r R2[T1, T2]) Values() (T1, T2) { + return r.V1, r.V2 } -func (p Promise[OutT]) Err() error { - panic("should not called") +type R3[T1 any, T2 any, T3 any] struct { + V1 T1 + V2 T2 + V3 T3 } -func (p Promise[OutT]) Value() OutT { - panic("should not called") +func (r R3[T1, T2, T3]) Values() (T1, T2, T3) { + return r.V1, r.V2, r.V3 } -// ----------------------------------------------------------------------------- +type R4[T1 any, T2 any, T3 any, T4 any] struct { + V1 T1 + V2 T2 + V3 T3 + V4 T4 +} -type PromiseImpl[TOut any] struct { +func (r R4[T1, T2, T3, T4]) Values() (T1, T2, T3, T4) { + return r.V1, r.V2, r.V3, r.V4 +} + +type Promise[TOut any] struct { Debug string Next int Exec *executor Parent asyncCall - Func func(resolve func(TOut, error)) - err error + Func func() value TOut c chan TOut } -func (p *PromiseImpl[TOut]) parent() asyncCall { +func NewPromise[TOut any](fn func()) *Promise[TOut] { + return &Promise[TOut]{Func: fn} +} + +func (p *Promise[TOut]) parent() asyncCall { return p.Parent } -func (p *PromiseImpl[TOut]) Resume() { +func (p *Promise[TOut]) Resume() { if debugAsync { log.Printf("Resume task: %+v\n", p) } p.Exec.schedule(p) } -func (p *PromiseImpl[TOut]) Done() bool { +func (p *Promise[TOut]) Done() bool { return p.Next == -1 } -func (p *PromiseImpl[TOut]) Call() { - p.Func(func(v TOut, err error) { - p.value = v - p.err = err - if debugAsync { - log.Printf("Resolve task: %+v, %+v, %+v\n", p, v, err) - } - if p.Parent != nil { - p.Parent.Resume() - } - }) +func (p *Promise[TOut]) Call() { + p.Func() } -func (p *PromiseImpl[TOut]) Err() error { - return p.err +func (p *Promise[TOut]) Return(v TOut) { + // TODO(lijie): panic if already resolved + p.value = v + if p.c != nil { + p.c <- v + } + if debugAsync { + log.Printf("Return task: %+v\n", p) + } + if p.Parent != nil { + p.Parent.Resume() + } } -func (p *PromiseImpl[TOut]) Value() TOut { +func (p *Promise[TOut]) Yield(v TOut) { + p.value = v + if debugAsync { + log.Printf("Yield task: %+v\n", p) + } + if p.Parent != nil { + p.Parent.Resume() + } +} + +func (p *Promise[TOut]) Value() TOut { return p.value } -func (p *PromiseImpl[TOut]) Chan() <-chan TOut { +func (p *Promise[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 - }) + p.Func() } return p.c } -func (p *PromiseImpl[TOut]) Await(timeout ...time.Duration) (ret TOut, err error) { +func (p *Promise[TOut]) Await() (ret TOut) { panic("should not called") } From 375b2b579eed77489f47a8d22d566edf0e295454 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 29 Jul 2024 15:42:00 +0800 Subject: [PATCH 08/27] asyncio: refactor --- x/io/_demo/asyncdemo/async.go | 191 ++++++++++++++++++---------------- x/io/io.go | 174 +++++-------------------------- x/io/{ => naive}/extra.go | 75 +++++++------ x/io/naive/naive.go | 154 +++++++++++++++++++++++++++ x/tuple/tuple.go | 57 ++++++++++ 5 files changed, 372 insertions(+), 279 deletions(-) rename x/io/{ => naive}/extra.go (74%) create mode 100644 x/io/naive/naive.go create mode 100644 x/tuple/tuple.go diff --git a/x/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go index 4cce61c4..3cccb9ed 100644 --- a/x/io/_demo/asyncdemo/async.go +++ b/x/io/_demo/asyncdemo/async.go @@ -8,6 +8,8 @@ import ( "time" "github.com/goplus/llgo/x/io" + "github.com/goplus/llgo/x/io/naive" + "github.com/goplus/llgo/x/tuple" ) // ----------------------------------------------------------------------------- @@ -35,22 +37,19 @@ type Response struct { Body string } -func (r *Response) Text() *io.Promise[io.R2[string, error]] { - co := &io.Promise[io.R2[string, error]]{} - co.Func = func() { - co.Return(io.R2[string, error]{V1: r.Body, V2: nil}) - } - return co +func (r *Response) Text() (co *io.Promise[tuple.Tuple2[string, error]]) { + co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) + return } -func (r *Response) TextCompiled() *io.Promise[io.R2[string, error]] { - co := &io.Promise[io.R2[string, error]]{} +func (r *Response) TextCompiled() *naive.PromiseImpl[tuple.Tuple2[string, error]] { + co := &naive.PromiseImpl[tuple.Tuple2[string, error]]{} co.Debug = "Text" co.Func = func() { switch co.Next { case 0: co.Next = -1 - co.Return(io.R2[string, error]{V1: r.Body, V2: nil}) + co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) return default: panic("Promise already done") @@ -59,26 +58,31 @@ func (r *Response) TextCompiled() *io.Promise[io.R2[string, error]] { return co } -func AsyncHttpGet(url string) *io.Promise[io.R2[*Response, error]] { - co := &io.Promise[io.R2[*Response, error]]{} - co.Func = func() { - http("GET", url, func(resp *Response, err error) { - co.Return(io.R2[*Response, error]{V1: resp, V2: nil}) - }) - } +// async AsyncHttpGet(url string) (resp *Response, err error) { +// http("GET", url, func(resp *Response, err error) { +// return resp, err +// }) +// } +func AsyncHttpGet(url string) *io.Promise[tuple.Tuple2[*Response, error]] { + co := &io.Promise[tuple.Tuple2[*Response, error]]{} + http("GET", url, func(resp *Response, err error) { + co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) + }) + co.Suspend() return co } -func AsyncHttpGetCompiled(url string) *io.Promise[io.R2[*Response, error]] { - co := &io.Promise[io.R2[*Response, error]]{} +func AsyncHttpGetCompiled(url string) *naive.PromiseImpl[tuple.Tuple2[*Response, error]] { + co := &naive.PromiseImpl[tuple.Tuple2[*Response, error]]{} co.Debug = "HttpGet" co.Func = func() { switch co.Next { case 0: co.Next = -1 http("GET", url, func(resp *Response, err error) { - co.Return(io.R2[*Response, error]{V1: resp, V2: nil}) + co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) }) + co.Suspend() return default: panic("Promise already done") @@ -87,25 +91,24 @@ func AsyncHttpGetCompiled(url string) *io.Promise[io.R2[*Response, error]] { return co } -func AsyncHttpPost(url string) *io.Promise[io.R2[*Response, error]] { - co := &io.Promise[io.R2[*Response, error]]{} - co.Func = func() { - http("POST", url, func(resp *Response, err error) { - co.Return(io.R2[*Response, error]{V1: resp, V2: nil}) - }) - } +func AsyncHttpPost(url string) *io.Promise[tuple.Tuple2[*Response, error]] { + co := &io.Promise[tuple.Tuple2[*Response, error]]{} + http("POST", url, func(resp *Response, err error) { + co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) + }) + co.Suspend() return co } -func AsyncHttpPostCompiled(url string) *io.Promise[io.R2[*Response, error]] { - P := &io.Promise[io.R2[*Response, error]]{} +func AsyncHttpPostCompiled(url string) *naive.PromiseImpl[tuple.Tuple2[*Response, error]] { + P := &naive.PromiseImpl[tuple.Tuple2[*Response, error]]{} P.Debug = "HttpPost" P.Func = func() { switch P.Next { case 0: P.Next = -1 http("POST", url, func(resp *Response, err error) { - P.Return(io.R2[*Response, error]{V1: resp, V2: nil}) + P.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) }) return default: @@ -121,43 +124,43 @@ type User struct { Name string } -func GetUser(name string) (co *io.Promise[io.R2[User, error]]) { +func GetUser(name string) (co *naive.PromiseImpl[tuple.Tuple2[User, error]]) { resp, err := AsyncHttpGet("http://example.com/user/" + name).Await().Values() if err != nil { // return User{}, err - co.Return(io.R2[User, error]{V1: User{}, V2: 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(io.R2[User, error]{V1: User{}, V2: 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(io.R2[User, error]{V1: User{}, V2: 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(io.R2[User, error]{V1: User{}, V2: err}) + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) return } // return user, nil - co.Return(io.R2[User, error]{V1: user, V2: nil}) + co.Return(tuple.Tuple2[User, error]{V1: user, V2: nil}) return } -func GetUserCompiled(name string) (co *io.Promise[io.R2[User, error]]) { - var state1 *io.Promise[io.R2[*Response, error]] - var state2 *io.Promise[io.R2[string, error]] +func GetUserCompiled(name string) (co *naive.PromiseImpl[tuple.Tuple2[User, error]]) { + var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] + var state2 *naive.PromiseImpl[tuple.Tuple2[string, error]] - co = &io.Promise[io.R2[User, error]]{} + co = &naive.PromiseImpl[tuple.Tuple2[User, error]]{} co.Debug = "GetUser" co.Func = func() { switch co.Next { @@ -173,12 +176,12 @@ func GetUserCompiled(name string) (co *io.Promise[io.R2[User, error]]) { resp, err := state1.Value().Values() log.Printf("resp: %v, err: %v\n", resp, err) if err != nil { - co.Return(io.R2[User, error]{V1: User{}, V2: err}) + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) return } if resp.StatusCode != 200 { - co.Return(io.R2[User, error]{V1: User{}, V2: 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 } @@ -191,18 +194,18 @@ func GetUserCompiled(name string) (co *io.Promise[io.R2[User, error]]) { co.Next = -1 body, err := state2.Value().Values() if err != nil { - co.Return(io.R2[User, error]{V1: User{}, V2: err}) + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) return } user := User{} log.Printf("body: %v\n", body) if err := json.Unmarshal([]byte(body), &user); err != nil { - co.Return(io.R2[User, error]{V1: User{}, V2: err}) + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) return } log.Printf("resolve user: %+v\n", user) - co.Return(io.R2[User, error]{V1: user, V2: nil}) + co.Return(tuple.Tuple2[User, error]{V1: user, V2: nil}) return default: panic(fmt.Errorf("Promise already done, %+v", co)) @@ -211,43 +214,43 @@ func GetUserCompiled(name string) (co *io.Promise[io.R2[User, error]]) { return } -func GetScore() (co *io.Promise[io.R2[float64, error]]) { +func GetScore() (co *naive.PromiseImpl[tuple.Tuple2[float64, error]]) { resp, err := AsyncHttpGet("http://example.com/score/").Await().Values() if err != nil { - co.Return(io.R2[float64, error]{V1: 0, V2: err}) + 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(io.R2[float64, error]{V1: 0, V2: 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(io.R2[float64, error]{V1: 0, V2: 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(io.R2[float64, error]{V1: 0, V2: err}) + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) return } // return score, nil - co.Return(io.R2[float64, error]{V1: score, V2: nil}) + co.Return(tuple.Tuple2[float64, error]{V1: score, V2: nil}) return } -func GetScoreCompiled() *io.Promise[io.R2[float64, error]] { - var state1 *io.Promise[io.R2[*Response, error]] - var state2 *io.Promise[io.R2[string, error]] +func GetScoreCompiled() *naive.PromiseImpl[tuple.Tuple2[float64, error]] { + var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] + var state2 *naive.PromiseImpl[tuple.Tuple2[string, error]] - co := &io.Promise[io.R2[float64, error]]{} + co := &naive.PromiseImpl[tuple.Tuple2[float64, error]]{} co.Debug = "GetScore" co.Func = func() { switch co.Next { @@ -263,12 +266,12 @@ func GetScoreCompiled() *io.Promise[io.R2[float64, error]] { resp, err := state1.Value().Values() if err != nil { - co.Return(io.R2[float64, error]{V1: 0, V2: err}) + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) return } if resp.StatusCode != 200 { - co.Return(io.R2[float64, error]{V1: 0, V2: 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 } @@ -282,16 +285,16 @@ func GetScoreCompiled() *io.Promise[io.R2[float64, error]] { co.Next = -1 body, err := state2.Value().Values() if err != nil { - co.Return(io.R2[float64, error]{V1: 0, V2: err}) + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) return } score := 0.0 if _, err := fmt.Sscanf(body, "%f", &score); err != nil { - co.Return(io.R2[float64, error]{V1: 0, V2: err}) + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) return } - co.Return(io.R2[float64, error]{V1: score, V2: nil}) + co.Return(tuple.Tuple2[float64, error]{V1: score, V2: nil}) return default: panic("Promise already done") @@ -300,7 +303,7 @@ func GetScoreCompiled() *io.Promise[io.R2[float64, error]] { return co } -func DoUpdate(op string) (co *io.Promise[error]) { +func DoUpdate(op string) (co *naive.PromiseImpl[error]) { resp, err := AsyncHttpPost("http://example.com/update/" + op).Await().Values() if err != nil { co.Return(err) @@ -315,10 +318,10 @@ func DoUpdate(op string) (co *io.Promise[error]) { return } -func DoUpdateCompiled(op string) *io.Promise[error] { - var state1 *io.Promise[io.R2[*Response, error]] +func DoUpdateCompiled(op string) *naive.PromiseImpl[error] { + var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] - co := &io.Promise[error]{} + co := &naive.PromiseImpl[error]{} co.Debug = "DoUpdate" co.Func = func() { switch co.Next { @@ -351,15 +354,15 @@ func DoUpdateCompiled(op string) *io.Promise[error] { return co } -func GenInts() (co *io.Promise[int]) { +func GenInts() (co *naive.PromiseImpl[int]) { co.Yield(3) co.Yield(2) co.Yield(5) return } -func GenIntsCompiled() *io.Promise[int] { - co := &io.Promise[int]{} +func GenIntsCompiled() *naive.PromiseImpl[int] { + co := &naive.PromiseImpl[int]{} co.Debug = "GenInts" co.Func = func() { switch co.Next { @@ -385,7 +388,7 @@ func GenIntsCompiled() *io.Promise[int] { } // Generator with async calls and panic -func GenUsers() (co *io.Promise[User]) { +func GenUsers() (co *naive.PromiseImpl[User]) { u, err := GetUser("Alice").Await().Values() if err != nil { panic(err) @@ -405,10 +408,10 @@ func GenUsers() (co *io.Promise[User]) { return } -func GenUsersCompiled() (resolve *io.Promise[User]) { - var state1, state2, state3 *io.Promise[io.R2[User, error]] +func GenUsersCompiled() (resolve *naive.PromiseImpl[User]) { + var state1, state2, state3 *naive.PromiseImpl[tuple.Tuple2[User, error]] - co := &io.Promise[User]{} + co := &naive.PromiseImpl[User]{} co.Debug = "GenUsers" co.Func = func() { switch co.Next { @@ -469,17 +472,17 @@ func GenUsersCompiled() (resolve *io.Promise[User]) { return co } -func Demo() { +func Demo() (co *io.Promise[io.Void]) { user, err := GetUser("1").Await().Values() log.Println(user, err) - user, err = io.Race[io.R2[User, error]](GetUser("2"), GetUser("3"), GetUser("4")).Value().Values() + user, err = naive.Race[tuple.Tuple2[User, error]](GetUser("2"), GetUser("3"), GetUser("4")).Value().Values() log.Println(user, err) - users := io.All[io.R2[User, error]]([]io.AsyncCall[io.R2[User, error]]{GetUser("5"), GetUser("6"), GetUser("7")}).Value() + 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, _ := io.Await3Compiled[User, float64, io.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")).Value().Values() + user, score, _ := naive.Await3Compiled[User, float64, io.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")).Value().Values() log.Println(user, score, err) // for loop with generator @@ -496,8 +499,8 @@ func Demo() { // for u, err := range GenUsers() {...} g1 := GenUsers() for { - g.Call() - u, err := io.Await[int](g) + g1.Call() + u := g1.Await() if g1.Done() { break } @@ -513,17 +516,21 @@ func Demo() { // case <-io.Timeout(5 * time.Second).Chan(): // log.Println("timeout") // } + + log.Println("Demo done") + co.Return(io.Void{}) + return } -func DemoCompiled() *io.Promise[io.Void] { - var state1 *io.Promise[io.R2[User, error]] - var state2 *io.Promise[io.R2[User, error]] - var state3 *io.Promise[[]io.R2[User, error]] - var state4 *io.Promise[io.R3[io.R2[User, error], io.R2[float64, error], error]] - var g1 *io.Promise[int] - var g2 *io.Promise[User] +func DemoCompiled() *naive.PromiseImpl[io.Void] { + var state1 *naive.PromiseImpl[tuple.Tuple2[User, error]] + var state2 *naive.PromiseImpl[tuple.Tuple2[User, error]] + var state3 *naive.PromiseImpl[[]tuple.Tuple2[User, error]] + var state4 *naive.PromiseImpl[tuple.Tuple3[tuple.Tuple2[User, error], tuple.Tuple2[float64, error], error]] + var g1 *naive.PromiseImpl[int] + var g2 *naive.PromiseImpl[User] - P := &io.Promise[io.Void]{} + P := &naive.PromiseImpl[io.Void]{} P.Debug = "Demo" P.Func = func() { switch P.Next { @@ -539,7 +546,7 @@ func DemoCompiled() *io.Promise[io.Void] { user, err := state1.Value().Values() log.Printf("user: %+v, err: %v\n", user, err) - state2 = io.Race[io.R2[User, error]](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) + state2 = naive.Race[tuple.Tuple2[User, error]](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) state2.Exec = P.Exec state2.Parent = P state2.Call() @@ -549,7 +556,7 @@ func DemoCompiled() *io.Promise[io.Void] { user, err := state2.Value().Values() log.Printf("race user: %+v, err: %v\n", user, err) - state3 = io.All[io.R2[User, error]]([]io.AsyncCall[io.R2[User, error]]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) + state3 = naive.All[tuple.Tuple2[User, error]]([]naive.AsyncCall[tuple.Tuple2[User, error]]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) state3.Exec = P.Exec state3.Parent = P state3.Call() @@ -560,7 +567,7 @@ func DemoCompiled() *io.Promise[io.Void] { users := state3.Value() log.Println(users) - state4 = io.Await3Compiled[io.R2[User, error], io.R2[float64, error], error](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) + state4 = naive.Await3Compiled[tuple.Tuple2[User, error], tuple.Tuple2[float64, error], error](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) state4.Exec = P.Exec state4.Parent = P state4.Call() @@ -604,7 +611,13 @@ func DemoCompiled() *io.Promise[io.Void] { func main() { log.SetFlags(log.Lshortfile | log.LstdFlags) - // io.Run(Demo()) - v := io.Run[io.Void](DemoCompiled()) + log.Printf("=========== Run Naive Demo ===========\n") + v := naive.RunImpl[io.Void](DemoCompiled()) log.Println(v) + log.Printf("=========== Run Naive Demo finished ===========\n") + + log.Printf("=========== Run Demo ===========\n") + v1 := Demo() + log.Println(v1) + log.Printf("=========== Run Demo finished ===========\n") } diff --git a/x/io/io.go b/x/io/io.go index 16ae959f..b6afc62e 100644 --- a/x/io/io.go +++ b/x/io/io.go @@ -17,8 +17,7 @@ package io import ( - "log" - "sync" + "unsafe" _ "unsafe" ) @@ -26,174 +25,49 @@ const ( LLGoPackage = "decl" ) -var debugAsync = false +const debugAsync = false type Void = [0]byte -// ----------------------------------------------------------------------------- - -type asyncCall interface { - parent() asyncCall - Resume() - Call() - Done() bool -} - -type AsyncCall[OutT any] interface { - Resume() -} - -type executor struct { - acs []asyncCall - mu sync.Mutex - cond *sync.Cond -} - -func newExecutor() *executor { - e := &executor{} - e.cond = sync.NewCond(&e.mu) - return e -} - -func (e *executor) schedule(ac asyncCall) { - e.mu.Lock() - e.acs = append(e.acs, ac) - e.mu.Unlock() - e.cond.Signal() -} - -func Run[OutT any](ac AsyncCall[OutT]) OutT { - e := newExecutor() - p := ac.(*Promise[OutT]) - p.Exec = e - var rootAc asyncCall = p - e.schedule(rootAc) - - for { - e.mu.Lock() - for len(e.acs) == 0 { - e.cond.Wait() - } - e.mu.Unlock() - ac := e.acs[0] - e.acs = e.acs[1:] - ac.Call() - if ac.Done() && ac == rootAc { - return p.value - } - } -} +type AsyncCall[TOut any] interface{} // ----------------------------------------------------------------------------- -type R1[T any] struct { - V1 T -} - -func (r R1[T]) Values() T { - return r.V1 -} - -type R2[T1 any, T2 any] struct { - V1 T1 - V2 T2 -} - -func (r R2[T1, T2]) Values() (T1, T2) { - return r.V1, r.V2 -} - -type R3[T1 any, T2 any, T3 any] struct { - V1 T1 - V2 T2 - V3 T3 -} - -func (r R3[T1, T2, T3]) Values() (T1, T2, T3) { - return r.V1, r.V2, r.V3 -} - -type R4[T1 any, T2 any, T3 any, T4 any] struct { - V1 T1 - V2 T2 - V3 T3 - V4 T4 -} - -func (r R4[T1, T2, T3, T4]) Values() (T1, T2, T3, T4) { - return r.V1, r.V2, r.V3, r.V4 -} - type Promise[TOut any] struct { - Debug string - Next int - Exec *executor - Parent asyncCall - - Func func() + hdl unsafe.Pointer value TOut - c chan TOut } -func NewPromise[TOut any](fn func()) *Promise[TOut] { - return &Promise[TOut]{Func: fn} -} - -func (p *Promise[TOut]) parent() asyncCall { - return p.Parent -} - -func (p *Promise[TOut]) Resume() { - if debugAsync { - log.Printf("Resume task: %+v\n", p) - } - p.Exec.schedule(p) -} - -func (p *Promise[TOut]) Done() bool { - return p.Next == -1 -} - -func (p *Promise[TOut]) Call() { - p.Func() +// llgo:link PromiseImpl llgo.coAwait +func (p *Promise[TOut]) Await() TOut { + panic("should not executed") } +// llgo:link Return llgo.coReturn func (p *Promise[TOut]) Return(v TOut) { - // TODO(lijie): panic if already resolved - p.value = v - if p.c != nil { - p.c <- v - } - if debugAsync { - log.Printf("Return task: %+v\n", p) - } - if p.Parent != nil { - p.Parent.Resume() - } + panic("should not executed") } +// llgo:link Yield llgo.coYield func (p *Promise[TOut]) Yield(v TOut) { - p.value = v - if debugAsync { - log.Printf("Yield task: %+v\n", p) - } - if p.Parent != nil { - p.Parent.Resume() - } + panic("should not executed") +} + +// llgo:link Suspend llgo.coSuspend +func (p *Promise[TOut]) Suspend() { + panic("should not executed") +} + +// llgo:link Resume llgo.coResume +func (p *Promise[TOut]) Resume() { + panic("should not executed") } func (p *Promise[TOut]) Value() TOut { return p.value } -func (p *Promise[TOut]) Chan() <-chan TOut { - if p.c == nil { - p.c = make(chan TOut, 1) - p.Func() - } - return p.c -} - -func (p *Promise[TOut]) Await() (ret TOut) { - panic("should not called") +// llgo:link Run llgo.coRun +func Run[TOut any](f func() TOut) TOut { + panic("should not executed") } diff --git a/x/io/extra.go b/x/io/naive/extra.go similarity index 74% rename from x/io/extra.go rename to x/io/naive/extra.go index 802a53b1..e92bf37d 100644 --- a/x/io/extra.go +++ b/x/io/naive/extra.go @@ -14,32 +14,27 @@ * limitations under the License. */ -package io +package naive import ( "log" "sync" "time" _ "unsafe" + + "github.com/goplus/llgo/x/io" + "github.com/goplus/llgo/x/tuple" ) // ----------------------------------------------------------------------------- -// 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) *Promise[Void] { - P := &Promise[Void]{} +func TimeoutCompiled(d time.Duration) *PromiseImpl[io.Void] { + P := &PromiseImpl[io.Void]{} P.Debug = "Timeout" P.Func = func() { go func() { time.Sleep(d) - P.Return(Void{}) + P.Return(io.Void{}) }() } return P @@ -50,17 +45,17 @@ type Result[T any] struct { Err error } -func Race[OutT any](acs ...AsyncCall[OutT]) *Promise[OutT] { +func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { if len(acs) == 0 { panic("race: no promise") } - ps := make([]*Promise[OutT], len(acs)) + ps := make([]*PromiseImpl[OutT], len(acs)) for idx, ac := range acs { - ps[idx] = ac.(*Promise[OutT]) + ps[idx] = ac.(*PromiseImpl[OutT]) } remaining := len(acs) returned := false - P := &Promise[OutT]{} + P := &PromiseImpl[OutT]{} P.Debug = "Race" P.Func = func() { switch P.Next { @@ -100,13 +95,13 @@ func Race[OutT any](acs ...AsyncCall[OutT]) *Promise[OutT] { return P } -func All[OutT any](acs []AsyncCall[OutT]) *Promise[[]OutT] { - ps := make([]*Promise[OutT], len(acs)) +func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]OutT] { + ps := make([]*PromiseImpl[OutT], len(acs)) for idx, ac := range acs { - ps[idx] = ac.(*Promise[OutT]) + ps[idx] = ac.(*PromiseImpl[OutT]) } done := 0 - P := &Promise[[]OutT]{} + P := &PromiseImpl[[]OutT]{} P.Debug = "All" P.Func = func() { switch P.Next { @@ -150,11 +145,11 @@ func All[OutT any](acs []AsyncCall[OutT]) *Promise[[]OutT] { // llgo:link Await2 llgo.await func Await2Compiled[OutT1, OutT2 any]( ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], - timeout ...time.Duration) (ret *Promise[R3[OutT1, OutT2, error]]) { - p1 := ac1.(*Promise[OutT1]) - p2 := ac2.(*Promise[OutT2]) + timeout ...time.Duration) (ret *PromiseImpl[tuple.Tuple3[OutT1, OutT2, error]]) { + p1 := ac1.(*PromiseImpl[OutT1]) + p2 := ac2.(*PromiseImpl[OutT2]) remaining := 2 - P := &Promise[R3[OutT1, OutT2, error]]{} + P := &PromiseImpl[tuple.Tuple3[OutT1, OutT2, error]]{} P.Debug = "Await2" P.Func = func() { switch P.Next { @@ -178,7 +173,7 @@ func Await2Compiled[OutT1, OutT2 any]( log.Fatalf("io.Await2: not done: %+v, %+v\n", p1, p2) } - P.Return(R3[OutT1, OutT2, error]{ + P.Return(tuple.Tuple3[OutT1, OutT2, error]{ V1: p1.value, V2: p2.value, V3: nil, @@ -194,12 +189,12 @@ func Await2Compiled[OutT1, OutT2 any]( // llgo:link Await2 llgo.await func Await3Compiled[OutT1, OutT2, OutT3 any]( ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], - timeout ...time.Duration) *Promise[R3[OutT1, OutT2, OutT3]] { - p1 := ac1.(*Promise[OutT1]) - p2 := ac2.(*Promise[OutT2]) - p3 := ac3.(*Promise[OutT3]) + timeout ...time.Duration) *PromiseImpl[tuple.Tuple3[OutT1, OutT2, OutT3]] { + p1 := ac1.(*PromiseImpl[OutT1]) + p2 := ac2.(*PromiseImpl[OutT2]) + p3 := ac3.(*PromiseImpl[OutT3]) remaining := 3 - P := &Promise[R3[OutT1, OutT2, OutT3]]{} + P := &PromiseImpl[tuple.Tuple3[OutT1, OutT2, OutT3]]{} P.Debug = "Await3" P.Func = func() { switch P.Next { @@ -228,7 +223,7 @@ func Await3Compiled[OutT1, OutT2, OutT3 any]( log.Fatalf("io.Await3: not done: %+v, %+v, %+v\n", p1, p2, p3) } - P.Return(R3[OutT1, OutT2, OutT3]{ + P.Return(tuple.Tuple3[OutT1, OutT2, OutT3]{ V1: p1.value, V2: p2.value, V3: p3.value, @@ -241,8 +236,8 @@ func Await3Compiled[OutT1, OutT2, OutT3 any]( return P } -func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *Promise[[]OutT] { - P := &Promise[[]OutT]{} +func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[[]OutT] { + P := &PromiseImpl[[]OutT]{} P.Debug = "Parallel" P.Func = func() { ret := make([]OutT, len(acs)) @@ -252,7 +247,7 @@ func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *Promise[[]OutT] { ac := ac wg.Add(1) go func(ac AsyncCall[OutT]) { - v := Run[OutT](ac) + v := RunImpl[OutT](ac) ret[idx] = v wg.Done() }(ac) @@ -264,23 +259,23 @@ func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *Promise[[]OutT] { } func PAwait3Compiled[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) *Promise[R4[OutT1, OutT2, OutT3, error]] { - P := &Promise[R4[OutT1, OutT2, OutT3, error]]{} + ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) *PromiseImpl[tuple.Tuple4[OutT1, OutT2, OutT3, error]] { + P := &PromiseImpl[tuple.Tuple4[OutT1, OutT2, OutT3, error]]{} P.Debug = "PAwait3" P.Func = func() { - ret := R4[OutT1, OutT2, OutT3, error]{} + ret := tuple.Tuple4[OutT1, OutT2, OutT3, error]{} wg := sync.WaitGroup{} wg.Add(3) go func() { - ret.V1 = Run[OutT1](ac1) + ret.V1 = RunImpl[OutT1](ac1) wg.Done() }() go func() { - ret.V2 = Run[OutT2](ac2) + ret.V2 = RunImpl[OutT2](ac2) wg.Done() }() go func() { - ret.V3 = Run[OutT3](ac3) + ret.V3 = RunImpl[OutT3](ac3) wg.Done() }() wg.Wait() diff --git a/x/io/naive/naive.go b/x/io/naive/naive.go new file mode 100644 index 00000000..a8de2bd9 --- /dev/null +++ b/x/io/naive/naive.go @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package naive + +import ( + "log" + "sync" +) + +const debugAsync = false + +// ----------------------------------------------------------------------------- + +type asyncCall interface { + parent() asyncCall + Resume() + Call() + Done() bool +} + +type AsyncCall[OutT any] interface { + Resume() +} + +type executor struct { + acs []asyncCall + mu sync.Mutex + cond *sync.Cond +} + +func newExecutor() *executor { + e := &executor{} + e.cond = sync.NewCond(&e.mu) + return e +} + +func (e *executor) schedule(ac asyncCall) { + e.mu.Lock() + e.acs = append(e.acs, ac) + e.mu.Unlock() + e.cond.Signal() +} + +func RunImpl[OutT any](ac AsyncCall[OutT]) OutT { + e := newExecutor() + p := ac.(*PromiseImpl[OutT]) + p.Exec = e + var rootAc asyncCall = p + e.schedule(rootAc) + + for { + e.mu.Lock() + for len(e.acs) == 0 { + e.cond.Wait() + } + e.mu.Unlock() + ac := e.acs[0] + e.acs = e.acs[1:] + ac.Call() + if ac.Done() && ac == rootAc { + return p.value + } + } +} + +// ----------------------------------------------------------------------------- + +type PromiseImpl[TOut any] struct { + Debug string + Next int + Exec *executor + Parent asyncCall + + Func func() + value TOut + c chan TOut +} + +func (p *PromiseImpl[TOut]) parent() asyncCall { + return p.Parent +} + +func (p *PromiseImpl[TOut]) Resume() { + if debugAsync { + log.Printf("Resume task: %+v\n", p) + } + p.Exec.schedule(p) +} + +func (p *PromiseImpl[TOut]) Done() bool { + return p.Next == -1 +} + +func (p *PromiseImpl[TOut]) Call() { + p.Func() +} + +func (p *PromiseImpl[TOut]) Suspend() { + +} + +func (p *PromiseImpl[TOut]) Return(v TOut) { + // TODO(lijie): panic if already resolved + p.value = v + if p.c != nil { + p.c <- v + } + if debugAsync { + log.Printf("Return task: %+v\n", p) + } + if p.Parent != nil { + p.Parent.Resume() + } +} + +func (p *PromiseImpl[TOut]) Yield(v TOut) { + p.value = v + if debugAsync { + log.Printf("Yield task: %+v\n", p) + } + if p.Parent != nil { + p.Parent.Resume() + } +} + +func (p *PromiseImpl[TOut]) Value() TOut { + return p.value +} + +func (p *PromiseImpl[TOut]) Chan() <-chan TOut { + if p.c == nil { + p.c = make(chan TOut, 1) + p.Func() + } + return p.c +} + +func (p *PromiseImpl[TOut]) Await() (ret TOut) { + panic("should not called") +} 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 +} From 0d3e78ad944f61efdf908e26c7d6a24df6cafe77 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 29 Jul 2024 23:17:21 +0800 Subject: [PATCH 09/27] asyncio: x/io -> x/async --- x/{io => async}/README.md | 0 x/{io => async}/_demo/asyncdemo/async.go | 30 ++++++++++++------------ x/{io/io.go => async/async.go} | 2 +- x/{io => async}/naive/extra.go | 18 +++++++------- x/{io => async}/naive/naive.go | 0 5 files changed, 25 insertions(+), 25 deletions(-) rename x/{io => async}/README.md (100%) rename x/{io => async}/_demo/asyncdemo/async.go (94%) rename x/{io/io.go => async/async.go} (99%) rename x/{io => async}/naive/extra.go (92%) rename x/{io => async}/naive/naive.go (100%) 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/io/_demo/asyncdemo/async.go b/x/async/_demo/asyncdemo/async.go similarity index 94% rename from x/io/_demo/asyncdemo/async.go rename to x/async/_demo/asyncdemo/async.go index 3cccb9ed..a0d49fd9 100644 --- a/x/io/_demo/asyncdemo/async.go +++ b/x/async/_demo/asyncdemo/async.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "github.com/goplus/llgo/x/io" - "github.com/goplus/llgo/x/io/naive" + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/async/naive" "github.com/goplus/llgo/x/tuple" ) @@ -37,7 +37,7 @@ type Response struct { Body string } -func (r *Response) Text() (co *io.Promise[tuple.Tuple2[string, error]]) { +func (r *Response) Text() (co *async.Promise[tuple.Tuple2[string, error]]) { co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) return } @@ -63,8 +63,8 @@ func (r *Response) TextCompiled() *naive.PromiseImpl[tuple.Tuple2[string, error] // return resp, err // }) // } -func AsyncHttpGet(url string) *io.Promise[tuple.Tuple2[*Response, error]] { - co := &io.Promise[tuple.Tuple2[*Response, error]]{} +func AsyncHttpGet(url string) *async.Promise[tuple.Tuple2[*Response, error]] { + co := &async.Promise[tuple.Tuple2[*Response, error]]{} http("GET", url, func(resp *Response, err error) { co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) }) @@ -91,8 +91,8 @@ func AsyncHttpGetCompiled(url string) *naive.PromiseImpl[tuple.Tuple2[*Response, return co } -func AsyncHttpPost(url string) *io.Promise[tuple.Tuple2[*Response, error]] { - co := &io.Promise[tuple.Tuple2[*Response, error]]{} +func AsyncHttpPost(url string) *async.Promise[tuple.Tuple2[*Response, error]] { + co := &async.Promise[tuple.Tuple2[*Response, error]]{} http("POST", url, func(resp *Response, err error) { co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) }) @@ -472,7 +472,7 @@ func GenUsersCompiled() (resolve *naive.PromiseImpl[User]) { return co } -func Demo() (co *io.Promise[io.Void]) { +func Demo() (co *async.Promise[async.Void]) { user, err := GetUser("1").Await().Values() log.Println(user, err) @@ -482,7 +482,7 @@ func Demo() (co *io.Promise[io.Void]) { 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, io.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")).Value().Values() + 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 @@ -513,16 +513,16 @@ func Demo() (co *io.Promise[io.Void]) { // log.Println("user:", user) // case score := <-GetScore().Chan(): // log.Println("score:", score) - // case <-io.Timeout(5 * time.Second).Chan(): + // case <-async.Timeout(5 * time.Second).Chan(): // log.Println("timeout") // } log.Println("Demo done") - co.Return(io.Void{}) + co.Return(async.Void{}) return } -func DemoCompiled() *naive.PromiseImpl[io.Void] { +func DemoCompiled() *naive.PromiseImpl[async.Void] { var state1 *naive.PromiseImpl[tuple.Tuple2[User, error]] var state2 *naive.PromiseImpl[tuple.Tuple2[User, error]] var state3 *naive.PromiseImpl[[]tuple.Tuple2[User, error]] @@ -530,7 +530,7 @@ func DemoCompiled() *naive.PromiseImpl[io.Void] { var g1 *naive.PromiseImpl[int] var g2 *naive.PromiseImpl[User] - P := &naive.PromiseImpl[io.Void]{} + P := &naive.PromiseImpl[async.Void]{} P.Debug = "Demo" P.Func = func() { switch P.Next { @@ -597,7 +597,7 @@ func DemoCompiled() *naive.PromiseImpl[io.Void] { if g2.Done() { P.Next = -1 log.Printf("Demo done\n") - P.Return(io.Void{}) + P.Return(async.Void{}) return } log.Printf("genUser: %+v, done: %v\n", g2.Value(), g2.Done()) @@ -612,7 +612,7 @@ func DemoCompiled() *naive.PromiseImpl[io.Void] { func main() { log.SetFlags(log.Lshortfile | log.LstdFlags) log.Printf("=========== Run Naive Demo ===========\n") - v := naive.RunImpl[io.Void](DemoCompiled()) + v := naive.RunImpl[async.Void](DemoCompiled()) log.Println(v) log.Printf("=========== Run Naive Demo finished ===========\n") diff --git a/x/io/io.go b/x/async/async.go similarity index 99% rename from x/io/io.go rename to x/async/async.go index b6afc62e..83b4af40 100644 --- a/x/io/io.go +++ b/x/async/async.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package io +package async import ( "unsafe" diff --git a/x/io/naive/extra.go b/x/async/naive/extra.go similarity index 92% rename from x/io/naive/extra.go rename to x/async/naive/extra.go index e92bf37d..21c00af3 100644 --- a/x/io/naive/extra.go +++ b/x/async/naive/extra.go @@ -22,19 +22,19 @@ import ( "time" _ "unsafe" - "github.com/goplus/llgo/x/io" + "github.com/goplus/llgo/x/async" "github.com/goplus/llgo/x/tuple" ) // ----------------------------------------------------------------------------- -func TimeoutCompiled(d time.Duration) *PromiseImpl[io.Void] { - P := &PromiseImpl[io.Void]{} +func TimeoutCompiled(d time.Duration) *PromiseImpl[async.Void] { + P := &PromiseImpl[async.Void]{} P.Debug = "Timeout" P.Func = func() { go func() { time.Sleep(d) - P.Return(io.Void{}) + P.Return(async.Void{}) }() } return P @@ -79,7 +79,7 @@ func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { for _, p := range ps { if p.Done() { if debugAsync { - log.Printf("io.Race done: %+v won the race\n", p) + log.Printf("async.Race done: %+v won the race\n", p) } returned = true P.Return(p.value) @@ -122,7 +122,7 @@ func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]OutT] { for _, p := range ps { if !p.Done() { - log.Fatalf("io.All: not done: %+v\n", p) + log.Fatalf("async.All: not done: %+v\n", p) } } @@ -131,7 +131,7 @@ func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]OutT] { ret[idx] = p.value } if debugAsync { - log.Printf("io.All done: %+v\n", ret) + log.Printf("async.All done: %+v\n", ret) } P.Return(ret) return @@ -170,7 +170,7 @@ func Await2Compiled[OutT1, OutT2 any]( } P.Next = -1 if !p1.Done() || !p2.Done() { - log.Fatalf("io.Await2: not done: %+v, %+v\n", p1, p2) + log.Fatalf("async.Await2: not done: %+v, %+v\n", p1, p2) } P.Return(tuple.Tuple3[OutT1, OutT2, error]{ @@ -220,7 +220,7 @@ func Await3Compiled[OutT1, OutT2, OutT3 any]( P.Next = -1 // TODO(lijie): return every error? if !p1.Done() || !p2.Done() || !p3.Done() { - log.Fatalf("io.Await3: not done: %+v, %+v, %+v\n", p1, p2, p3) + log.Fatalf("async.Await3: not done: %+v, %+v, %+v\n", p1, p2, p3) } P.Return(tuple.Tuple3[OutT1, OutT2, OutT3]{ diff --git a/x/io/naive/naive.go b/x/async/naive/naive.go similarity index 100% rename from x/io/naive/naive.go rename to x/async/naive/naive.go From 0687efaec6f598bbab12d6865ead07ae6bfa8139 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Wed, 31 Jul 2024 20:36:17 +0800 Subject: [PATCH 10/27] ssa: add coroution intrinsics --- ssa/coro.go | 486 +++++++++++++++++++++++++++++++++++++++++++++++++ ssa/package.go | 44 +++++ ssa/type.go | 14 +- 3 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 ssa/coro.go diff --git a/ssa/coro.go b/ssa/coro.go new file mode 100644 index 00000000..d6183f93 --- /dev/null +++ b/ssa/coro.go @@ -0,0 +1,486 @@ +/* + * 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 ( + "go/token" + "go/types" +) + +// declare void @llvm.coro.destroy(i8*) +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(i8*) +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 i32 @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 i8* @llvm.coro.begin(token, i8*) +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 i8* @llvm.coro.free(token, i8*) +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, i8*, i8*, i8*) +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(i8*, 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 +} + +// ----------------------------------------------------------------------------- + +// 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) +} + +// 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 { + 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 { + 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()) + args = append([]Expr{handle, unwind}, args...) + return b.Call(fn, args...) +} + +// 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) +} + +// 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/package.go b/ssa/package.go index 382ef365..7be6d901 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -135,6 +135,8 @@ type aProgram struct { rtMapTy llvm.Type rtChanTy llvm.Type + tokenType llvm.Type + anyTy Type voidTy Type voidPtr Type @@ -165,6 +167,8 @@ type aProgram struct { deferTy Type deferPtr Type + tokenTy Type + pyImpTy *types.Signature pyNewList *types.Signature pyListSetI *types.Signature @@ -188,6 +192,39 @@ type aProgram struct { sigsetjmpTy *types.Signature sigljmpTy *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 @@ -437,6 +474,13 @@ 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 +} + /* // Eface returns the empty interface type. // It is equivalent to Any. 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 From cdfa0166bd4bea0e760b83dd6708bfcee18c6360 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 3 Aug 2024 20:51:43 +0800 Subject: [PATCH 11/27] ssa: make block with label name for debug readable --- cl/compile.go | 2 +- ssa/decl.go | 12 +++++++----- ssa/eh.go | 4 ++-- ssa/stmt_builder.go | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index cd3acbfd..cadc8a8d 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -318,7 +318,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)) diff --git a/ssa/decl.go b/ssa/decl.go index 89128409..6bdde0fd 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -293,13 +293,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 +309,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 dfbfcb89..4aff254c 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -162,7 +162,7 @@ func (b Builder) getDefer(kind DoAction) *aDefer { czero := prog.IntVal(0, prog.CInt()) retval := b.Sigsetjmp(jb, czero) if kind != DeferAlways { - rundBlk = self.MakeBlock() + rundBlk = self.MakeBlock("") } else { blks = self.MakeBlocks(2) next, rundBlk = blks[0], blks[1] @@ -228,7 +228,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.runsNext = append(self.runsNext, blk) b.Store(self.rundPtr, blk.Addr()) diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index f837c3fd..15546ccc 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -94,7 +94,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) From 806193fc6e3691717f04e8eceb22346bbca8250a Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 3 Aug 2024 20:57:38 +0800 Subject: [PATCH 12/27] ssa: set expr name for debug readable --- ssa/expr.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ssa/expr.go b/ssa/expr.go index 46dd202f..95261611 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 { From efa771f3ffd21cd165c4ffcb8ac1461f0c3cb17f Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sun, 4 Aug 2024 09:54:34 +0800 Subject: [PATCH 13/27] cl: compile async functions --- cl/_testdata/async/in.go | 37 ++ cl/_testdata/async/out.ll | 372 ++++++++++++++++ cl/async.go | 119 +++++ cl/compile.go | 26 +- cl/import.go | 11 +- cl/instr.go | 25 +- ssa/coro.go | 217 +++++++++- ssa/decl.go | 9 +- ssa/memory.go | 7 + ssa/package.go | 5 + ssa/stmt_builder.go | 10 +- x/async/_demo/asyncdemo/async.go | 623 --------------------------- x/async/_demo/asyncdemo/asyncdemo.go | 229 ++++++++++ x/async/_demo/gendemo/gendemo.go | 21 + x/async/async.go | 41 +- x/async/coro.go | 39 ++ x/async/naive/extra.go | 285 ------------ x/async/naive/naive.go | 154 ------- 18 files changed, 1127 insertions(+), 1103 deletions(-) create mode 100644 cl/_testdata/async/in.go create mode 100644 cl/_testdata/async/out.ll create mode 100644 cl/async.go delete mode 100644 x/async/_demo/asyncdemo/async.go create mode 100644 x/async/_demo/asyncdemo/asyncdemo.go create mode 100644 x/async/_demo/gendemo/gendemo.go create mode 100644 x/async/coro.go delete mode 100644 x/async/naive/extra.go delete mode 100644 x/async/naive/naive.go diff --git a/cl/_testdata/async/in.go b/cl/_testdata/async/in.go new file mode 100644 index 00000000..259b98b2 --- /dev/null +++ b/cl/_testdata/async/in.go @@ -0,0 +1,37 @@ +package async_compile + +import ( + "fmt" + + "github.com/goplus/llgo/x/async" +) + +func GenInts() (co *async.Promise[int]) { + co.Yield(1) + co.Yield(2) + co.Yield(3) + return +} + +func WrapGenInts() *async.Promise[int] { + return GenInts() +} + +func UseGenInts() int { + co := WrapGenInts() + r := 0 + for !co.Done() { + r += co.Next() + } + return r +} + +func GenIntsWithDefer() (co *async.Promise[int]) { + defer func() { + if r := recover(); r != nil { + fmt.Println("panic:", r) + } + }() + co.Yield(1) + panic("GenIntsWithDefer") +} diff --git a/cl/_testdata/async/out.ll b/cl/_testdata/async/out.ll new file mode 100644 index 00000000..3306d581 --- /dev/null +++ b/cl/_testdata/async/out.ll @@ -0,0 +1,372 @@ +; ModuleID = 'github.com/goplus/llgo/cl/_testdata/async' +source_filename = "github.com/goplus/llgo/cl/_testdata/async" + +%"github.com/goplus/llgo/internal/runtime.Defer" = type { ptr, i64, ptr, ptr } +%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } +%"github.com/goplus/llgo/internal/runtime.eface" = type { ptr, ptr } +%"github.com/goplus/llgo/internal/runtime.Slice" = type { ptr, i64, i64 } +%"github.com/goplus/llgo/internal/runtime.iface" = type { ptr, ptr } +%"github.com/goplus/llgo/x/async.Promise[int]" = type { ptr, i64 } + +@"github.com/goplus/llgo/cl/_testdata/async.init$guard" = global i1 false, align 1 +@__llgo_defer = linkonce global i32 0, align 4 +@0 = private unnamed_addr constant [16 x i8] c"GenIntsWithDefer", align 1 +@_llgo_string = linkonce global ptr null, align 8 +@1 = private unnamed_addr constant [6 x i8] c"panic:", align 1 + +define ptr @"github.com/goplus/llgo/cl/_testdata/async.GenInts"() #0 { +entry: + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + +alloc: ; preds = %entry + %frame.size = call i64 @llvm.coro.size.i64() + %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + br label %_llgo_4 + +clean: ; preds = %_llgo_7, %_llgo_6, %_llgo_5, %_llgo_4 + %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %_llgo_6, %_llgo_5, %_llgo_4, %clean + %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +_llgo_4: ; preds = %alloc, %entry + %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + store ptr %hdl, ptr %promise, align 8 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) + %2 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %2, label %suspend [ + i8 0, label %_llgo_5 + i8 1, label %clean + ] + +_llgo_5: ; preds = %_llgo_4 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 2) + %3 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %3, label %suspend [ + i8 0, label %_llgo_6 + i8 1, label %clean + ] + +_llgo_6: ; preds = %_llgo_5 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 3) + %4 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %4, label %suspend [ + i8 0, label %_llgo_7 + i8 1, label %clean + ] + +_llgo_7: ; preds = %_llgo_6 + br label %clean +} + +define ptr @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer"() #0 { +entry: + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + +alloc: ; preds = %entry + %frame.size = call i64 @llvm.coro.size.i64() + %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + br label %_llgo_4 + +clean: ; preds = %_llgo_5, %_llgo_8 + %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %_llgo_8, %clean + %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +_llgo_4: ; preds = %alloc, %entry + %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + store ptr %hdl, ptr %promise, align 8 + %2 = alloca ptr, align 8 + %3 = call ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr %2, i64 8) + %4 = load i32, ptr @__llgo_defer, align 4 + %5 = call ptr @pthread_getspecific(i32 %4) + %6 = alloca i8, i64 196, align 1 + %7 = alloca i8, i64 32, align 1 + %8 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 0 + store ptr %6, ptr %8, align 8 + %9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 + store i64 0, ptr %9, align 4 + %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 2 + store ptr %5, ptr %10, align 8 + %11 = call i32 @pthread_setspecific(i32 %4, ptr %7) + %12 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 + %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 + %14 = call i32 @sigsetjmp(ptr %6, i32 0) + %15 = icmp eq i32 %14, 0 + br i1 %15, label %_llgo_8, label %_llgo_9 + +_llgo_5: ; preds = %_llgo_8, %_llgo_7 + %16 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 0 + store ptr @0, ptr %17, align 8 + %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 1 + store i64 16, ptr %18, align 4 + %19 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %16, align 8 + %20 = load ptr, ptr @_llgo_string, align 8 + %21 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/internal/runtime.String" %19, ptr %21, align 8 + %22 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %23 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, i32 0, i32 0 + store ptr %20, ptr %23, align 8 + %24 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, i32 0, i32 1 + store ptr %21, ptr %24, align 8 + %25 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, align 8 + call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %25) + unreachable + %26 = load ptr, ptr %3, align 8 + br label %clean + +_llgo_6: ; preds = %_llgo_9 + %27 = load i64, ptr %12, align 4 + call void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() + %28 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, align 8 + %29 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %28, 2 + %30 = call i32 @pthread_setspecific(i32 %4, ptr %29) + %31 = load ptr, ptr %13, align 8 + indirectbr ptr %31, [label %_llgo_7] + +_llgo_7: ; preds = %_llgo_6 + call void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr %5) + br label %_llgo_5 + +_llgo_8: ; preds = %_llgo_4 + %32 = load ptr, ptr %3, align 8 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) + %33 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %33, label %suspend [ + i8 0, label %_llgo_5 + i8 1, label %clean + ] + +_llgo_9: ; preds = %_llgo_4 + store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %13, align 8 + br label %_llgo_6 + +_llgo_10: ; No predecessors! +} + +define void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() { +_llgo_0: + %0 = call %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/internal/runtime.Recover"() + %1 = call i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface" %0, %"github.com/goplus/llgo/internal/runtime.eface" zeroinitializer) + %2 = xor i1 %1, true + br i1 %2, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %3 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 32) + %4 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %3, i64 0 + %5 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %6 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %5, i32 0, i32 0 + store ptr @1, ptr %6, align 8 + %7 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %5, i32 0, i32 1 + store i64 6, ptr %7, align 4 + %8 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %5, align 8 + %9 = load ptr, ptr @_llgo_string, align 8 + %10 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/internal/runtime.String" %8, ptr %10, align 8 + %11 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %12 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, i32 0, i32 0 + store ptr %9, ptr %12, align 8 + %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, i32 0, i32 1 + store ptr %10, ptr %13, align 8 + %14 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, align 8 + store %"github.com/goplus/llgo/internal/runtime.eface" %14, ptr %4, align 8 + %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %3, i64 1 + store %"github.com/goplus/llgo/internal/runtime.eface" %0, ptr %15, align 8 + %16 = alloca %"github.com/goplus/llgo/internal/runtime.Slice", align 8 + %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 0 + store ptr %3, ptr %17, align 8 + %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 1 + store i64 2, ptr %18, align 4 + %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 2 + store i64 2, ptr %19, align 4 + %20 = load %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, align 8 + %21 = call { i64, %"github.com/goplus/llgo/internal/runtime.iface" } @fmt.Println(%"github.com/goplus/llgo/internal/runtime.Slice" %20) + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i64 @"github.com/goplus/llgo/cl/_testdata/async.UseGenInts"() { +_llgo_0: + %0 = call ptr @"github.com/goplus/llgo/cl/_testdata/async.WrapGenInts"() + br label %_llgo_3 + +_llgo_1: ; preds = %_llgo_3 + %1 = call i64 @"github.com/goplus/llgo/x/async.(*Promise).Next[int]"(ptr %0) + %2 = add i64 %3, %1 + br label %_llgo_3 + +_llgo_2: ; preds = %_llgo_3 + ret i64 %3 + +_llgo_3: ; preds = %_llgo_1, %_llgo_0 + %3 = phi i64 [ 0, %_llgo_0 ], [ %2, %_llgo_1 ] + %4 = call i1 @"github.com/goplus/llgo/x/async.(*Promise).Done[int]"(ptr %0) + br i1 %4, label %_llgo_2, label %_llgo_1 +} + +define ptr @"github.com/goplus/llgo/cl/_testdata/async.WrapGenInts"() #0 { +entry: + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + +alloc: ; preds = %entry + %frame.size = call i64 @llvm.coro.size.i64() + %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + br label %_llgo_4 + +clean: ; preds = %_llgo_4 + %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %clean + %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + ret ptr %promise + +_llgo_4: ; preds = %alloc, %entry + %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + store ptr %hdl, ptr %promise, align 8 + %2 = call ptr @"github.com/goplus/llgo/cl/_testdata/async.GenInts"() + br label %clean +} + +define void @"github.com/goplus/llgo/cl/_testdata/async.init"() { +_llgo_0: + %0 = load i1, ptr @"github.com/goplus/llgo/cl/_testdata/async.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"github.com/goplus/llgo/cl/_testdata/async.init$guard", align 1 + call void @fmt.init() + call void @"github.com/goplus/llgo/cl/_testdata/async.init$after"() + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) #1 + +; Function Attrs: nounwind +declare i1 @llvm.coro.alloc(token) #2 + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.size.i64() #3 + +; Function Attrs: nounwind +declare ptr @llvm.coro.begin(token, ptr writeonly) #2 + +; Function Attrs: nounwind memory(argmem: read) +declare ptr @llvm.coro.free(token, ptr nocapture readonly) #4 + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(ptr, i1, token) #2 + +declare void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr, i64) + +; Function Attrs: nounwind +declare i8 @llvm.coro.suspend(token, i1) #2 + +declare ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr, i64) + +declare ptr @pthread_getspecific(i32) + +declare i32 @pthread_setspecific(i32, ptr) + +declare i32 @sigsetjmp(ptr, i32) + +declare void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr) + +define void @"github.com/goplus/llgo/cl/_testdata/async.init$after"() { +_llgo_0: + %0 = load ptr, ptr @_llgo_string, align 8 + %1 = icmp eq ptr %0, null + br i1 %1, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %2 = call ptr @"github.com/goplus/llgo/internal/runtime.Basic"(i64 24) + store ptr %2, ptr @_llgo_string, align 8 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + %3 = load i32, ptr @__llgo_defer, align 4 + %4 = icmp eq i32 %3, 0 + br i1 %4, label %_llgo_3, label %_llgo_4 + +_llgo_3: ; preds = %_llgo_2 + %5 = call i32 @pthread_key_create(ptr @__llgo_defer, ptr null) + br label %_llgo_4 + +_llgo_4: ; preds = %_llgo_3, %_llgo_2 + ret void +} + +declare ptr @"github.com/goplus/llgo/internal/runtime.Basic"(i64) + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64) + +declare void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface") + +declare %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/internal/runtime.Recover"() + +declare i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface", %"github.com/goplus/llgo/internal/runtime.eface") + +declare { i64, %"github.com/goplus/llgo/internal/runtime.iface" } @fmt.Println(%"github.com/goplus/llgo/internal/runtime.Slice") + +define i1 @"github.com/goplus/llgo/x/async.(*Promise).Done[int]"(ptr %0) { +_llgo_0: + %1 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 0 + %2 = load ptr, ptr %1, align 8 + %3 = call i1 @llvm.coro.done(ptr %2) + %4 = zext i1 %3 to i64 + %5 = trunc i64 %4 to i8 + %6 = icmp ne i8 %5, 0 + ret i1 %6 +} + +define i64 @"github.com/goplus/llgo/x/async.(*Promise).Next[int]"(ptr %0) { +_llgo_0: + %1 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 0 + %2 = load ptr, ptr %1, align 8 + call void @llvm.coro.resume(ptr %2) + %3 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 1 + %4 = load i64, ptr %3, align 4 + ret i64 %4 +} + +declare void @fmt.init() + +; Function Attrs: nounwind memory(argmem: readwrite) +declare i1 @llvm.coro.done(ptr nocapture readonly) #5 + +declare void @llvm.coro.resume(ptr) + +declare i32 @pthread_key_create(ptr, ptr) + +attributes #0 = { "presplitcoroutine" } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) } +attributes #2 = { nounwind } +attributes #3 = { nounwind memory(none) } +attributes #4 = { nounwind memory(argmem: read) } +attributes #5 = { nounwind memory(argmem: readwrite) } diff --git a/cl/async.go b/cl/async.go new file mode 100644 index 00000000..217f80ae --- /dev/null +++ b/cl/async.go @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cl + +import ( + "go/types" + "strings" + + llssa "github.com/goplus/llgo/ssa" + "golang.org/x/tools/go/ssa" +) + +// TODO(lijie): need more generics, shouldn't limit to async.Promise +func promiseType(ty types.Type) (types.Type, bool) { + // ty is a generic type, so we need to check the package path and type name + if ptrTy, ok := ty.(*types.Pointer); ok { + ty = ptrTy.Elem() + if ty, ok := ty.(*types.Named); ok { + if ty.Obj().Pkg() == nil { + return nil, false + } + if ty.Obj().Pkg().Path() == "github.com/goplus/llgo/x/async" && ty.Obj().Name() == "Promise" { + return ty, true + } + } + } + return nil, false +} + +// check function return async.Promise[T] +// TODO(lijie): make it generic +func isAsyncFunc(sig *types.Signature) bool { + r := sig.Results() + if r.Len() != 1 { + return false + } + ty := r.At(0).Type() + _, ok := promiseType(ty) + return ok +} + +func (p *context) coAwait(b llssa.Builder, args []ssa.Value) llssa.Expr { + if !isAsyncFunc(b.Func.RawType().(*types.Signature)) { + panic("coAwait(promise *T) T: invalid context") + } + if len(args) == 1 { + // promise := p.compileValue(b, args[0]) + b.Unreachable() + // return b.CoroutineAwait(promise) + } + panic("coAwait(promise *T) T: invalid arguments") +} + +func (p *context) coSuspend(b llssa.Builder, final llssa.Expr) { + b.CoSuspend(b.AsyncToken(), final) +} + +func (p *context) coDone(b llssa.Builder, args []ssa.Value) llssa.Expr { + if len(args) != 1 { + panic("coDone(promise *T): invalid arguments") + } + hdl := p.compileValue(b, args[0]) + return b.CoDone(hdl) +} + +func (p *context) coResume(b llssa.Builder, args []ssa.Value) { + if len(args) == 1 { + hdl := p.compileValue(b, args[0]) + b.CoResume(hdl) + } +} + +func (p *context) coReturn(b llssa.Builder, args []ssa.Value) { + cargs := make([]llssa.Expr, len(args)) + for i, arg := range args { + cargs[i] = p.compileValue(b, arg) + } + b.CoReturn(cargs...) +} + +func (p *context) coYield(b llssa.Builder, fn *ssa.Function, args []ssa.Value) { + typ := fn.Signature.Recv().Type() + mthds := p.goProg.MethodSets.MethodSet(typ) + // TODO(lijie): make llgo instruction callable (e.g. llgo.yield) + var setValue *ssa.Function + for i := 0; i < mthds.Len(); i++ { + m := mthds.At(i) + if ssaMthd := p.goProg.MethodValue(m); ssaMthd != nil { + if ssaMthd.Name() == "setValue" || strings.HasPrefix(ssaMthd.Name(), "setValue[") { + setValue = ssaMthd + break + } + } + } + if setValue == nil { + panic("coYield(): not found method setValue") + } + value := p.compileValue(b, args[1]) + setValueFn, _, _ := p.funcOf(setValue) + b.CoYield(setValueFn, value) +} + +func (p *context) coRun(b llssa.Builder, args []ssa.Value) { + panic("coRun(): not implemented") +} diff --git a/cl/compile.go b/cl/compile.go index cadc8a8d..e36e838a 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -214,6 +214,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun log.Println("==> NewFunc", name, "type:", sig.Recv(), sig, "ftype:", ftype) } } + async := isAsyncFunc(f.Signature) if fn == nil { if name == "main" { argc := types.NewParam(token.NoPos, pkgTypes, "", types.Typ[types.Int32]) @@ -223,13 +224,20 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun results := types.NewTuple(ret) sig = types.NewSignatureType(nil, nil, nil, params, results, false) } - fn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), hasCtx) + fn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), hasCtx, async) } - + nBlkOff := 0 if nblk := len(f.Blocks); nblk > 0 { + if async { + nBlkOff = 4 + fn.MakeBlock("entry") + fn.MakeBlock("alloc") + fn.MakeBlock("clean") + fn.MakeBlock("suspend") + } fn.MakeBlocks(nblk) // to set fn.HasBody() = true if f.Recover != nil { // set recover block - fn.SetRecover(fn.Block(f.Recover.Index)) + fn.SetRecover(fn.Block(f.Recover.Index + nBlkOff)) } p.inits = append(p.inits, func() { p.fn = fn @@ -245,6 +253,10 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun log.Println("==> FuncBody", name) } b := fn.NewBuilder() + b.SetBlockOffset(nBlkOff) + if async { + b.BeginAsync(fn) + } p.bvals = make(map[ssa.Value]llssa.Expr) off := make([]int, len(f.Blocks)) for i, block := range f.Blocks { @@ -280,7 +292,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do var pkg = p.pkg var fn = p.fn var instrs = block.Instrs[n:] - var ret = fn.Block(block.Index) + var ret = fn.Block(block.Index + b.BlockOffset()) b.SetBlock(ret) if doModInit { if pyModInit = p.pyMod != ""; pyModInit { @@ -650,7 +662,11 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { results = make([]llssa.Expr, 1) results[0] = p.prog.IntVal(0, p.prog.CInt()) } - b.Return(results...) + if b.Async() { + b.EndAsync() + } else { + b.Return(results...) + } case *ssa.If: fn := p.fn cond := p.compileValue(b, v.Cond) diff --git a/cl/import.go b/cl/import.go index 5d9b8b25..d723f3a8 100644 --- a/cl/import.go +++ b/cl/import.go @@ -412,7 +412,16 @@ const ( llgoAtomicUMax = llgoAtomicOpBase + llssa.OpUMax llgoAtomicUMin = llgoAtomicOpBase + llssa.OpUMin - llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin) + llgoCoBase = llgoInstrBase + 0x30 + llgoCoAwait = llgoCoBase + 0 + llgoCoSuspend = llgoCoBase + 1 + llgoCoDone = llgoCoBase + 2 + llgoCoResume = llgoCoBase + 3 + llgoCoReturn = llgoCoBase + 4 + llgoCoYield = llgoCoBase + 5 + llgoCoRun = llgoCoBase + 6 + + llgoAtomicOpLast = llgoCoRun ) func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, string, int) { diff --git a/cl/instr.go b/cl/instr.go index 83837ad0..9a65ba8e 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -237,6 +237,14 @@ var llgoInstrs = map[string]int{ "atomicMin": int(llgoAtomicMin), "atomicUMax": int(llgoAtomicUMax), "atomicUMin": int(llgoAtomicUMin), + + "coAwait": int(llgoCoAwait), + "coResume": int(llgoCoResume), + "coSuspend": int(llgoCoSuspend), + "coDone": int(llgoCoDone), + "coReturn": int(llgoCoReturn), + "coYield": int(llgoCoYield), + "coRun": int(llgoCoRun), } // funcOf returns a function by name and set ftype = goFunc, cFunc, etc. @@ -265,7 +273,8 @@ func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyObj return nil, nil, ignoredFunc } sig := fn.Signature - aFn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false) + async := isAsyncFunc(sig) + aFn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false, async) } } return @@ -390,6 +399,20 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon ret = p.funcAddr(b, args) case llgoUnreachable: // func unreachable() b.Unreachable() + case llgoCoAwait: + ret = p.coAwait(b, args) + case llgoCoSuspend: + p.coSuspend(b, p.prog.BoolVal(false)) + case llgoCoDone: + return p.coDone(b, args) + case llgoCoResume: + p.coResume(b, args) + case llgoCoReturn: + p.coReturn(b, args) + case llgoCoYield: + p.coYield(b, cv, args) + case llgoCoRun: + p.coRun(b, args) default: if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast { ret = p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args) diff --git a/ssa/coro.go b/ssa/coro.go index d6183f93..e765332c 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -17,11 +17,46 @@ package ssa import ( + "fmt" + "go/constant" "go/token" "go/types" + "log" ) -// declare void @llvm.coro.destroy(i8*) +// declare void @llvm.coro.destroy(ptr ) +// declare void @llvm.coro.resume(ptr ) +// declare i1 @llvm.coro.done(ptr ) +// declare ptr @llvm.coro.promise(ptr , i32 , i1 ) +// declare i32 @llvm.coro.size.i32() +// declare i32 @llvm.coro.size.i64() +// declare i32 @llvm.coro.align.i32() +// declare i64 @llvm.coro.align.i64() +// declare ptr @llvm.coro.begin(token , ptr ) +// declare ptr @llvm.coro.free(token %id, ptr ) +// declare i1 @llvm.coro.alloc(token ) +// declare ptr @llvm.coro.noop() +// declare ptr @llvm.coro.frame() +// declare token @llvm.coro.id(i32 , ptr , ptr , ptr ) +// declare token @llvm.coro.id.async(i32 , i32 , ptr , ptr ) +// declare token @llvm.coro.id.retcon(i32 , i32 , ptr , ptr , ptr , ptr ) +// declare token @llvm.coro.id.retcon.once(i32 , i32 , ptr , ptr , ptr , ptr ) +// declare i1 @llvm.coro.end(ptr , i1 , token ) +// declare token @llvm.coro.end.results(...) +// declare i1 @llvm.coro.end.async(ptr , i1 , ...) +// declare i8 @llvm.coro.suspend(token , i1 ) +// declare token @llvm.coro.save(ptr ) +// declare {ptr, ptr, ptr} @llvm.coro.suspend.async(ptr , ptr , ... ... ) +// declare ptr @llvm.coro.prepare.async(ptr ) +// declare i1 @llvm.coro.suspend.retcon(...) +// declare void @await_suspend_function(ptr %awaiter, ptr %hdl) +// declare void @llvm.coro.await.suspend.void(ptr , ptr , ptr ) +// declare i1 @llvm.coro.await.suspend.bool(ptr , ptr , ptr ) +// declare void @llvm.coro.await.suspend.handle(ptr , ptr , ptr ) + +// ----------------------------------------------------------------------------- + +// declare void @llvm.coro.destroy(ptr ) func (p Program) tyCoDestroy() *types.Signature { if p.coDestroyTy == nil { i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) @@ -31,7 +66,7 @@ func (p Program) tyCoDestroy() *types.Signature { return p.coDestroyTy } -// declare void @llvm.coro.resume(i8*) +// declare void @llvm.coro.resume(ptr ) func (p Program) tyCoResume() *types.Signature { if p.coResumeTy == nil { i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) @@ -101,7 +136,7 @@ func (p Program) tyCoAlignI64() *types.Signature { return p.coAlignI64Ty } -// declare i8* @llvm.coro.begin(token, i8*) +// declare ptr @llvm.coro.begin(token , ptr ) func (p Program) tyCoBegin() *types.Signature { if p.coBeginTy == nil { tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) @@ -113,7 +148,7 @@ func (p Program) tyCoBegin() *types.Signature { return p.coBeginTy } -// declare i8* @llvm.coro.free(token, i8*) +// declare ptr @llvm.coro.free(token %id, ptr ) func (p Program) tyCoFree() *types.Signature { if p.coFreeTy == nil { tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) @@ -125,7 +160,7 @@ func (p Program) tyCoFree() *types.Signature { return p.coFreeTy } -// declare i1 @llvm.coro.alloc(token) +// declare i1 @llvm.coro.alloc(token ) func (p Program) tyCoAlloc() *types.Signature { if p.coAllocTy == nil { tokenParam := types.NewParam(token.NoPos, nil, "", p.Token().raw.Type) @@ -155,7 +190,7 @@ func (p Program) tyCoFrame() *types.Signature { return p.coFrameTy } -// declare token @llvm.coro.id(i32, i8*, i8*, i8*) +// declare token @llvm.coro.id(i32 , ptr , ptr , ptr ) func (p Program) tyCoID() *types.Signature { if p.coIDTy == nil { i32 := types.NewParam(token.NoPos, nil, "", p.Int32().raw.Type) @@ -207,7 +242,7 @@ func (p Program) tyCoIDRetconOnce() *types.Signature { return p.coIDRetconOnceTy } -// declare i1 @llvm.coro.end(i8*, i1, token) +// declare i1 @llvm.coro.end(ptr , i1 , token ) func (p Program) tyCoEnd() *types.Signature { if p.coEndTy == nil { i8Ptr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) @@ -322,6 +357,112 @@ func (p Program) tyCoAwaitSuspendHandle() *types.Signature { // ----------------------------------------------------------------------------- +func (b Builder) SetBlockOffset(offset int) { + b.blkOffset = offset +} + +func (b Builder) BlockOffset() int { + return b.blkOffset +} + +func (b Builder) Async() bool { + return b.async +} + +func (b Builder) AsyncToken() Expr { + return b.asyncToken +} + +func (b Builder) SetAsyncToken(token Expr) { + b.asyncToken = token +} + +func (b Builder) EndAsync() { + _, _, cleanBlk := b.onSuspBlk(b.blk) + b.Jump(cleanBlk) +} + +func promiseImplType(ty types.Type) types.Type { + ty = ty.Underlying().(*types.Struct).Field(0).Type() + if ptrTy, ok := ty.(*types.Pointer); ok { + return ptrTy.Elem() + } + panic(fmt.Sprintf("unexpected promise impl type: %v", ty)) +} + +/* +id := @llvm.coro.id(0, null, null, null) +frameSize := @llvm.coro.size.i64() +needAlloc := @llvm.coro.alloc(id) +; Allocate memory for return type and coroutine frame +frame := null + + if needAlloc { + frame := malloc(frameSize) + } + +hdl := @llvm.coro.begin(id, frame) +*retPtr = hdl +*/ +func (b Builder) BeginAsync(fn Function) { + ty := fn.Type.RawType().(*types.Signature).Results().At(0).Type() + ptrTy, ok := ty.(*types.Pointer) + if !ok { + panic("async function must return a *async.Promise") + } + promiseTy := b.Prog.Type(ptrTy.Elem(), InGo) + + b.async = true + entryBlk := fn.Block(0) + allocBlk := fn.Block(1) + cleanBlk := fn.Block(2) + suspdBlk := fn.Block(3) + beginBlk := fn.Block(4) + + b.SetBlock(entryBlk) + promiseSize := b.Const(constant.MakeUint64(b.Prog.SizeOf(promiseTy)), b.Prog.Int64()).SetName("promise.size") + promise := b.AllocZ(promiseSize).SetName("promise") + promise.Type = b.Prog.Pointer(promiseTy) + b.promise = promise + log.Printf("promise ptr: %v", promise.RawType()) + align := b.Const(constant.MakeInt64(0), b.Prog.CInt()).SetName("align") + null := b.Const(nil, b.Prog.CIntPtr()) + id := b.CoID(align, null, null, null).SetName("id") + b.asyncToken = id + needAlloc := b.CoAlloc(id).SetName("need.dyn.alloc") + b.If(needAlloc, allocBlk, beginBlk) + b.SetBlock(allocBlk) + frameSize := b.CoSizeI64().SetName("frame.size") + frame := b.AllocZ(frameSize).SetName("frame") + b.Jump(beginBlk) + b.SetBlock(beginBlk) + phi := b.Phi(b.Prog.VoidPtr()) + phi.SetName("frame") + phi.AddIncoming(b, []BasicBlock{entryBlk, allocBlk}, func(i int, blk BasicBlock) Expr { + if i == 0 { + return null + } + return frame + }) + hdl := b.CoBegin(id, phi.Expr) + hdl.SetName("hdl") + b.Store(promise, hdl) + + b.SetBlock(cleanBlk) + b.CoFree(id, hdl) + b.Jump(suspdBlk) + + b.SetBlock(suspdBlk) + b.CoEnd(hdl, b.Prog.BoolVal(false), b.Prog.TokenNone()) + b.Return(promise) + + b.onSuspBlk = func(nextBlk BasicBlock) (BasicBlock, BasicBlock, BasicBlock) { + return suspdBlk, nextBlk, cleanBlk + } +} + +// ----------------------------------------------------------------------------- + // declare void @llvm.coro.destroy(ptr ) func (b Builder) CoDestroy(hdl Expr) { fn := b.Pkg.cFunc("llvm.coro.destroy", b.Prog.tyCoDestroy()) @@ -335,11 +476,20 @@ func (b Builder) CoResume(hdl Expr) { } // declare i1 @llvm.coro.done(ptr ) -func (b Builder) CoDone(hdl Expr) Expr { +func (b Builder) coDone(hdl Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.done", b.Prog.tyCoDone()) return b.Call(fn, hdl) } +// return c.Char +func (b Builder) CoDone(hdl Expr) Expr { + bvar := b.coDone(hdl) + // TODO(lijie): inefficient + // %6 = zext i1 %5 to i64 + // %7 = trunc i64 %6 to i8 + return b.valFromData(b.Prog.Byte(), bvar.impl) +} + // declare ptr @llvm.coro.promise(ptr , i32 , i1 ) func (b Builder) CoPromise(ptr, align, from Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.promise", b.Prog.tyCoPromise()) @@ -402,12 +552,21 @@ func (b Builder) CoFrame() Expr { // declare token @llvm.coro.id(i32 , ptr , ptr , ptr ) func (b Builder) CoID(align Expr, promise, coroAddr, fnAddrs Expr) Expr { + if align.Type != b.Prog.Int32() { + panic("align must be i32") + } fn := b.Pkg.cFunc("llvm.coro.id", b.Prog.tyCoID()) return b.Call(fn, align, promise, coroAddr, fnAddrs) } // declare token @llvm.coro.id.async(i32 , i32 , ptr , ptr ) func (b Builder) CoIDAsync(contextSize, align, contextArg, asyncFnPtr Expr) Expr { + if contextSize.Type != b.Prog.Int32() { + panic("contextSize must be i32") + } + if align.Type != b.Prog.Int32() { + panic("align must be i32") + } fn := b.Pkg.cFunc("llvm.coro.id.async", b.Prog.tyCoIDAsync()) return b.Call(fn, contextSize, align, contextArg, asyncFnPtr) } @@ -439,16 +598,44 @@ func (b Builder) CoEndResults(args []Expr) Expr { // declare i1 @llvm.coro.end.async(ptr , i1 , ...) func (b Builder) CoEndAsync(handle, unwind Expr, args ...Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.end.async", b.Prog.tyCoEndAsync()) - args = append([]Expr{handle, unwind}, args...) - return b.Call(fn, args...) + vargs := append([]Expr{handle, unwind}, args...) + return b.Call(fn, vargs...) } // declare i8 @llvm.coro.suspend(token , i1 ) -func (b Builder) CoSuspend(save, final Expr) Expr { +func (b Builder) coSuspend(save, final Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.suspend", b.Prog.tyCoSuspend()) return b.Call(fn, save, final) } +func (b Builder) CoSuspend(save, final Expr) { + if !b.async { + panic(fmt.Errorf("suspend %v not in async block", b.Func.Name())) + } + ret := b.coSuspend(save, final) + // add resume block + b.Func.MakeBlock("") + nextBlk := b.Func.Block(b.blk.idx + 1) + susp, next, clean := b.onSuspBlk(nextBlk) + swt := b.Switch(ret, susp) + swt.Case(b.Const(constant.MakeInt64(0), b.Prog.Byte()), next) + swt.Case(b.Const(constant.MakeInt64(1), b.Prog.Byte()), clean) + swt.End(b) + b.SetBlock(nextBlk) +} + +func (b Builder) CoReturn(args ...Expr) { + if !b.async { + panic(fmt.Errorf("return %v not in async block", b.Func.Name())) + } + + b.Func.MakeBlock("") + nextBlk := b.Func.Block(b.blk.idx + 1) + _, _, cleanBlk := b.onSuspBlk(nextBlk) + b.Jump(cleanBlk) + b.SetBlock(nextBlk) +} + // declare token @llvm.coro.save(ptr ) func (b Builder) CoSave(hdl Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.save", b.Prog.tyCoSave()) @@ -484,3 +671,11 @@ func (b Builder) CoAwaitSuspendHandle(awaiter, handle, f Expr) { fn := b.Pkg.cFunc("llvm.coro.await.suspend.handle", b.Prog.tyCoAwaitSuspendHandle()) b.Call(fn, awaiter, handle, f) } + +func (b Builder) CoYield(setValueFn Function, value Expr) { + if !b.async { + panic(fmt.Errorf("yield %v not in async block", b.Func.Name())) + } + b.Call(setValueFn.Expr, b.promise, value) + b.CoSuspend(b.AsyncToken(), b.Prog.BoolVal(false)) +} diff --git a/ssa/decl.go b/ssa/decl.go index 6bdde0fd..ceae3e72 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -180,11 +180,11 @@ type Function = *aFunction // NewFunc creates a new function. func (p Package) NewFunc(name string, sig *types.Signature, bg Background) Function { - return p.NewFuncEx(name, sig, bg, false) + return p.NewFuncEx(name, sig, bg, false, false) } // NewFuncEx creates a new function. -func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, hasFreeVars bool) Function { +func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, hasFreeVars, async bool) Function { if v, ok := p.fns[name]; ok { return v } @@ -193,6 +193,9 @@ func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, has log.Println("NewFunc", name, t.raw.Type, "hasFreeVars:", hasFreeVars) } fn := llvm.AddFunction(p.mod, name, t.ll) + if async { + fn.AddFunctionAttr(p.Prog.ctx.CreateStringAttribute("presplitcoroutine", "")) + } ret := newFunction(fn, t, p, p.Prog, hasFreeVars) p.fns[name] = ret return ret @@ -268,7 +271,7 @@ func (p Function) NewBuilder() Builder { b := prog.ctx.NewBuilder() // TODO(xsw): Finalize may cause panic, so comment it. // b.Finalize() - return &aBuilder{b, nil, p, p.Pkg, prog} + return &aBuilder{impl: b, blk: nil, Func: p, Pkg: p.Pkg, Prog: prog} } // HasBody reports whether the function has a body. diff --git a/ssa/memory.go b/ssa/memory.go index d14fd71b..3f49be6c 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -232,6 +232,13 @@ func (b Builder) ArrayAlloca(telem Type, n Expr) (ret Expr) { return } +func (b Builder) OffsetPtr(ptr, offset Expr) Expr { + if debugInstr { + log.Printf("OffsetPtr %v, %v\n", ptr.impl, offset.impl) + } + return Expr{llvm.CreateGEP(b.impl, ptr.Type.ll, ptr.impl, []llvm.Value{offset.impl}), ptr.Type} +} + /* TODO(xsw): // ArrayAlloc allocates zero initialized space for an array of n elements of type telem. func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) { diff --git a/ssa/package.go b/ssa/package.go index 7be6d901..9ce5f227 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -481,6 +481,11 @@ func (p Program) Token() Type { return p.tokenTy } +func (p Program) TokenNone() Expr { + impl := llvm.ConstNull(p.Token().ll) + return Expr{impl: impl, Type: p.Token()} +} + /* // Eface returns the empty interface type. // It is equivalent to Any. diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index 15546ccc..c2b7cff4 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -63,6 +63,12 @@ type aBuilder struct { Func Function Pkg Package Prog Program + + async bool + asyncToken Expr + promise Expr + onSuspBlk func(blk BasicBlock) (susp BasicBlock, next BasicBlock, clean BasicBlock) + blkOffset int } // Builder represents a builder for creating instructions in a function. @@ -288,7 +294,7 @@ func (b Builder) Times(n Expr, loop func(i Expr)) { } // ----------------------------------------------------------------------------- -/* + type caseStmt struct { v llvm.Value blk llvm.BasicBlock @@ -326,7 +332,7 @@ func (b Builder) Switch(v Expr, defb BasicBlock) Switch { } return &aSwitch{v.impl, defb.first, nil} } -*/ + // ----------------------------------------------------------------------------- // Phi represents a phi node. diff --git a/x/async/_demo/asyncdemo/async.go b/x/async/_demo/asyncdemo/async.go deleted file mode 100644 index a0d49fd9..00000000 --- a/x/async/_demo/asyncdemo/async.go +++ /dev/null @@ -1,623 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "strings" - "time" - - "github.com/goplus/llgo/x/async" - "github.com/goplus/llgo/x/async/naive" - "github.com/goplus/llgo/x/tuple" -) - -// ----------------------------------------------------------------------------- - -func http(method string, url string, callback func(resp *Response, err error)) { - go func() { - body := "" - if strings.HasPrefix(url, "http://example.com/user/") { - name := url[len("http://example.com/user/"):] - body = `{"name":"` + name + `"}` - } else if strings.HasPrefix(url, "http://example.com/score/") { - body = "99.5" - } - time.Sleep(200 * time.Millisecond) - resp := &Response{StatusCode: 200, Body: body} - callback(resp, nil) - }() -} - -// ----------------------------------------------------------------------------- - -type Response struct { - StatusCode int - - Body string -} - -func (r *Response) Text() (co *async.Promise[tuple.Tuple2[string, error]]) { - co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) - return -} - -func (r *Response) TextCompiled() *naive.PromiseImpl[tuple.Tuple2[string, error]] { - co := &naive.PromiseImpl[tuple.Tuple2[string, error]]{} - co.Debug = "Text" - co.Func = func() { - switch co.Next { - case 0: - co.Next = -1 - co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) - return - default: - panic("Promise already done") - } - } - return co -} - -// async AsyncHttpGet(url string) (resp *Response, err error) { -// http("GET", url, func(resp *Response, err error) { -// return resp, err -// }) -// } -func AsyncHttpGet(url string) *async.Promise[tuple.Tuple2[*Response, error]] { - co := &async.Promise[tuple.Tuple2[*Response, error]]{} - http("GET", url, func(resp *Response, err error) { - co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) - }) - co.Suspend() - return co -} - -func AsyncHttpGetCompiled(url string) *naive.PromiseImpl[tuple.Tuple2[*Response, error]] { - co := &naive.PromiseImpl[tuple.Tuple2[*Response, error]]{} - co.Debug = "HttpGet" - co.Func = func() { - switch co.Next { - case 0: - co.Next = -1 - http("GET", url, func(resp *Response, err error) { - co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) - }) - co.Suspend() - return - default: - panic("Promise already done") - } - } - return co -} - -func AsyncHttpPost(url string) *async.Promise[tuple.Tuple2[*Response, error]] { - co := &async.Promise[tuple.Tuple2[*Response, error]]{} - http("POST", url, func(resp *Response, err error) { - co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) - }) - co.Suspend() - return co -} - -func AsyncHttpPostCompiled(url string) *naive.PromiseImpl[tuple.Tuple2[*Response, error]] { - P := &naive.PromiseImpl[tuple.Tuple2[*Response, error]]{} - P.Debug = "HttpPost" - P.Func = func() { - switch P.Next { - case 0: - P.Next = -1 - http("POST", url, func(resp *Response, err error) { - P.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) - }) - return - default: - panic("Promise already done") - } - } - return P -} - -// ----------------------------------------------------------------------------- - -type User struct { - Name string -} - -func GetUser(name string) (co *naive.PromiseImpl[tuple.Tuple2[User, error]]) { - resp, err := AsyncHttpGet("http://example.com/user/" + name).Await().Values() - if err != nil { - // return User{}, err - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - - if resp.StatusCode != 200 { - // return User{}, fmt.Errorf("http status code: %d", resp.StatusCode) - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) - return - } - - body, err := resp.Text().Await().Values() - if err != nil { - // return User{}, err - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - user := User{} - if err := json.Unmarshal([]byte(body), &user); err != nil { - // return User{}, err - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - - // return user, nil - co.Return(tuple.Tuple2[User, error]{V1: user, V2: nil}) - return -} - -func GetUserCompiled(name string) (co *naive.PromiseImpl[tuple.Tuple2[User, error]]) { - var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] - var state2 *naive.PromiseImpl[tuple.Tuple2[string, error]] - - co = &naive.PromiseImpl[tuple.Tuple2[User, error]]{} - co.Debug = "GetUser" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - state1 = AsyncHttpGetCompiled("http://example.com/user/" + name) - state1.Exec = co.Exec - state1.Parent = co - state1.Call() - return - case 1: - co.Next = 2 - resp, err := state1.Value().Values() - log.Printf("resp: %v, err: %v\n", resp, err) - if err != nil { - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - - if resp.StatusCode != 200 { - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) - return - } - - state2 = resp.TextCompiled() - state2.Exec = co.Exec - state2.Parent = co - state2.Call() - return - case 2: - co.Next = -1 - body, err := state2.Value().Values() - if err != nil { - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - user := User{} - log.Printf("body: %v\n", body) - if err := json.Unmarshal([]byte(body), &user); err != nil { - co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) - return - } - - log.Printf("resolve user: %+v\n", user) - co.Return(tuple.Tuple2[User, error]{V1: user, V2: nil}) - return - default: - panic(fmt.Errorf("Promise already done, %+v", co)) - } - } - return -} - -func GetScore() (co *naive.PromiseImpl[tuple.Tuple2[float64, error]]) { - resp, err := AsyncHttpGet("http://example.com/score/").Await().Values() - if err != nil { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - if resp.StatusCode != 200 { - // return 0, fmt.Errorf("http status code: %d", resp.StatusCode) - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) - return - } - - body, err := resp.Text().Await().Values() - if err != nil { - // return 0, err - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - score := 0.0 - if _, err := fmt.Sscanf(body, "%f", &score); err != nil { - // return 0, err - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - // return score, nil - co.Return(tuple.Tuple2[float64, error]{V1: score, V2: nil}) - return -} - -func GetScoreCompiled() *naive.PromiseImpl[tuple.Tuple2[float64, error]] { - var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] - var state2 *naive.PromiseImpl[tuple.Tuple2[string, error]] - - co := &naive.PromiseImpl[tuple.Tuple2[float64, error]]{} - co.Debug = "GetScore" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - state1 = AsyncHttpGetCompiled("http://example.com/score/") - state1.Exec = co.Exec - state1.Parent = co - state1.Call() - return - case 1: - co.Next = 2 - - resp, err := state1.Value().Values() - if err != nil { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - if resp.StatusCode != 200 { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) - return - } - - state2 = resp.TextCompiled() - state2.Exec = co.Exec - state2.Parent = co - state2.Call() - - return - case 2: - co.Next = -1 - body, err := state2.Value().Values() - if err != nil { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - - score := 0.0 - if _, err := fmt.Sscanf(body, "%f", &score); err != nil { - co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) - return - } - co.Return(tuple.Tuple2[float64, error]{V1: score, V2: nil}) - return - default: - panic("Promise already done") - } - } - return co -} - -func DoUpdate(op string) (co *naive.PromiseImpl[error]) { - resp, err := AsyncHttpPost("http://example.com/update/" + op).Await().Values() - if err != nil { - co.Return(err) - return - } - - if resp.StatusCode != 200 { - co.Return(fmt.Errorf("http status code: %d", resp.StatusCode)) - } - - co.Return(nil) - return -} - -func DoUpdateCompiled(op string) *naive.PromiseImpl[error] { - var state1 *naive.PromiseImpl[tuple.Tuple2[*Response, error]] - - co := &naive.PromiseImpl[error]{} - co.Debug = "DoUpdate" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - state1 = AsyncHttpPostCompiled("http://example.com/update/" + op) - state1.Exec = co.Exec - state1.Parent = co - state1.Call() - return - case 1: - co.Next = -1 - resp, err := state1.Value().Values() - if err != nil { - co.Return(err) - return - } - - if resp.StatusCode != 200 { - co.Return(fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - co.Return(nil) - return - default: - panic("Promise already done") - } - } - return co -} - -func GenInts() (co *naive.PromiseImpl[int]) { - co.Yield(3) - co.Yield(2) - co.Yield(5) - return -} - -func GenIntsCompiled() *naive.PromiseImpl[int] { - co := &naive.PromiseImpl[int]{} - co.Debug = "GenInts" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - co.Yield(3) - return - case 1: - co.Next = 2 - co.Yield(2) - return - case 2: - co.Next = 3 - co.Yield(5) - return - case 3: - co.Next = -1 - default: - panic("Generator already done") - } - } - return co -} - -// Generator with async calls and panic -func GenUsers() (co *naive.PromiseImpl[User]) { - u, err := GetUser("Alice").Await().Values() - if err != nil { - panic(err) - } - co.Yield(u) - u, err = GetUser("Bob").Await().Values() - if err != nil { - panic(err) - } - co.Yield(u) - u, err = GetUser("Cindy").Await().Values() - if err != nil { - panic(err) - } - co.Yield(u) - log.Printf("genUsers done\n") - return -} - -func GenUsersCompiled() (resolve *naive.PromiseImpl[User]) { - var state1, state2, state3 *naive.PromiseImpl[tuple.Tuple2[User, error]] - - co := &naive.PromiseImpl[User]{} - co.Debug = "GenUsers" - co.Func = func() { - switch co.Next { - case 0: - co.Next = 1 - state1 = GetUserCompiled("Alice") - state1.Exec = co.Exec - state1.Parent = co - state1.Call() - return - case 1: - co.Next = 2 - u, err := state1.Value().Values() - if err != nil { - panic(err) - } else { - co.Yield(u) - } - return - case 2: - co.Next = 3 - state2 = GetUserCompiled("Bob") - state2.Exec = co.Exec - state2.Parent = co - state2.Call() - return - case 3: - co.Next = 4 - u, err := state2.Value().Values() - if err != nil { - panic(err) - } else { - co.Yield(u) - } - return - case 4: - co.Next = 5 - state3 = GetUserCompiled("Cindy") - state3.Exec = co.Exec - state3.Parent = co - state3.Call() - return - case 5: - co.Next = 6 - u, err := state3.Value().Values() - if err != nil { - panic(err) - } else { - co.Yield(u) - } - return - case 6: - co.Next = -1 - default: - panic("Generator already done") - } - } - return co -} - -func Demo() (co *async.Promise[async.Void]) { - user, err := GetUser("1").Await().Values() - log.Println(user, err) - - user, err = naive.Race[tuple.Tuple2[User, error]](GetUser("2"), GetUser("3"), GetUser("4")).Value().Values() - log.Println(user, err) - - users := naive.All[tuple.Tuple2[User, error]]([]naive.AsyncCall[tuple.Tuple2[User, error]]{GetUser("5"), GetUser("6"), GetUser("7")}).Value() - log.Println(users, err) - - user, score, _ := naive.Await3Compiled[User, float64, async.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")).Value().Values() - log.Println(user, score, err) - - // for loop with generator - g := GenInts() - for { - g.Call() - if g.Done() { - break - } - log.Println("genInt:", g.Value(), g.Done()) - } - - // for loop with async generator - // for u, err := range GenUsers() {...} - g1 := GenUsers() - for { - g1.Call() - u := g1.Await() - if g1.Done() { - break - } - log.Println("genUser:", u, err) - } - - // TODO(lijie): select from multiple promises without channel - // select { - // case user := <-GetUser("123").Chan(): - // log.Println("user:", user) - // case score := <-GetScore().Chan(): - // log.Println("score:", score) - // case <-async.Timeout(5 * time.Second).Chan(): - // log.Println("timeout") - // } - - log.Println("Demo done") - co.Return(async.Void{}) - return -} - -func DemoCompiled() *naive.PromiseImpl[async.Void] { - var state1 *naive.PromiseImpl[tuple.Tuple2[User, error]] - var state2 *naive.PromiseImpl[tuple.Tuple2[User, error]] - var state3 *naive.PromiseImpl[[]tuple.Tuple2[User, error]] - var state4 *naive.PromiseImpl[tuple.Tuple3[tuple.Tuple2[User, error], tuple.Tuple2[float64, error], error]] - var g1 *naive.PromiseImpl[int] - var g2 *naive.PromiseImpl[User] - - P := &naive.PromiseImpl[async.Void]{} - P.Debug = "Demo" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - state1 = GetUserCompiled("1") - state1.Exec = P.Exec - state1.Parent = P - state1.Call() - return - case 1: - P.Next = 2 - user, err := state1.Value().Values() - log.Printf("user: %+v, err: %v\n", user, err) - - state2 = naive.Race[tuple.Tuple2[User, error]](GetUserCompiled("2"), GetUserCompiled("3"), GetUserCompiled("4")) - state2.Exec = P.Exec - state2.Parent = P - state2.Call() - return - case 2: - P.Next = 3 - user, err := state2.Value().Values() - log.Printf("race user: %+v, err: %v\n", user, err) - - state3 = naive.All[tuple.Tuple2[User, error]]([]naive.AsyncCall[tuple.Tuple2[User, error]]{GetUserCompiled("5"), GetUserCompiled("6"), GetUserCompiled("7")}) - state3.Exec = P.Exec - state3.Parent = P - state3.Call() - return - case 3: - - P.Next = 4 - users := state3.Value() - log.Println(users) - - state4 = naive.Await3Compiled[tuple.Tuple2[User, error], tuple.Tuple2[float64, error], error](GetUserCompiled("8"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) - state4.Exec = P.Exec - state4.Parent = P - state4.Call() - return - case 4: - P.Next = 5 - user, score, _ := state4.Value().Values() - log.Println(user, score) - - g1 = GenIntsCompiled() - for { - g1.Call() - if g1.Done() { - break - } - - log.Printf("genInt: %+v, done: %v\n", g1.Value(), g1.Done()) - } - - g2 = GenUsersCompiled() - g2.Exec = P.Exec - g2.Parent = P - g2.Call() - return - case 5: - g2.Call() - if g2.Done() { - P.Next = -1 - log.Printf("Demo done\n") - P.Return(async.Void{}) - return - } - log.Printf("genUser: %+v, done: %v\n", g2.Value(), g2.Done()) - return - default: - panic("Promise already done") - } - } - return P -} - -func main() { - log.SetFlags(log.Lshortfile | log.LstdFlags) - log.Printf("=========== Run Naive Demo ===========\n") - v := naive.RunImpl[async.Void](DemoCompiled()) - log.Println(v) - log.Printf("=========== Run Naive Demo finished ===========\n") - - log.Printf("=========== Run Demo ===========\n") - v1 := Demo() - log.Println(v1) - log.Printf("=========== Run Demo finished ===========\n") -} diff --git a/x/async/_demo/asyncdemo/asyncdemo.go b/x/async/_demo/asyncdemo/asyncdemo.go new file mode 100644 index 00000000..45e0f959 --- /dev/null +++ b/x/async/_demo/asyncdemo/asyncdemo.go @@ -0,0 +1,229 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "strings" + "time" + + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/tuple" +) + +// ----------------------------------------------------------------------------- + +func http(method string, url string, callback func(resp *Response, err error)) { + go func() { + body := "" + if strings.HasPrefix(url, "http://example.com/user/") { + name := url[len("http://example.com/user/"):] + body = `{"name":"` + name + `"}` + } else if strings.HasPrefix(url, "http://example.com/score/") { + body = "99.5" + } + time.Sleep(200 * time.Millisecond) + resp := &Response{StatusCode: 200, Body: body} + callback(resp, nil) + }() +} + +// ----------------------------------------------------------------------------- + +type Response struct { + StatusCode int + + Body string +} + +func (r *Response) Text() (co async.Promise[tuple.Tuple2[string, error]]) { + co.Return(tuple.Tuple2[string, error]{V1: r.Body, V2: nil}) + return +} + +// async AsyncHttpGet(url string) (resp *Response, err error) { +// http("GET", url, func(resp *Response, err error) { +// return resp, err +// }) +// } +func AsyncHttpGet(url string) (co async.Promise[tuple.Tuple2[*Response, error]]) { + return co.Async(func(resolve func(tuple.Tuple2[*Response, error])) { + http("GET", url, func(resp *Response, err error) { + resolve(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) + }) + }) +} + +func AsyncHttpPost(url string) (co async.Promise[tuple.Tuple2[*Response, error]]) { + http("POST", url, func(resp *Response, err error) { + // co.Return(tuple.Tuple2[*Response, error]{V1: resp, V2: nil}) + }) + co.Suspend() + return +} + +// ----------------------------------------------------------------------------- + +type User struct { + Name string +} + +func GetUser(name string) (co async.Promise[tuple.Tuple2[User, error]]) { + resp, err := AsyncHttpGet("http://example.com/user/" + name).Await().Values() + if err != nil { + // return User{}, err + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) + return + } + + if resp.StatusCode != 200 { + // return User{}, fmt.Errorf("http status code: %d", resp.StatusCode) + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) + return + } + + body, err := resp.Text().Await().Values() + if err != nil { + // return User{}, err + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) + return + } + user := User{} + if err := json.Unmarshal([]byte(body), &user); err != nil { + // return User{}, err + co.Return(tuple.Tuple2[User, error]{V1: User{}, V2: err}) + return + } + + // return user, nil + co.Return(tuple.Tuple2[User, error]{V1: user, V2: nil}) + return +} + +func GetScore() (co *async.Promise[tuple.Tuple2[float64, error]]) { + resp, err := AsyncHttpGet("http://example.com/score/").Await().Values() + if err != nil { + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) + return + } + + if resp.StatusCode != 200 { + // return 0, fmt.Errorf("http status code: %d", resp.StatusCode) + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: fmt.Errorf("http status code: %d", resp.StatusCode)}) + return + } + + body, err := resp.Text().Await().Values() + if err != nil { + // return 0, err + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) + return + } + + score := 0.0 + if _, err := fmt.Sscanf(body, "%f", &score); err != nil { + // return 0, err + co.Return(tuple.Tuple2[float64, error]{V1: 0, V2: err}) + return + } + + // return score, nil + co.Return(tuple.Tuple2[float64, error]{V1: score, V2: nil}) + return +} + +func DoUpdate(op string) (co *async.Promise[error]) { + resp, err := AsyncHttpPost("http://example.com/update/" + op).Await().Values() + if err != nil { + co.Return(err) + return + } + + if resp.StatusCode != 200 { + co.Return(fmt.Errorf("http status code: %d", resp.StatusCode)) + } + + co.Return(nil) + return +} + +func GenInts() (co *async.Promise[int]) { + co.Yield(3) + co.Yield(2) + co.Yield(5) + return +} + +// Generator with async calls and panic +func GenUsers() (co *async.Promise[User]) { + u, err := GetUser("Alice").Await().Values() + if err != nil { + panic(err) + } + co.Yield(u) + u, err = GetUser("Bob").Await().Values() + if err != nil { + panic(err) + } + co.Yield(u) + u, err = GetUser("Cindy").Await().Values() + if err != nil { + panic(err) + } + co.Yield(u) + log.Printf("genUsers done\n") + return +} + +func Demo() (co *async.Promise[async.Void]) { + user, err := GetUser("1").Await().Values() + log.Println(user, err) + + // user, err = naive.Race[tuple.Tuple2[User, error]](GetUser("2"), GetUser("3"), GetUser("4")).Value().Values() + // log.Println(user, err) + + // users := naive.All[tuple.Tuple2[User, error]]([]naive.AsyncCall[tuple.Tuple2[User, error]]{GetUser("5"), GetUser("6"), GetUser("7")}).Value() + // log.Println(users, err) + + // user, score, _ := naive.Await3Compiled[User, float64, async.Void](GetUser("8"), GetScore(), DoUpdate("update sth.")).Value().Values() + // log.Println(user, score, err) + + // for loop with generator + g := GenInts() + for !g.Done() { + log.Println("genInt:", g.Value(), g.Done()) + g.Resume() + } + + // for loop with async generator + // for u, err := range GenUsers() {...} + g1 := GenUsers() + for !g1.Done() { + u := g1.Value() + log.Println("genUser:", u) + g1.Resume() + } + + // TODO(lijie): select from multiple promises without channel + // select { + // case user := <-GetUser("123").Chan(): + // log.Println("user:", user) + // case score := <-GetScore().Chan(): + // log.Println("score:", score) + // case <-async.Timeout(5 * time.Second).Chan(): + // log.Println("timeout") + // } + + log.Println("Demo done") + co.Return(async.Void{}) + return +} + +func main() { + log.SetFlags(log.Lshortfile | log.LstdFlags) + + log.Printf("=========== Run Demo ===========\n") + v1 := Demo() + log.Println(v1) + log.Printf("=========== Run Demo finished ===========\n") +} diff --git a/x/async/_demo/gendemo/gendemo.go b/x/async/_demo/gendemo/gendemo.go new file mode 100644 index 00000000..b58b3e27 --- /dev/null +++ b/x/async/_demo/gendemo/gendemo.go @@ -0,0 +1,21 @@ +package main + +import "github.com/goplus/llgo/x/async" + +func GenInts() (co *async.Promise[int]) { + print("1") + co.Yield(1) + print("2") + co.Yield(2) + print("3") + co.Yield(3) + print("4") + return +} + +func main() { + co := GenInts() + for !co.Done() { + print(co.Next()) + } +} diff --git a/x/async/async.go b/x/async/async.go index 83b4af40..0060a959 100644 --- a/x/async/async.go +++ b/x/async/async.go @@ -25,12 +25,8 @@ const ( LLGoPackage = "decl" ) -const debugAsync = false - type Void = [0]byte -type AsyncCall[TOut any] interface{} - // ----------------------------------------------------------------------------- type Promise[TOut any] struct { @@ -38,36 +34,45 @@ type Promise[TOut any] struct { value TOut } -// llgo:link PromiseImpl llgo.coAwait +// // llgo:link (*Promise).Await llgo.coAwait func (p *Promise[TOut]) Await() TOut { panic("should not executed") } -// llgo:link Return llgo.coReturn func (p *Promise[TOut]) Return(v TOut) { - panic("should not executed") + p.value = v + coReturn(p.hdl) } -// llgo:link Yield llgo.coYield -func (p *Promise[TOut]) Yield(v TOut) { - panic("should not executed") -} +// llgo:link (*Promise).Yield llgo.coYield +func (p *Promise[TOut]) Yield(v TOut) {} -// llgo:link Suspend llgo.coSuspend -func (p *Promise[TOut]) Suspend() { - panic("should not executed") -} +// llgo:link (*Promise).Suspend llgo.coSuspend +func (p *Promise[TOut]) Suspend() {} -// llgo:link Resume llgo.coResume func (p *Promise[TOut]) Resume() { - panic("should not executed") + coResume(p.hdl) +} + +func (p *Promise[TOut]) Next() TOut { + coResume(p.hdl) + return p.value +} + +// TODO(lijie): should merge to Yield() +// call by llgo.coYield +func (p *Promise[TOut]) setValue(v TOut) { + p.value = v } func (p *Promise[TOut]) Value() TOut { return p.value } -// llgo:link Run llgo.coRun +func (p *Promise[TOut]) Done() bool { + return coDone(p.hdl) != 0 +} + func Run[TOut any](f func() TOut) TOut { panic("should not executed") } diff --git a/x/async/coro.go b/x/async/coro.go new file mode 100644 index 00000000..62393d4e --- /dev/null +++ b/x/async/coro.go @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package async + +import ( + "unsafe" + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +// llgo:link coDone llgo.coDone +func coDone(hdl unsafe.Pointer) c.Char { + panic("should not executed") +} + +// llgo:link coResume llgo.coResume +func coResume(hdl unsafe.Pointer) { + panic("should not executed") +} + +// llgo:link coReturn llgo.coReturn +func coReturn(hdl unsafe.Pointer) { + panic("should not executed") +} diff --git a/x/async/naive/extra.go b/x/async/naive/extra.go deleted file mode 100644 index 21c00af3..00000000 --- a/x/async/naive/extra.go +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package naive - -import ( - "log" - "sync" - "time" - _ "unsafe" - - "github.com/goplus/llgo/x/async" - "github.com/goplus/llgo/x/tuple" -) - -// ----------------------------------------------------------------------------- - -func TimeoutCompiled(d time.Duration) *PromiseImpl[async.Void] { - P := &PromiseImpl[async.Void]{} - P.Debug = "Timeout" - P.Func = func() { - go func() { - time.Sleep(d) - P.Return(async.Void{}) - }() - } - return P -} - -type Result[T any] struct { - V T - Err error -} - -func Race[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[OutT] { - if len(acs) == 0 { - panic("race: no promise") - } - ps := make([]*PromiseImpl[OutT], len(acs)) - for idx, ac := range acs { - ps[idx] = ac.(*PromiseImpl[OutT]) - } - remaining := len(acs) - returned := false - P := &PromiseImpl[OutT]{} - P.Debug = "Race" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - for _, p := range ps { - p.Exec = P.Exec - p.Parent = P - p.Call() - } - return - case 1: - remaining-- - if remaining < 0 { - log.Fatalf("race: remaining < 0: %+v\n", remaining) - } - if returned { - return - } - - for _, p := range ps { - if p.Done() { - if debugAsync { - log.Printf("async.Race done: %+v won the race\n", p) - } - returned = true - P.Return(p.value) - return - } - } - log.Fatalf("no promise done: %+v\n", ps) - return - default: - panic("unreachable") - } - } - return P -} - -func All[OutT any](acs []AsyncCall[OutT]) *PromiseImpl[[]OutT] { - ps := make([]*PromiseImpl[OutT], len(acs)) - for idx, ac := range acs { - ps[idx] = ac.(*PromiseImpl[OutT]) - } - done := 0 - P := &PromiseImpl[[]OutT]{} - P.Debug = "All" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - for _, p := range ps { - p.Exec = P.Exec - p.Parent = P - p.Call() - } - return - case 1: - done++ - if done < len(acs) { - return - } - P.Next = -1 - - for _, p := range ps { - if !p.Done() { - log.Fatalf("async.All: not done: %+v\n", p) - } - } - - ret := make([]OutT, len(acs)) - for idx, p := range ps { - ret[idx] = p.value - } - if debugAsync { - log.Printf("async.All done: %+v\n", ret) - } - P.Return(ret) - return - default: - panic("unreachable") - } - } - return P -} - -// llgo:link Await2 llgo.await -func Await2Compiled[OutT1, OutT2 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], - timeout ...time.Duration) (ret *PromiseImpl[tuple.Tuple3[OutT1, OutT2, error]]) { - p1 := ac1.(*PromiseImpl[OutT1]) - p2 := ac2.(*PromiseImpl[OutT2]) - remaining := 2 - P := &PromiseImpl[tuple.Tuple3[OutT1, OutT2, error]]{} - P.Debug = "Await2" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - p1.Exec = P.Exec - p1.Parent = P - p1.Call() - - p2.Exec = P.Exec - p2.Parent = P - p2.Call() - return - case 1: - remaining-- - if remaining > 0 { - return - } - P.Next = -1 - if !p1.Done() || !p2.Done() { - log.Fatalf("async.Await2: not done: %+v, %+v\n", p1, p2) - } - - P.Return(tuple.Tuple3[OutT1, OutT2, error]{ - V1: p1.value, - V2: p2.value, - V3: nil, - }) - return - default: - panic("unreachable") - } - } - return P -} - -// llgo:link Await2 llgo.await -func Await3Compiled[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], - timeout ...time.Duration) *PromiseImpl[tuple.Tuple3[OutT1, OutT2, OutT3]] { - p1 := ac1.(*PromiseImpl[OutT1]) - p2 := ac2.(*PromiseImpl[OutT2]) - p3 := ac3.(*PromiseImpl[OutT3]) - remaining := 3 - P := &PromiseImpl[tuple.Tuple3[OutT1, OutT2, OutT3]]{} - P.Debug = "Await3" - P.Func = func() { - switch P.Next { - case 0: - P.Next = 1 - p1.Exec = P.Exec - p1.Parent = P - p1.Call() - - p2.Exec = P.Exec - p2.Parent = P - p2.Call() - - p3.Exec = P.Exec - p3.Parent = P - p3.Call() - return - case 1: - remaining-- - if remaining > 0 { - return - } - P.Next = -1 - // TODO(lijie): return every error? - if !p1.Done() || !p2.Done() || !p3.Done() { - log.Fatalf("async.Await3: not done: %+v, %+v, %+v\n", p1, p2, p3) - } - - P.Return(tuple.Tuple3[OutT1, OutT2, OutT3]{ - V1: p1.value, - V2: p2.value, - V3: p3.value, - }) - return - default: - panic("unreachable") - } - } - return P -} - -func PAllCompiled[OutT any](acs ...AsyncCall[OutT]) *PromiseImpl[[]OutT] { - P := &PromiseImpl[[]OutT]{} - P.Debug = "Parallel" - P.Func = func() { - ret := make([]OutT, len(acs)) - wg := sync.WaitGroup{} - for idx, ac := range acs { - idx := idx - ac := ac - wg.Add(1) - go func(ac AsyncCall[OutT]) { - v := RunImpl[OutT](ac) - ret[idx] = v - wg.Done() - }(ac) - } - wg.Wait() - P.Return(ret) - } - return P -} - -func PAwait3Compiled[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3]) *PromiseImpl[tuple.Tuple4[OutT1, OutT2, OutT3, error]] { - P := &PromiseImpl[tuple.Tuple4[OutT1, OutT2, OutT3, error]]{} - P.Debug = "PAwait3" - P.Func = func() { - ret := tuple.Tuple4[OutT1, OutT2, OutT3, error]{} - wg := sync.WaitGroup{} - wg.Add(3) - go func() { - ret.V1 = RunImpl[OutT1](ac1) - wg.Done() - }() - go func() { - ret.V2 = RunImpl[OutT2](ac2) - wg.Done() - }() - go func() { - ret.V3 = RunImpl[OutT3](ac3) - wg.Done() - }() - wg.Wait() - P.Return(ret) - } - return P -} diff --git a/x/async/naive/naive.go b/x/async/naive/naive.go deleted file mode 100644 index a8de2bd9..00000000 --- a/x/async/naive/naive.go +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package naive - -import ( - "log" - "sync" -) - -const debugAsync = false - -// ----------------------------------------------------------------------------- - -type asyncCall interface { - parent() asyncCall - Resume() - Call() - Done() bool -} - -type AsyncCall[OutT any] interface { - Resume() -} - -type executor struct { - acs []asyncCall - mu sync.Mutex - cond *sync.Cond -} - -func newExecutor() *executor { - e := &executor{} - e.cond = sync.NewCond(&e.mu) - return e -} - -func (e *executor) schedule(ac asyncCall) { - e.mu.Lock() - e.acs = append(e.acs, ac) - e.mu.Unlock() - e.cond.Signal() -} - -func RunImpl[OutT any](ac AsyncCall[OutT]) OutT { - e := newExecutor() - p := ac.(*PromiseImpl[OutT]) - p.Exec = e - var rootAc asyncCall = p - e.schedule(rootAc) - - for { - e.mu.Lock() - for len(e.acs) == 0 { - e.cond.Wait() - } - e.mu.Unlock() - ac := e.acs[0] - e.acs = e.acs[1:] - ac.Call() - if ac.Done() && ac == rootAc { - return p.value - } - } -} - -// ----------------------------------------------------------------------------- - -type PromiseImpl[TOut any] struct { - Debug string - Next int - Exec *executor - Parent asyncCall - - Func func() - value TOut - c chan TOut -} - -func (p *PromiseImpl[TOut]) parent() asyncCall { - return p.Parent -} - -func (p *PromiseImpl[TOut]) Resume() { - if debugAsync { - log.Printf("Resume task: %+v\n", p) - } - p.Exec.schedule(p) -} - -func (p *PromiseImpl[TOut]) Done() bool { - return p.Next == -1 -} - -func (p *PromiseImpl[TOut]) Call() { - p.Func() -} - -func (p *PromiseImpl[TOut]) Suspend() { - -} - -func (p *PromiseImpl[TOut]) Return(v TOut) { - // TODO(lijie): panic if already resolved - p.value = v - if p.c != nil { - p.c <- v - } - if debugAsync { - log.Printf("Return task: %+v\n", p) - } - if p.Parent != nil { - p.Parent.Resume() - } -} - -func (p *PromiseImpl[TOut]) Yield(v TOut) { - p.value = v - if debugAsync { - log.Printf("Yield task: %+v\n", p) - } - if p.Parent != nil { - p.Parent.Resume() - } -} - -func (p *PromiseImpl[TOut]) Value() TOut { - return p.value -} - -func (p *PromiseImpl[TOut]) Chan() <-chan TOut { - if p.c == nil { - p.c = make(chan TOut, 1) - p.Func() - } - return p.c -} - -func (p *PromiseImpl[TOut]) Await() (ret TOut) { - panic("should not called") -} From 93bff83e3d36995e1ae2228c52e2a64790b92e56 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 10:41:39 +0800 Subject: [PATCH 14/27] ssa: fix @llvm.coro.size.i64 doc --- ssa/coro.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssa/coro.go b/ssa/coro.go index e765332c..65757db3 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -29,7 +29,7 @@ import ( // declare i1 @llvm.coro.done(ptr ) // declare ptr @llvm.coro.promise(ptr , i32 , i1 ) // declare i32 @llvm.coro.size.i32() -// declare i32 @llvm.coro.size.i64() +// declare i64 @llvm.coro.size.i64() // declare i32 @llvm.coro.align.i32() // declare i64 @llvm.coro.align.i64() // declare ptr @llvm.coro.begin(token , ptr ) @@ -109,7 +109,7 @@ func (p Program) tyCoSizeI32() *types.Signature { return p.coSizeI32Ty } -// declare i32 @llvm.coro.size.i64() +// 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)) From a1fdc05e26fd221c1b2d60baf315276e963078c3 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 12:37:34 +0800 Subject: [PATCH 15/27] cl: fix CoYield --- cl/_testdata/async/out.ll | 86 ++++++++++++++++++++++----------------- cl/async.go | 9 ++-- cl/compile.go | 1 + ssa/coro.go | 10 +---- 4 files changed, 56 insertions(+), 50 deletions(-) diff --git a/cl/_testdata/async/out.ll b/cl/_testdata/async/out.ll index 3306d581..a7c687ac 100644 --- a/cl/_testdata/async/out.ll +++ b/cl/_testdata/async/out.ll @@ -1,7 +1,7 @@ ; ModuleID = 'github.com/goplus/llgo/cl/_testdata/async' source_filename = "github.com/goplus/llgo/cl/_testdata/async" -%"github.com/goplus/llgo/internal/runtime.Defer" = type { ptr, i64, ptr, ptr } +%"github.com/goplus/llgo/internal/runtime.Defer" = type { ptr, i64, ptr, ptr, ptr } %"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } %"github.com/goplus/llgo/internal/runtime.eface" = type { ptr, ptr } %"github.com/goplus/llgo/internal/runtime.Slice" = type { ptr, i64, i64 } @@ -94,66 +94,71 @@ _llgo_4: ; preds = %alloc, %entry %4 = load i32, ptr @__llgo_defer, align 4 %5 = call ptr @pthread_getspecific(i32 %4) %6 = alloca i8, i64 196, align 1 - %7 = alloca i8, i64 32, align 1 + %7 = alloca i8, i64 40, align 1 %8 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 0 store ptr %6, ptr %8, align 8 %9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 store i64 0, ptr %9, align 4 %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 2 store ptr %5, ptr %10, align 8 - %11 = call i32 @pthread_setspecific(i32 %4, ptr %7) - %12 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 - %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 - %14 = call i32 @sigsetjmp(ptr %6, i32 0) - %15 = icmp eq i32 %14, 0 - br i1 %15, label %_llgo_8, label %_llgo_9 + %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 + store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_6), ptr %11, align 8 + %12 = call i32 @pthread_setspecific(i32 %4, ptr %7) + %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 + %14 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 + %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 4 + %16 = call i32 @sigsetjmp(ptr %6, i32 0) + %17 = icmp eq i32 %16, 0 + br i1 %17, label %_llgo_8, label %_llgo_9 _llgo_5: ; preds = %_llgo_8, %_llgo_7 - %16 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 0 - store ptr @0, ptr %17, align 8 - %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 1 - store i64 16, ptr %18, align 4 - %19 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %16, align 8 - %20 = load ptr, ptr @_llgo_string, align 8 - %21 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) - store %"github.com/goplus/llgo/internal/runtime.String" %19, ptr %21, align 8 - %22 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 - %23 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, i32 0, i32 0 - store ptr %20, ptr %23, align 8 - %24 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, i32 0, i32 1 - store ptr %21, ptr %24, align 8 - %25 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %22, align 8 - call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %25) + %18 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 0 + store ptr @0, ptr %19, align 8 + %20 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 1 + store i64 16, ptr %20, align 4 + %21 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %18, align 8 + %22 = load ptr, ptr @_llgo_string, align 8 + %23 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/internal/runtime.String" %21, ptr %23, align 8 + %24 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %25 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %24, i32 0, i32 0 + store ptr %22, ptr %25, align 8 + %26 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %24, i32 0, i32 1 + store ptr %23, ptr %26, align 8 + %27 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %24, align 8 + call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %27) unreachable - %26 = load ptr, ptr %3, align 8 + %28 = load ptr, ptr %3, align 8 br label %clean _llgo_6: ; preds = %_llgo_9 - %27 = load i64, ptr %12, align 4 + store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %14, align 8 + %29 = load i64, ptr %13, align 4 call void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() - %28 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, align 8 - %29 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %28, 2 - %30 = call i32 @pthread_setspecific(i32 %4, ptr %29) - %31 = load ptr, ptr %13, align 8 - indirectbr ptr %31, [label %_llgo_7] + %30 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, align 8 + %31 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %30, 2 + %32 = call i32 @pthread_setspecific(i32 %4, ptr %31) + %33 = load ptr, ptr %15, align 8 + indirectbr ptr %33, [label %_llgo_7] -_llgo_7: ; preds = %_llgo_6 +_llgo_7: ; preds = %_llgo_9, %_llgo_6 call void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr %5) br label %_llgo_5 _llgo_8: ; preds = %_llgo_4 - %32 = load ptr, ptr %3, align 8 + %34 = load ptr, ptr %3, align 8 call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) - %33 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %33, label %suspend [ + %35 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %35, label %suspend [ i8 0, label %_llgo_5 i8 1, label %clean ] _llgo_9: ; preds = %_llgo_4 - store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %13, align 8 - br label %_llgo_6 + store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %15, align 8 + %36 = load ptr, ptr %14, align 8 + indirectbr ptr %36, [label %_llgo_7, label %_llgo_6] _llgo_10: ; No predecessors! } @@ -283,7 +288,12 @@ declare ptr @llvm.coro.free(token, ptr nocapture readonly) #4 ; Function Attrs: nounwind declare i1 @llvm.coro.end(ptr, i1, token) #2 -declare void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr, i64) +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) #2 diff --git a/cl/async.go b/cl/async.go index 217f80ae..33a57bf4 100644 --- a/cl/async.go +++ b/cl/async.go @@ -17,6 +17,7 @@ package cl import ( + "go/constant" "go/types" "strings" @@ -95,7 +96,7 @@ func (p *context) coReturn(b llssa.Builder, args []ssa.Value) { func (p *context) coYield(b llssa.Builder, fn *ssa.Function, args []ssa.Value) { typ := fn.Signature.Recv().Type() mthds := p.goProg.MethodSets.MethodSet(typ) - // TODO(lijie): make llgo instruction callable (e.g. llgo.yield) + // TODO(lijie): make llgo instruction callable (e.g. llgo.yield) to avoid extra setValue method var setValue *ssa.Function for i := 0; i < mthds.Len(); i++ { m := mthds.At(i) @@ -110,8 +111,10 @@ func (p *context) coYield(b llssa.Builder, fn *ssa.Function, args []ssa.Value) { panic("coYield(): not found method setValue") } value := p.compileValue(b, args[1]) - setValueFn, _, _ := p.funcOf(setValue) - b.CoYield(setValueFn, value) + setValueFn, _, _ := p.compileFunction(setValue) + // 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) { diff --git a/cl/compile.go b/cl/compile.go index 1f6478c1..e5e5158d 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -240,6 +240,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun } fn.MakeBlocks(nblk) // to set fn.HasBody() = true if f.Recover != nil { // set recover block + // 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() { diff --git a/ssa/coro.go b/ssa/coro.go index 65757db3..899c7b65 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -382,14 +382,6 @@ func (b Builder) EndAsync() { b.Jump(cleanBlk) } -func promiseImplType(ty types.Type) types.Type { - ty = ty.Underlying().(*types.Struct).Field(0).Type() - if ptrTy, ok := ty.(*types.Pointer); ok { - return ptrTy.Elem() - } - panic(fmt.Sprintf("unexpected promise impl type: %v", ty)) -} - /* id := @llvm.coro.id(0, null, null, null) frameSize := @llvm.coro.size.i64() @@ -672,7 +664,7 @@ func (b Builder) CoAwaitSuspendHandle(awaiter, handle, f Expr) { b.Call(fn, awaiter, handle, f) } -func (b Builder) CoYield(setValueFn Function, value Expr) { +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())) } From 3bf0780a672141f55f917099ad0dea6ee42cc406 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 15:19:42 +0800 Subject: [PATCH 16/27] asyncio: trap on unexpected resume --- cl/async.go | 2 +- cl/compile.go | 3 ++- ssa/coro.go | 24 ++++++++++++++++-------- ssa/eh.go | 12 ++++++++++++ ssa/package.go | 2 ++ ssa/stmt_builder.go | 1 + 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/cl/async.go b/cl/async.go index 33a57bf4..df94d60f 100644 --- a/cl/async.go +++ b/cl/async.go @@ -67,7 +67,7 @@ func (p *context) coAwait(b llssa.Builder, args []ssa.Value) llssa.Expr { } func (p *context) coSuspend(b llssa.Builder, final llssa.Expr) { - b.CoSuspend(b.AsyncToken(), final) + b.CoSuspend(b.AsyncToken(), final, nil) } func (p *context) coDone(b llssa.Builder, args []ssa.Value) llssa.Expr { diff --git a/cl/compile.go b/cl/compile.go index e5e5158d..1ebef907 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -232,11 +232,12 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun nBlkOff := 0 if nblk := len(f.Blocks); nblk > 0 { if async { - nBlkOff = 4 + nBlkOff = 5 fn.MakeBlock("entry") fn.MakeBlock("alloc") fn.MakeBlock("clean") fn.MakeBlock("suspend") + fn.MakeBlock("trap") } fn.MakeBlocks(nblk) // to set fn.HasBody() = true if f.Recover != nil { // set recover block diff --git a/ssa/coro.go b/ssa/coro.go index 899c7b65..b059e0dd 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -378,8 +378,7 @@ func (b Builder) SetAsyncToken(token Expr) { } func (b Builder) EndAsync() { - _, _, cleanBlk := b.onSuspBlk(b.blk) - b.Jump(cleanBlk) + b.onReturn() } /* @@ -409,7 +408,8 @@ func (b Builder) BeginAsync(fn Function) { allocBlk := fn.Block(1) cleanBlk := fn.Block(2) suspdBlk := fn.Block(3) - beginBlk := fn.Block(4) + trapBlk := fn.Block(4) + beginBlk := fn.Block(5) b.SetBlock(entryBlk) promiseSize := b.Const(constant.MakeUint64(b.Prog.SizeOf(promiseTy)), b.Prog.Int64()).SetName("promise.size") @@ -448,9 +448,16 @@ func (b Builder) BeginAsync(fn Function) { 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) { return suspdBlk, nextBlk, cleanBlk } + b.onReturn = func() { + b.CoSuspend(b.asyncToken, b.Prog.BoolVal(true), trapBlk) + } } // ----------------------------------------------------------------------------- @@ -600,14 +607,15 @@ func (b Builder) coSuspend(save, final Expr) Expr { return b.Call(fn, save, final) } -func (b Builder) CoSuspend(save, final Expr) { +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) - // add resume block - b.Func.MakeBlock("") - nextBlk := b.Func.Block(b.blk.idx + 1) susp, next, clean := b.onSuspBlk(nextBlk) swt := b.Switch(ret, susp) swt.Case(b.Const(constant.MakeInt64(0), b.Prog.Byte()), next) @@ -669,5 +677,5 @@ func (b Builder) CoYield(setValueFn Function, value Expr, final Expr) { panic(fmt.Errorf("yield %v not in async block", b.Func.Name())) } b.Call(setValueFn.Expr, b.promise, value) - b.CoSuspend(b.AsyncToken(), b.Prog.BoolVal(false)) + b.CoSuspend(b.AsyncToken(), final, nil) } diff --git a/ssa/eh.go b/ssa/eh.go index 7ddbaaff..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 ( diff --git a/ssa/package.go b/ssa/package.go index df23a4bb..1e6fc5ea 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -193,6 +193,8 @@ 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 diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index c2b7cff4..ed228b17 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -68,6 +68,7 @@ type aBuilder struct { asyncToken Expr promise Expr onSuspBlk func(blk BasicBlock) (susp BasicBlock, next BasicBlock, clean BasicBlock) + onReturn func() blkOffset int } From 98072f3f4bf649006174e11aa543a9a2514f77d7 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 15:21:14 +0800 Subject: [PATCH 17/27] asyncio: fix unreasonable return type of promise.Next() --- cl/_testdata/async/in.go | 4 +- cl/_testdata/async/out.ll | 164 +++++++++++++++++++++++--------------- x/async/async.go | 3 +- 3 files changed, 103 insertions(+), 68 deletions(-) diff --git a/cl/_testdata/async/in.go b/cl/_testdata/async/in.go index 259b98b2..66a55303 100644 --- a/cl/_testdata/async/in.go +++ b/cl/_testdata/async/in.go @@ -21,7 +21,9 @@ func UseGenInts() int { co := WrapGenInts() r := 0 for !co.Done() { - r += co.Next() + v := co.Value() + r += v + co.Next() } return r } diff --git a/cl/_testdata/async/out.ll b/cl/_testdata/async/out.ll index a7c687ac..150ac479 100644 --- a/cl/_testdata/async/out.ll +++ b/cl/_testdata/async/out.ll @@ -19,50 +19,58 @@ entry: %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) - br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + br i1 %need.dyn.alloc, label %alloc, label %_llgo_5 alloc: ; preds = %entry %frame.size = call i64 @llvm.coro.size.i64() %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) - br label %_llgo_4 + br label %_llgo_5 -clean: ; preds = %_llgo_7, %_llgo_6, %_llgo_5, %_llgo_4 +clean: ; preds = %_llgo_8, %_llgo_7, %_llgo_6, %_llgo_5 %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) br label %suspend -suspend: ; preds = %_llgo_6, %_llgo_5, %_llgo_4, %clean +suspend: ; preds = %_llgo_8, %_llgo_7, %_llgo_6, %_llgo_5, %clean %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) ret ptr %promise -_llgo_4: ; preds = %alloc, %entry +trap: ; preds = %_llgo_8 + call void @llvm.trap() + unreachable + +_llgo_5: ; preds = %alloc, %entry %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) store ptr %hdl, ptr %promise, align 8 call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) %2 = call i8 @llvm.coro.suspend(token %id, i1 false) switch i8 %2, label %suspend [ - i8 0, label %_llgo_5 - i8 1, label %clean - ] - -_llgo_5: ; preds = %_llgo_4 - call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 2) - %3 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %3, label %suspend [ i8 0, label %_llgo_6 i8 1, label %clean ] _llgo_6: ; preds = %_llgo_5 - call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 3) - %4 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %4, label %suspend [ + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 2) + %3 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %3, label %suspend [ i8 0, label %_llgo_7 i8 1, label %clean ] _llgo_7: ; preds = %_llgo_6 - br label %clean + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 3) + %4 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %4, label %suspend [ + i8 0, label %_llgo_8 + i8 1, label %clean + ] + +_llgo_8: ; preds = %_llgo_7 + %5 = call i8 @llvm.coro.suspend(token %id, i1 true) + switch i8 %5, label %suspend [ + i8 0, label %trap + i8 1, label %clean + ] } define ptr @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer"() #0 { @@ -70,22 +78,26 @@ entry: %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) - br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + br i1 %need.dyn.alloc, label %alloc, label %_llgo_5 alloc: ; preds = %entry %frame.size = call i64 @llvm.coro.size.i64() %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) - br label %_llgo_4 + br label %_llgo_5 -clean: ; preds = %_llgo_5, %_llgo_8 +clean: ; preds = %_llgo_6, %_llgo_9 %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) br label %suspend -suspend: ; preds = %_llgo_8, %clean +suspend: ; preds = %_llgo_6, %_llgo_9, %clean %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) ret ptr %promise -_llgo_4: ; preds = %alloc, %entry +trap: ; preds = %_llgo_6 + call void @llvm.trap() + unreachable + +_llgo_5: ; preds = %alloc, %entry %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) store ptr %hdl, ptr %promise, align 8 @@ -102,16 +114,16 @@ _llgo_4: ; preds = %alloc, %entry %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 2 store ptr %5, ptr %10, align 8 %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 - store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_6), ptr %11, align 8 + store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %11, align 8 %12 = call i32 @pthread_setspecific(i32 %4, ptr %7) %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 %14 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 4 %16 = call i32 @sigsetjmp(ptr %6, i32 0) %17 = icmp eq i32 %16, 0 - br i1 %17, label %_llgo_8, label %_llgo_9 + br i1 %17, label %_llgo_9, label %_llgo_10 -_llgo_5: ; preds = %_llgo_8, %_llgo_7 +_llgo_6: ; preds = %_llgo_9, %_llgo_8 %18 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 0 store ptr @0, ptr %19, align 8 @@ -130,37 +142,41 @@ _llgo_5: ; preds = %_llgo_8, %_llgo_7 call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %27) unreachable %28 = load ptr, ptr %3, align 8 - br label %clean - -_llgo_6: ; preds = %_llgo_9 - store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %14, align 8 - %29 = load i64, ptr %13, align 4 - call void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() - %30 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, align 8 - %31 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %30, 2 - %32 = call i32 @pthread_setspecific(i32 %4, ptr %31) - %33 = load ptr, ptr %15, align 8 - indirectbr ptr %33, [label %_llgo_7] - -_llgo_7: ; preds = %_llgo_9, %_llgo_6 - call void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr %5) - br label %_llgo_5 - -_llgo_8: ; preds = %_llgo_4 - %34 = load ptr, ptr %3, align 8 - call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) - %35 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %35, label %suspend [ - i8 0, label %_llgo_5 + %29 = call i8 @llvm.coro.suspend(token %id, i1 true) + switch i8 %29, label %suspend [ + i8 0, label %trap i8 1, label %clean ] -_llgo_9: ; preds = %_llgo_4 - store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %15, align 8 - %36 = load ptr, ptr %14, align 8 - indirectbr ptr %36, [label %_llgo_7, label %_llgo_6] +_llgo_7: ; preds = %_llgo_10 + store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_8), ptr %14, align 8 + %30 = load i64, ptr %13, align 4 + call void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() + %31 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, align 8 + %32 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %31, 2 + %33 = call i32 @pthread_setspecific(i32 %4, ptr %32) + %34 = load ptr, ptr %15, align 8 + indirectbr ptr %34, [label %_llgo_8] -_llgo_10: ; No predecessors! +_llgo_8: ; preds = %_llgo_10, %_llgo_7 + call void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr %5) + br label %_llgo_6 + +_llgo_9: ; preds = %_llgo_5 + %35 = load ptr, ptr %3, align 8 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) + %36 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %36, label %suspend [ + i8 0, label %_llgo_6 + i8 1, label %clean + ] + +_llgo_10: ; preds = %_llgo_5 + store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_8), ptr %15, align 8 + %37 = load ptr, ptr %14, align 8 + indirectbr ptr %37, [label %_llgo_8, label %_llgo_7] + +_llgo_11: ; No predecessors! } define void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() { @@ -212,8 +228,9 @@ _llgo_0: br label %_llgo_3 _llgo_1: ; preds = %_llgo_3 - %1 = call i64 @"github.com/goplus/llgo/x/async.(*Promise).Next[int]"(ptr %0) + %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 @@ -230,27 +247,35 @@ entry: %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) - br i1 %need.dyn.alloc, label %alloc, label %_llgo_4 + br i1 %need.dyn.alloc, label %alloc, label %_llgo_5 alloc: ; preds = %entry %frame.size = call i64 @llvm.coro.size.i64() %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) - br label %_llgo_4 + br label %_llgo_5 -clean: ; preds = %_llgo_4 +clean: ; preds = %_llgo_5 %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) br label %suspend -suspend: ; preds = %clean +suspend: ; preds = %_llgo_5, %clean %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) ret ptr %promise -_llgo_4: ; preds = %alloc, %entry +trap: ; preds = %_llgo_5 + call void @llvm.trap() + unreachable + +_llgo_5: ; preds = %alloc, %entry %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) store ptr %hdl, ptr %promise, align 8 %2 = call ptr @"github.com/goplus/llgo/cl/_testdata/async.GenInts"() - br label %clean + %3 = call i8 @llvm.coro.suspend(token %id, i1 true) + switch i8 %3, label %suspend [ + i8 0, label %trap + i8 1, label %clean + ] } define void @"github.com/goplus/llgo/cl/_testdata/async.init"() { @@ -288,6 +313,9 @@ declare ptr @llvm.coro.free(token, ptr nocapture readonly) #4 ; Function Attrs: nounwind declare i1 @llvm.coro.end(ptr, i1, token) #2 +; Function Attrs: cold noreturn nounwind memory(inaccessiblemem: write) +declare void @llvm.trap() #5 + 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 @@ -355,20 +383,25 @@ _llgo_0: ret i1 %6 } -define i64 @"github.com/goplus/llgo/x/async.(*Promise).Next[int]"(ptr %0) { +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) - %3 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 1 - %4 = load i64, ptr %3, align 4 - ret i64 %4 + ret void } declare void @fmt.init() ; Function Attrs: nounwind memory(argmem: readwrite) -declare i1 @llvm.coro.done(ptr nocapture readonly) #5 +declare i1 @llvm.coro.done(ptr nocapture readonly) #6 declare void @llvm.coro.resume(ptr) @@ -379,4 +412,5 @@ attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: re attributes #2 = { nounwind } attributes #3 = { nounwind memory(none) } attributes #4 = { nounwind memory(argmem: read) } -attributes #5 = { nounwind memory(argmem: readwrite) } +attributes #5 = { cold noreturn nounwind memory(inaccessiblemem: write) } +attributes #6 = { nounwind memory(argmem: readwrite) } diff --git a/x/async/async.go b/x/async/async.go index 0060a959..d572e3e0 100644 --- a/x/async/async.go +++ b/x/async/async.go @@ -54,9 +54,8 @@ func (p *Promise[TOut]) Resume() { coResume(p.hdl) } -func (p *Promise[TOut]) Next() TOut { +func (p *Promise[TOut]) Next() { coResume(p.hdl) - return p.value } // TODO(lijie): should merge to Yield() From bb03df70590ec3d0e8e796cd1ff3ffccfbe7f68b Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 16:42:51 +0800 Subject: [PATCH 18/27] ssa: refactor Builder.BeginAsync --- cl/compile.go | 16 +++++++++------- ssa/coro.go | 11 ++--------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index 1ebef907..81f716c6 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -231,15 +231,17 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun } nBlkOff := 0 if nblk := len(f.Blocks); nblk > 0 { + var entryBlk, allocBlk, cleanBlk, suspdBlk, trapBlk, beginBlk llssa.BasicBlock if async { nBlkOff = 5 - fn.MakeBlock("entry") - fn.MakeBlock("alloc") - fn.MakeBlock("clean") - fn.MakeBlock("suspend") - fn.MakeBlock("trap") + 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 + fn.MakeBlocks(nblk) // to set fn.HasBody() = true + beginBlk = fn.Block(nBlkOff) if f.Recover != nil { // set recover block // TODO(lijie): fix this for async function because of the block offset increase fn.SetRecover(fn.Block(f.Recover.Index + nBlkOff)) @@ -260,7 +262,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun b := fn.NewBuilder() b.SetBlockOffset(nBlkOff) if async { - b.BeginAsync(fn) + b.BeginAsync(fn, entryBlk, allocBlk, cleanBlk, suspdBlk, trapBlk, beginBlk) } p.bvals = make(map[ssa.Value]llssa.Expr) off := make([]int, len(f.Blocks)) diff --git a/ssa/coro.go b/ssa/coro.go index b059e0dd..07f150d8 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -21,7 +21,6 @@ import ( "go/constant" "go/token" "go/types" - "log" ) // declare void @llvm.coro.destroy(ptr ) @@ -382,6 +381,7 @@ func (b Builder) EndAsync() { } /* +retPtr := malloc(sizeof(Promise)) id := @llvm.coro.id(0, null, null, null) frameSize := @llvm.coro.size.i64() needAlloc := @llvm.coro.alloc(id) @@ -395,7 +395,7 @@ frame := null hdl := @llvm.coro.begin(id, frame) *retPtr = hdl */ -func (b Builder) BeginAsync(fn Function) { +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 { @@ -404,19 +404,12 @@ func (b Builder) BeginAsync(fn Function) { promiseTy := b.Prog.Type(ptrTy.Elem(), InGo) b.async = true - entryBlk := fn.Block(0) - allocBlk := fn.Block(1) - cleanBlk := fn.Block(2) - suspdBlk := fn.Block(3) - trapBlk := fn.Block(4) - beginBlk := fn.Block(5) b.SetBlock(entryBlk) promiseSize := b.Const(constant.MakeUint64(b.Prog.SizeOf(promiseTy)), b.Prog.Int64()).SetName("promise.size") promise := b.AllocZ(promiseSize).SetName("promise") promise.Type = b.Prog.Pointer(promiseTy) b.promise = promise - log.Printf("promise ptr: %v", promise.RawType()) align := b.Const(constant.MakeInt64(0), b.Prog.CInt()).SetName("align") null := b.Const(nil, b.Prog.CIntPtr()) id := b.CoID(align, null, null, null).SetName("id") From b04ac8df306c1fe9d78bad41060a5ad7ec4abbcb Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 16:43:36 +0800 Subject: [PATCH 19/27] ssa: comment unused coroutine builders --- ssa/coro.go | 12 ++++++++++++ ssa/package.go | 50 +++++++++++++++++++++++++------------------------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/ssa/coro.go b/ssa/coro.go index 07f150d8..a1e35eaa 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -202,6 +202,7 @@ func (p Program) tyCoID() *types.Signature { return p.coIDTy } +/* // declare token @llvm.coro.id.async(i32 , i32 , ptr , ptr ) func (p Program) tyCoIDAsync() *types.Signature { if p.coIDAsyncTy == nil { @@ -240,6 +241,7 @@ func (p Program) tyCoIDRetconOnce() *types.Signature { } return p.coIDRetconOnceTy } +*/ // declare i1 @llvm.coro.end(ptr , i1 , token ) func (p Program) tyCoEnd() *types.Signature { @@ -254,6 +256,7 @@ func (p Program) tyCoEnd() *types.Signature { return p.coEndTy } +/* // TODO(lijie): varargs // declare token @llvm.coro.end.results(...) func (p Program) tyCoEndResults() *types.Signature { @@ -265,6 +268,7 @@ func (p Program) tyCoEndResults() *types.Signature { func (p Program) tyCoEndAsync() *types.Signature { panic("not implemented") } +*/ // declare i8 @llvm.coro.suspend(token , i1 ) func (p Program) tyCoSuspend() *types.Signature { @@ -279,6 +283,7 @@ func (p Program) tyCoSuspend() *types.Signature { return p.coSuspendTy } +/* // declare token @llvm.coro.save(ptr ) func (p Program) tyCoSave() *types.Signature { if p.coSaveTy == nil { @@ -353,6 +358,7 @@ func (p Program) tyCoAwaitSuspendHandle() *types.Signature { } return p.coAwaitSuspendHandleTy } +*/ // ----------------------------------------------------------------------------- @@ -551,6 +557,7 @@ func (b Builder) CoID(align Expr, promise, coroAddr, fnAddrs Expr) Expr { 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() { @@ -574,6 +581,7 @@ func (b Builder) CoIDRetconOnce(size, align, buffer, prototype, alloc, dealloc E 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 { @@ -581,6 +589,7 @@ func (b Builder) CoEnd(hdl Expr, unwind Expr, resultToken Expr) Expr { 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()) @@ -593,6 +602,7 @@ func (b Builder) CoEndAsync(handle, unwind Expr, args ...Expr) Expr { 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 { @@ -629,6 +639,7 @@ func (b Builder) CoReturn(args ...Expr) { b.SetBlock(nextBlk) } +/* // declare token @llvm.coro.save(ptr ) func (b Builder) CoSave(hdl Expr) Expr { fn := b.Pkg.cFunc("llvm.coro.save", b.Prog.tyCoSave()) @@ -664,6 +675,7 @@ func (b Builder) CoAwaitSuspendHandle(awaiter, handle, f Expr) { fn := b.Pkg.cFunc("llvm.coro.await.suspend.handle", b.Prog.tyCoAwaitSuspendHandle()) b.Call(fn, awaiter, handle, f) } +*/ func (b Builder) CoYield(setValueFn Function, value Expr, final Expr) { if !b.async { diff --git a/ssa/package.go b/ssa/package.go index 1e6fc5ea..79a2acad 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -202,31 +202,31 @@ type aProgram struct { 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 + 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 From d3df782fca7a1ab3bd8ff6c61382261a36f6c4f3 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 17:24:36 +0800 Subject: [PATCH 20/27] asyncic: coReturn --- cl/async.go | 31 ++++++++++++++----------------- cl/instr.go | 2 +- ssa/coro.go | 32 ++++++++++++++------------------ 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/cl/async.go b/cl/async.go index df94d60f..bb81e01d 100644 --- a/cl/async.go +++ b/cl/async.go @@ -85,33 +85,30 @@ func (p *context) coResume(b llssa.Builder, args []ssa.Value) { } } -func (p *context) coReturn(b llssa.Builder, args []ssa.Value) { - cargs := make([]llssa.Expr, len(args)) - for i, arg := range args { - cargs[i] = p.compileValue(b, arg) - } - b.CoReturn(cargs...) -} - -func (p *context) coYield(b llssa.Builder, fn *ssa.Function, args []ssa.Value) { +func (p *context) getSetValueFunc(fn *ssa.Function) llssa.Function { typ := fn.Signature.Recv().Type() mthds := p.goProg.MethodSets.MethodSet(typ) - // TODO(lijie): make llgo instruction callable (e.g. llgo.yield) to avoid extra setValue method - var setValue *ssa.Function for i := 0; i < mthds.Len(); i++ { m := mthds.At(i) if ssaMthd := p.goProg.MethodValue(m); ssaMthd != nil { if ssaMthd.Name() == "setValue" || strings.HasPrefix(ssaMthd.Name(), "setValue[") { - setValue = ssaMthd - break + setValueFn, _, _ := p.compileFunction(ssaMthd) + return setValueFn } } } - if setValue == nil { - panic("coYield(): not found method setValue") - } + 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]) - setValueFn, _, _ := p.compileFunction(setValue) // 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) diff --git a/cl/instr.go b/cl/instr.go index 9a65ba8e..50cfc22f 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -408,7 +408,7 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon case llgoCoResume: p.coResume(b, args) case llgoCoReturn: - p.coReturn(b, args) + p.coReturn(b, cv, args) case llgoCoYield: p.coYield(b, cv, args) case llgoCoRun: diff --git a/ssa/coro.go b/ssa/coro.go index a1e35eaa..9864e2f3 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -378,10 +378,6 @@ func (b Builder) AsyncToken() Expr { return b.asyncToken } -func (b Builder) SetAsyncToken(token Expr) { - b.asyncToken = token -} - func (b Builder) EndAsync() { b.onReturn() } @@ -452,6 +448,9 @@ func (b Builder) BeginAsync(fn Function, entryBlk, allocBlk, cleanBlk, suspdBlk, b.Unreachable() b.onSuspBlk = func(nextBlk BasicBlock) (BasicBlock, BasicBlock, BasicBlock) { + if nextBlk == nil { + nextBlk = trapBlk + } return suspdBlk, nextBlk, cleanBlk } b.onReturn = func() { @@ -627,16 +626,21 @@ func (b Builder) CoSuspend(save, final Expr, nextBlk BasicBlock) { b.SetBlock(nextBlk) } -func (b Builder) CoReturn(args ...Expr) { +func (b Builder) CoReturn(setValueFn Function, value Expr) { if !b.async { panic(fmt.Errorf("return %v not in async block", b.Func.Name())) } - - b.Func.MakeBlock("") - nextBlk := b.Func.Block(b.blk.idx + 1) - _, _, cleanBlk := b.onSuspBlk(nextBlk) + b.Call(setValueFn.Expr, b.promise, value) + _, _, cleanBlk := b.onSuspBlk(nil) b.Jump(cleanBlk) - b.SetBlock(nextBlk) +} + +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) } /* @@ -676,11 +680,3 @@ func (b Builder) CoAwaitSuspendHandle(awaiter, handle, f Expr) { b.Call(fn, awaiter, handle, f) } */ - -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) -} From 5fca8ebd4e9ad86b55586f256f10216259d3464b Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 16:43:56 +0800 Subject: [PATCH 21/27] ssa: test coroutine builders --- ssa/ssa_test.go | 171 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 4946869d..a11e8335 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -552,3 +552,174 @@ _llgo_0: } `) } + +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() #0 + +attributes #0 = { cold noreturn nounwind memory(inaccessiblemem: write) } +`) +} + +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) #0 + +; Function Attrs: nounwind memory(none) +declare i32 @llvm.coro.size.i32() #1 + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.size.i64() #1 + +; Function Attrs: nounwind memory(none) +declare i32 @llvm.coro.align.i32() #1 + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.align.i64() #1 + +; Function Attrs: nounwind +declare i1 @llvm.coro.alloc(token) #2 + +; Function Attrs: nounwind +declare ptr @llvm.coro.begin(token, ptr writeonly) #2 + +; Function Attrs: nounwind memory(argmem: read) +declare ptr @llvm.coro.free(token, ptr nocapture readonly) #3 + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(ptr, i1, token) #2 + +declare void @llvm.coro.resume(ptr) + +; Function Attrs: nounwind memory(argmem: readwrite) +declare i1 @llvm.coro.done(ptr nocapture readonly) #4 + +declare void @llvm.coro.destroy(ptr) + +; Function Attrs: nounwind memory(none) +declare ptr @llvm.coro.promise(ptr nocapture, i32, i1) #1 + +; Function Attrs: nounwind memory(none) +declare ptr @llvm.coro.noop() #1 + +; Function Attrs: nounwind memory(none) +declare ptr @llvm.coro.frame() #1 + +declare void @setValue(i64) + +; Function Attrs: nounwind +declare i8 @llvm.coro.suspend(token, i1) #2 + +attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) } +attributes #1 = { nounwind memory(none) } +attributes #2 = { nounwind } +attributes #3 = { nounwind memory(argmem: read) } +attributes #4 = { nounwind memory(argmem: readwrite) } +`) +} From 294abb512694879f27af0128c977027c8f5e5b85 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 18:00:14 +0800 Subject: [PATCH 22/27] ssa: test Builder.Switch --- ssa/ssa_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index a11e8335..05ac2dfd 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") From 835d6fee1e06b5455251408c02a3bfb15c1b6315 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 18:10:57 +0800 Subject: [PATCH 23/27] cl: test async function compiling --- cl/compile_test.go | 116 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/cl/compile_test.go b/cl/compile_test.go index fa77673c..14be500c 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -124,3 +124,119 @@ _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() #0 { +entry: + %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) + br i1 %need.dyn.alloc, label %alloc, label %_llgo_5 + +alloc: ; preds = %entry + %frame.size = call i64 @llvm.coro.size.i64() + %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + br label %_llgo_5 + +clean: ; preds = %_llgo_7, %_llgo_6, %_llgo_5 + %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + br label %suspend + +suspend: ; preds = %_llgo_7, %_llgo_6, %_llgo_5, %clean + %1 = 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 + %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + store ptr %hdl, ptr %promise, align 8 + call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) + %2 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %2, label %suspend [ + i8 0, label %_llgo_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) + %3 = call i8 @llvm.coro.suspend(token %id, i1 false) + switch i8 %3, label %suspend [ + i8 0, label %_llgo_7 + i8 1, label %clean + ] + +_llgo_7: ; preds = %_llgo_6 + %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 @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 +} + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) #1 + +; Function Attrs: nounwind +declare i1 @llvm.coro.alloc(token) #2 + +; Function Attrs: nounwind memory(none) +declare i64 @llvm.coro.size.i64() #3 + +; Function Attrs: nounwind +declare ptr @llvm.coro.begin(token, ptr writeonly) #2 + +; Function Attrs: nounwind memory(argmem: read) +declare ptr @llvm.coro.free(token, ptr nocapture readonly) #4 + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(ptr, i1, token) #2 + +; Function Attrs: cold noreturn nounwind memory(inaccessiblemem: write) +declare void @llvm.trap() #5 + +declare void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr, i64) + +; Function Attrs: nounwind +declare i8 @llvm.coro.suspend(token, i1) #2 + +attributes #0 = { "presplitcoroutine" } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) } +attributes #2 = { nounwind } +attributes #3 = { nounwind memory(none) } +attributes #4 = { nounwind memory(argmem: read) } +attributes #5 = { cold noreturn nounwind memory(inaccessiblemem: write) } +`) +} From 9102ca6b1e94f6a62c413765807826682d62e9e9 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 20:13:31 +0800 Subject: [PATCH 24/27] ssa: workaround for LLVM attribute compilation errors --- cl/compile_test.go | 24 +++++++++--------------- ssa/package.go | 36 +++++++++++++++++++++++++++++++++++- ssa/ssa_test.go | 36 +++++++++++++++--------------------- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/cl/compile_test.go b/cl/compile_test.go index 14be500c..b1942b80 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -140,7 +140,7 @@ source_filename = "foo" @"foo.init$guard" = global i1 false, align 1 -define ptr @foo.GenInts() #0 { +define ptr @foo.GenInts() presplitcoroutine { entry: %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) @@ -207,36 +207,30 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) -declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) #1 +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) ; Function Attrs: nounwind -declare i1 @llvm.coro.alloc(token) #2 +declare i1 @llvm.coro.alloc(token) ; Function Attrs: nounwind memory(none) -declare i64 @llvm.coro.size.i64() #3 +declare i64 @llvm.coro.size.i64() ; Function Attrs: nounwind -declare ptr @llvm.coro.begin(token, ptr writeonly) #2 +declare ptr @llvm.coro.begin(token, ptr writeonly) ; Function Attrs: nounwind memory(argmem: read) -declare ptr @llvm.coro.free(token, ptr nocapture readonly) #4 +declare ptr @llvm.coro.free(token, ptr nocapture readonly) ; Function Attrs: nounwind -declare i1 @llvm.coro.end(ptr, i1, token) #2 +declare i1 @llvm.coro.end(ptr, i1, token) ; Function Attrs: cold noreturn nounwind memory(inaccessiblemem: write) -declare void @llvm.trap() #5 +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) #2 +declare i8 @llvm.coro.suspend(token, i1) -attributes #0 = { "presplitcoroutine" } -attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) } -attributes #2 = { nounwind } -attributes #3 = { nounwind memory(none) } -attributes #4 = { nounwind memory(argmem: read) } -attributes #5 = { cold noreturn nounwind memory(inaccessiblemem: write) } `) } diff --git a/ssa/package.go b/ssa/package.go index 79a2acad..0c1ed75e 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" @@ -699,9 +701,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 05ac2dfd..3e5c1762 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -610,9 +610,8 @@ _llgo_0: } ; Function Attrs: cold noreturn nounwind memory(inaccessiblemem: write) -declare void @llvm.trap() #0 +declare void @llvm.trap() -attributes #0 = { cold noreturn nounwind memory(inaccessiblemem: write) } `) } @@ -710,57 +709,52 @@ _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) #0 +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) ; Function Attrs: nounwind memory(none) -declare i32 @llvm.coro.size.i32() #1 +declare i32 @llvm.coro.size.i32() ; Function Attrs: nounwind memory(none) -declare i64 @llvm.coro.size.i64() #1 +declare i64 @llvm.coro.size.i64() ; Function Attrs: nounwind memory(none) -declare i32 @llvm.coro.align.i32() #1 +declare i32 @llvm.coro.align.i32() ; Function Attrs: nounwind memory(none) -declare i64 @llvm.coro.align.i64() #1 +declare i64 @llvm.coro.align.i64() ; Function Attrs: nounwind -declare i1 @llvm.coro.alloc(token) #2 +declare i1 @llvm.coro.alloc(token) ; Function Attrs: nounwind -declare ptr @llvm.coro.begin(token, ptr writeonly) #2 +declare ptr @llvm.coro.begin(token, ptr writeonly) ; Function Attrs: nounwind memory(argmem: read) -declare ptr @llvm.coro.free(token, ptr nocapture readonly) #3 +declare ptr @llvm.coro.free(token, ptr nocapture readonly) ; Function Attrs: nounwind -declare i1 @llvm.coro.end(ptr, i1, token) #2 +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) #4 +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) #1 +declare ptr @llvm.coro.promise(ptr nocapture, i32, i1) ; Function Attrs: nounwind memory(none) -declare ptr @llvm.coro.noop() #1 +declare ptr @llvm.coro.noop() ; Function Attrs: nounwind memory(none) -declare ptr @llvm.coro.frame() #1 +declare ptr @llvm.coro.frame() declare void @setValue(i64) ; Function Attrs: nounwind -declare i8 @llvm.coro.suspend(token, i1) #2 +declare i8 @llvm.coro.suspend(token, i1) -attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) } -attributes #1 = { nounwind memory(none) } -attributes #2 = { nounwind } -attributes #3 = { nounwind memory(argmem: read) } -attributes #4 = { nounwind memory(argmem: readwrite) } `) } From a1d46e905bcbfccb8619cfdda9f17267a28c467a Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 20:21:11 +0800 Subject: [PATCH 25/27] asyncio: demo & test --- cl/_testdata/async/in.go | 2 +- cl/_testdata/async/out.ll | 63 ++++++++++++++------------------ x/async/_demo/gendemo/gendemo.go | 17 ++++++--- 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/cl/_testdata/async/in.go b/cl/_testdata/async/in.go index 66a55303..a59eec6c 100644 --- a/cl/_testdata/async/in.go +++ b/cl/_testdata/async/in.go @@ -1,4 +1,4 @@ -package async_compile +package async import ( "fmt" diff --git a/cl/_testdata/async/out.ll b/cl/_testdata/async/out.ll index 150ac479..c6c0f612 100644 --- a/cl/_testdata/async/out.ll +++ b/cl/_testdata/async/out.ll @@ -1,5 +1,5 @@ -; ModuleID = 'github.com/goplus/llgo/cl/_testdata/async' -source_filename = "github.com/goplus/llgo/cl/_testdata/async" +; ModuleID = 'async' +source_filename = "async" %"github.com/goplus/llgo/internal/runtime.Defer" = type { ptr, i64, ptr, ptr, ptr } %"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } @@ -8,13 +8,13 @@ source_filename = "github.com/goplus/llgo/cl/_testdata/async" %"github.com/goplus/llgo/internal/runtime.iface" = type { ptr, ptr } %"github.com/goplus/llgo/x/async.Promise[int]" = type { ptr, i64 } -@"github.com/goplus/llgo/cl/_testdata/async.init$guard" = global i1 false, align 1 +@"async.init$guard" = global i1 false, align 1 @__llgo_defer = linkonce global i32 0, align 4 @0 = private unnamed_addr constant [16 x i8] c"GenIntsWithDefer", align 1 @_llgo_string = linkonce global ptr null, align 8 @1 = private unnamed_addr constant [6 x i8] c"panic:", align 1 -define ptr @"github.com/goplus/llgo/cl/_testdata/async.GenInts"() #0 { +define ptr @async.GenInts() presplitcoroutine { entry: %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) @@ -73,7 +73,7 @@ _llgo_8: ; preds = %_llgo_7 ] } -define ptr @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer"() #0 { +define ptr @async.GenIntsWithDefer() presplitcoroutine { entry: %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) @@ -114,7 +114,7 @@ _llgo_5: ; preds = %alloc, %entry %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 2 store ptr %5, ptr %10, align 8 %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 - store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_7), ptr %11, align 8 + store ptr blockaddress(@async.GenIntsWithDefer, %_llgo_7), ptr %11, align 8 %12 = call i32 @pthread_setspecific(i32 %4, ptr %7) %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 %14 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 @@ -149,9 +149,9 @@ _llgo_6: ; preds = %_llgo_9, %_llgo_8 ] _llgo_7: ; preds = %_llgo_10 - store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_8), ptr %14, align 8 + store ptr blockaddress(@async.GenIntsWithDefer, %_llgo_8), ptr %14, align 8 %30 = load i64, ptr %13, align 4 - call void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() + call void @"async.GenIntsWithDefer$1"() %31 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, align 8 %32 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %31, 2 %33 = call i32 @pthread_setspecific(i32 %4, ptr %32) @@ -172,14 +172,14 @@ _llgo_9: ; preds = %_llgo_5 ] _llgo_10: ; preds = %_llgo_5 - store ptr blockaddress(@"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer", %_llgo_8), ptr %15, align 8 + store ptr blockaddress(@async.GenIntsWithDefer, %_llgo_8), ptr %15, align 8 %37 = load ptr, ptr %14, align 8 indirectbr ptr %37, [label %_llgo_8, label %_llgo_7] _llgo_11: ; No predecessors! } -define void @"github.com/goplus/llgo/cl/_testdata/async.GenIntsWithDefer$1"() { +define void @"async.GenIntsWithDefer$1"() { _llgo_0: %0 = call %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/internal/runtime.Recover"() %1 = call i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface" %0, %"github.com/goplus/llgo/internal/runtime.eface" zeroinitializer) @@ -222,9 +222,9 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 ret void } -define i64 @"github.com/goplus/llgo/cl/_testdata/async.UseGenInts"() { +define i64 @async.UseGenInts() { _llgo_0: - %0 = call ptr @"github.com/goplus/llgo/cl/_testdata/async.WrapGenInts"() + %0 = call ptr @async.WrapGenInts() br label %_llgo_3 _llgo_1: ; preds = %_llgo_3 @@ -242,7 +242,7 @@ _llgo_3: ; preds = %_llgo_1, %_llgo_0 br i1 %4, label %_llgo_2, label %_llgo_1 } -define ptr @"github.com/goplus/llgo/cl/_testdata/async.WrapGenInts"() #0 { +define ptr @async.WrapGenInts() presplitcoroutine { entry: %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) @@ -270,7 +270,7 @@ _llgo_5: ; preds = %alloc, %entry %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) store ptr %hdl, ptr %promise, align 8 - %2 = call ptr @"github.com/goplus/llgo/cl/_testdata/async.GenInts"() + %2 = call ptr @async.GenInts() %3 = call i8 @llvm.coro.suspend(token %id, i1 true) switch i8 %3, label %suspend [ i8 0, label %trap @@ -278,15 +278,15 @@ _llgo_5: ; preds = %alloc, %entry ] } -define void @"github.com/goplus/llgo/cl/_testdata/async.init"() { +define void @async.init() { _llgo_0: - %0 = load i1, ptr @"github.com/goplus/llgo/cl/_testdata/async.init$guard", align 1 + %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 @"github.com/goplus/llgo/cl/_testdata/async.init$guard", align 1 + store i1 true, ptr @"async.init$guard", align 1 call void @fmt.init() - call void @"github.com/goplus/llgo/cl/_testdata/async.init$after"() + call void @"async.init$after"() br label %_llgo_2 _llgo_2: ; preds = %_llgo_1, %_llgo_0 @@ -296,25 +296,25 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) -declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) #1 +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) ; Function Attrs: nounwind -declare i1 @llvm.coro.alloc(token) #2 +declare i1 @llvm.coro.alloc(token) ; Function Attrs: nounwind memory(none) -declare i64 @llvm.coro.size.i64() #3 +declare i64 @llvm.coro.size.i64() ; Function Attrs: nounwind -declare ptr @llvm.coro.begin(token, ptr writeonly) #2 +declare ptr @llvm.coro.begin(token, ptr writeonly) ; Function Attrs: nounwind memory(argmem: read) -declare ptr @llvm.coro.free(token, ptr nocapture readonly) #4 +declare ptr @llvm.coro.free(token, ptr nocapture readonly) ; Function Attrs: nounwind -declare i1 @llvm.coro.end(ptr, i1, token) #2 +declare i1 @llvm.coro.end(ptr, i1, token) ; Function Attrs: cold noreturn nounwind memory(inaccessiblemem: write) -declare void @llvm.trap() #5 +declare void @llvm.trap() define void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %0, i64 %1) { _llgo_0: @@ -324,7 +324,7 @@ _llgo_0: } ; Function Attrs: nounwind -declare i8 @llvm.coro.suspend(token, i1) #2 +declare i8 @llvm.coro.suspend(token, i1) declare ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr, i64) @@ -336,7 +336,7 @@ declare i32 @sigsetjmp(ptr, i32) declare void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr) -define void @"github.com/goplus/llgo/cl/_testdata/async.init$after"() { +define void @"async.init$after"() { _llgo_0: %0 = load ptr, ptr @_llgo_string, align 8 %1 = icmp eq ptr %0, null @@ -401,16 +401,9 @@ _llgo_0: declare void @fmt.init() ; Function Attrs: nounwind memory(argmem: readwrite) -declare i1 @llvm.coro.done(ptr nocapture readonly) #6 +declare i1 @llvm.coro.done(ptr nocapture readonly) declare void @llvm.coro.resume(ptr) declare i32 @pthread_key_create(ptr, ptr) -attributes #0 = { "presplitcoroutine" } -attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) } -attributes #2 = { nounwind } -attributes #3 = { nounwind memory(none) } -attributes #4 = { nounwind memory(argmem: read) } -attributes #5 = { cold noreturn nounwind memory(inaccessiblemem: write) } -attributes #6 = { nounwind memory(argmem: readwrite) } diff --git a/x/async/_demo/gendemo/gendemo.go b/x/async/_demo/gendemo/gendemo.go index b58b3e27..e835faf1 100644 --- a/x/async/_demo/gendemo/gendemo.go +++ b/x/async/_demo/gendemo/gendemo.go @@ -1,21 +1,26 @@ package main -import "github.com/goplus/llgo/x/async" +import ( + "fmt" + + "github.com/goplus/llgo/x/async" +) func GenInts() (co *async.Promise[int]) { - print("1") + println("gen: 1") co.Yield(1) - print("2") + println("gen: 2") co.Yield(2) - print("3") + println("gen: 3") co.Yield(3) - print("4") return } func main() { co := GenInts() for !co.Done() { - print(co.Next()) + fmt.Printf("got: %v\n", co.Value()) + co.Next() } + fmt.Printf("done\n") } From 578bc165c43e9022615a352ab92927b6cd8901ff Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 5 Aug 2024 20:52:34 +0800 Subject: [PATCH 26/27] test: fix cl and ssa tests --- cl/_testdata/async/in.go | 12 --- cl/_testdata/async/out.ll | 210 -------------------------------------- cl/compile_test.go | 2 +- ssa/cl_test.go | 2 +- 4 files changed, 2 insertions(+), 224 deletions(-) diff --git a/cl/_testdata/async/in.go b/cl/_testdata/async/in.go index a59eec6c..d83cdae8 100644 --- a/cl/_testdata/async/in.go +++ b/cl/_testdata/async/in.go @@ -1,8 +1,6 @@ package async import ( - "fmt" - "github.com/goplus/llgo/x/async" ) @@ -27,13 +25,3 @@ func UseGenInts() int { } return r } - -func GenIntsWithDefer() (co *async.Promise[int]) { - defer func() { - if r := recover(); r != nil { - fmt.Println("panic:", r) - } - }() - co.Yield(1) - panic("GenIntsWithDefer") -} diff --git a/cl/_testdata/async/out.ll b/cl/_testdata/async/out.ll index c6c0f612..9aff8cda 100644 --- a/cl/_testdata/async/out.ll +++ b/cl/_testdata/async/out.ll @@ -1,18 +1,9 @@ ; ModuleID = 'async' source_filename = "async" -%"github.com/goplus/llgo/internal/runtime.Defer" = type { ptr, i64, ptr, ptr, ptr } -%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } -%"github.com/goplus/llgo/internal/runtime.eface" = type { ptr, ptr } -%"github.com/goplus/llgo/internal/runtime.Slice" = type { ptr, i64, i64 } -%"github.com/goplus/llgo/internal/runtime.iface" = type { ptr, ptr } %"github.com/goplus/llgo/x/async.Promise[int]" = type { ptr, i64 } @"async.init$guard" = global i1 false, align 1 -@__llgo_defer = linkonce global i32 0, align 4 -@0 = private unnamed_addr constant [16 x i8] c"GenIntsWithDefer", align 1 -@_llgo_string = linkonce global ptr null, align 8 -@1 = private unnamed_addr constant [6 x i8] c"panic:", align 1 define ptr @async.GenInts() presplitcoroutine { entry: @@ -73,155 +64,6 @@ _llgo_8: ; preds = %_llgo_7 ] } -define ptr @async.GenIntsWithDefer() presplitcoroutine { -entry: - %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) - %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) - %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) - br i1 %need.dyn.alloc, label %alloc, label %_llgo_5 - -alloc: ; preds = %entry - %frame.size = call i64 @llvm.coro.size.i64() - %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) - br label %_llgo_5 - -clean: ; preds = %_llgo_6, %_llgo_9 - %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) - br label %suspend - -suspend: ; preds = %_llgo_6, %_llgo_9, %clean - %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) - ret ptr %promise - -trap: ; preds = %_llgo_6 - call void @llvm.trap() - unreachable - -_llgo_5: ; preds = %alloc, %entry - %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] - %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) - store ptr %hdl, ptr %promise, align 8 - %2 = alloca ptr, align 8 - %3 = call ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr %2, i64 8) - %4 = load i32, ptr @__llgo_defer, align 4 - %5 = call ptr @pthread_getspecific(i32 %4) - %6 = alloca i8, i64 196, align 1 - %7 = alloca i8, i64 40, align 1 - %8 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 0 - store ptr %6, ptr %8, align 8 - %9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 - store i64 0, ptr %9, align 4 - %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 2 - store ptr %5, ptr %10, align 8 - %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 - store ptr blockaddress(@async.GenIntsWithDefer, %_llgo_7), ptr %11, align 8 - %12 = call i32 @pthread_setspecific(i32 %4, ptr %7) - %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 1 - %14 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 3 - %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, i32 0, i32 4 - %16 = call i32 @sigsetjmp(ptr %6, i32 0) - %17 = icmp eq i32 %16, 0 - br i1 %17, label %_llgo_9, label %_llgo_10 - -_llgo_6: ; preds = %_llgo_9, %_llgo_8 - %18 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 0 - store ptr @0, ptr %19, align 8 - %20 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 1 - store i64 16, ptr %20, align 4 - %21 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %18, align 8 - %22 = load ptr, ptr @_llgo_string, align 8 - %23 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) - store %"github.com/goplus/llgo/internal/runtime.String" %21, ptr %23, align 8 - %24 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 - %25 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %24, i32 0, i32 0 - store ptr %22, ptr %25, align 8 - %26 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %24, i32 0, i32 1 - store ptr %23, ptr %26, align 8 - %27 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %24, align 8 - call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %27) - unreachable - %28 = load ptr, ptr %3, align 8 - %29 = call i8 @llvm.coro.suspend(token %id, i1 true) - switch i8 %29, label %suspend [ - i8 0, label %trap - i8 1, label %clean - ] - -_llgo_7: ; preds = %_llgo_10 - store ptr blockaddress(@async.GenIntsWithDefer, %_llgo_8), ptr %14, align 8 - %30 = load i64, ptr %13, align 4 - call void @"async.GenIntsWithDefer$1"() - %31 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %7, align 8 - %32 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %31, 2 - %33 = call i32 @pthread_setspecific(i32 %4, ptr %32) - %34 = load ptr, ptr %15, align 8 - indirectbr ptr %34, [label %_llgo_8] - -_llgo_8: ; preds = %_llgo_10, %_llgo_7 - call void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr %5) - br label %_llgo_6 - -_llgo_9: ; preds = %_llgo_5 - %35 = load ptr, ptr %3, align 8 - call void @"github.com/goplus/llgo/x/async.(*Promise).setValue[int]"(ptr %promise, i64 1) - %36 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %36, label %suspend [ - i8 0, label %_llgo_6 - i8 1, label %clean - ] - -_llgo_10: ; preds = %_llgo_5 - store ptr blockaddress(@async.GenIntsWithDefer, %_llgo_8), ptr %15, align 8 - %37 = load ptr, ptr %14, align 8 - indirectbr ptr %37, [label %_llgo_8, label %_llgo_7] - -_llgo_11: ; No predecessors! -} - -define void @"async.GenIntsWithDefer$1"() { -_llgo_0: - %0 = call %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/internal/runtime.Recover"() - %1 = call i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface" %0, %"github.com/goplus/llgo/internal/runtime.eface" zeroinitializer) - %2 = xor i1 %1, true - br i1 %2, label %_llgo_1, label %_llgo_2 - -_llgo_1: ; preds = %_llgo_0 - %3 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 32) - %4 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %3, i64 0 - %5 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %6 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %5, i32 0, i32 0 - store ptr @1, ptr %6, align 8 - %7 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %5, i32 0, i32 1 - store i64 6, ptr %7, align 4 - %8 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %5, align 8 - %9 = load ptr, ptr @_llgo_string, align 8 - %10 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) - store %"github.com/goplus/llgo/internal/runtime.String" %8, ptr %10, align 8 - %11 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 - %12 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, i32 0, i32 0 - store ptr %9, ptr %12, align 8 - %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, i32 0, i32 1 - store ptr %10, ptr %13, align 8 - %14 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %11, align 8 - store %"github.com/goplus/llgo/internal/runtime.eface" %14, ptr %4, align 8 - %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %3, i64 1 - store %"github.com/goplus/llgo/internal/runtime.eface" %0, ptr %15, align 8 - %16 = alloca %"github.com/goplus/llgo/internal/runtime.Slice", align 8 - %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 0 - store ptr %3, ptr %17, align 8 - %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 1 - store i64 2, ptr %18, align 4 - %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, i32 0, i32 2 - store i64 2, ptr %19, align 4 - %20 = load %"github.com/goplus/llgo/internal/runtime.Slice", ptr %16, align 8 - %21 = call { i64, %"github.com/goplus/llgo/internal/runtime.iface" } @fmt.Println(%"github.com/goplus/llgo/internal/runtime.Slice" %20) - br label %_llgo_2 - -_llgo_2: ; preds = %_llgo_1, %_llgo_0 - ret void -} - define i64 @async.UseGenInts() { _llgo_0: %0 = call ptr @async.WrapGenInts() @@ -285,8 +127,6 @@ _llgo_0: _llgo_1: ; preds = %_llgo_0 store i1 true, ptr @"async.init$guard", align 1 - call void @fmt.init() - call void @"async.init$after"() br label %_llgo_2 _llgo_2: ; preds = %_llgo_1, %_llgo_0 @@ -326,52 +166,6 @@ _llgo_0: ; Function Attrs: nounwind declare i8 @llvm.coro.suspend(token, i1) -declare ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr, i64) - -declare ptr @pthread_getspecific(i32) - -declare i32 @pthread_setspecific(i32, ptr) - -declare i32 @sigsetjmp(ptr, i32) - -declare void @"github.com/goplus/llgo/internal/runtime.Rethrow"(ptr) - -define void @"async.init$after"() { -_llgo_0: - %0 = load ptr, ptr @_llgo_string, align 8 - %1 = icmp eq ptr %0, null - br i1 %1, label %_llgo_1, label %_llgo_2 - -_llgo_1: ; preds = %_llgo_0 - %2 = call ptr @"github.com/goplus/llgo/internal/runtime.Basic"(i64 24) - store ptr %2, ptr @_llgo_string, align 8 - br label %_llgo_2 - -_llgo_2: ; preds = %_llgo_1, %_llgo_0 - %3 = load i32, ptr @__llgo_defer, align 4 - %4 = icmp eq i32 %3, 0 - br i1 %4, label %_llgo_3, label %_llgo_4 - -_llgo_3: ; preds = %_llgo_2 - %5 = call i32 @pthread_key_create(ptr @__llgo_defer, ptr null) - br label %_llgo_4 - -_llgo_4: ; preds = %_llgo_3, %_llgo_2 - ret void -} - -declare ptr @"github.com/goplus/llgo/internal/runtime.Basic"(i64) - -declare ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64) - -declare void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface") - -declare %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/internal/runtime.Recover"() - -declare i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface", %"github.com/goplus/llgo/internal/runtime.eface") - -declare { i64, %"github.com/goplus/llgo/internal/runtime.iface" } @fmt.Println(%"github.com/goplus/llgo/internal/runtime.Slice") - define i1 @"github.com/goplus/llgo/x/async.(*Promise).Done[int]"(ptr %0) { _llgo_0: %1 = getelementptr inbounds %"github.com/goplus/llgo/x/async.Promise[int]", ptr %0, i32 0, i32 0 @@ -398,12 +192,8 @@ _llgo_0: ret void } -declare void @fmt.init() - ; Function Attrs: nounwind memory(argmem: readwrite) declare i1 @llvm.coro.done(ptr nocapture readonly) declare void @llvm.coro.resume(ptr) -declare i32 @pthread_key_create(ptr, ptr) - diff --git a/cl/compile_test.go b/cl/compile_test.go index b1942b80..1bdd3ac1 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) { 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) { From 7b2747ce0c61942b19ab7efbc4e8510e3bdb0bc4 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Tue, 6 Aug 2024 14:40:38 +0800 Subject: [PATCH 27/27] asyncio: merge promise and coro frame allocation --- cl/_testdata/async/out.ll | 62 ++++++++++++++++++++------------------- cl/compile_test.go | 37 +++++++++++------------ ssa/coro.go | 20 +++++++++---- ssa/ssa_test.go | 26 ++++++++++++++++ 4 files changed, 91 insertions(+), 54 deletions(-) diff --git a/cl/_testdata/async/out.ll b/cl/_testdata/async/out.ll index 9aff8cda..3f76ea07 100644 --- a/cl/_testdata/async/out.ll +++ b/cl/_testdata/async/out.ll @@ -7,22 +7,23 @@ source_filename = "async" define ptr @async.GenInts() presplitcoroutine { entry: - %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %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 - %frame.size = call i64 @llvm.coro.size.i64() - %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + %0 = getelementptr ptr, ptr %promise, i64 16 br label %_llgo_5 clean: ; preds = %_llgo_8, %_llgo_7, %_llgo_6, %_llgo_5 - %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + %1 = call ptr @llvm.coro.free(token %id, ptr %hdl) br label %suspend suspend: ; preds = %_llgo_8, %_llgo_7, %_llgo_6, %_llgo_5, %clean - %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + %2 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) ret ptr %promise trap: ; preds = %_llgo_8 @@ -30,35 +31,35 @@ trap: ; preds = %_llgo_8 unreachable _llgo_5: ; preds = %alloc, %entry - %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] - %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + %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) - %2 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %2, label %suspend [ + %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) - %3 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %3, label %suspend [ + %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) - %4 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %4, label %suspend [ + %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 - %5 = call i8 @llvm.coro.suspend(token %id, i1 true) - switch i8 %5, label %suspend [ + %6 = call i8 @llvm.coro.suspend(token %id, i1 true) + switch i8 %6, label %suspend [ i8 0, label %trap i8 1, label %clean ] @@ -86,22 +87,23 @@ _llgo_3: ; preds = %_llgo_1, %_llgo_0 define ptr @async.WrapGenInts() presplitcoroutine { entry: - %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %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 - %frame.size = call i64 @llvm.coro.size.i64() - %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + %0 = getelementptr ptr, ptr %promise, i64 16 br label %_llgo_5 clean: ; preds = %_llgo_5 - %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + %1 = call ptr @llvm.coro.free(token %id, ptr %hdl) br label %suspend suspend: ; preds = %_llgo_5, %clean - %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + %2 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) ret ptr %promise trap: ; preds = %_llgo_5 @@ -109,12 +111,12 @@ trap: ; preds = %_llgo_5 unreachable _llgo_5: ; preds = %alloc, %entry - %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] - %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + %frame = phi ptr [ null, %entry ], [ %0, %alloc ] + %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame) store ptr %hdl, ptr %promise, align 8 - %2 = call ptr @async.GenInts() - %3 = call i8 @llvm.coro.suspend(token %id, i1 true) - switch i8 %3, label %suspend [ + %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 ] @@ -133,17 +135,17 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 ret void } -declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) - ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) -; Function Attrs: nounwind -declare i1 @llvm.coro.alloc(token) - ; 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) diff --git a/cl/compile_test.go b/cl/compile_test.go index 1bdd3ac1..1fbbd086 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -142,22 +142,23 @@ source_filename = "foo" define ptr @foo.GenInts() presplitcoroutine { entry: - %promise = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %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 - %frame.size = call i64 @llvm.coro.size.i64() - %frame = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 %frame.size) + %0 = getelementptr ptr, ptr %promise, i64 16 br label %_llgo_5 clean: ; preds = %_llgo_7, %_llgo_6, %_llgo_5 - %0 = call ptr @llvm.coro.free(token %id, ptr %hdl) + %1 = call ptr @llvm.coro.free(token %id, ptr %hdl) br label %suspend suspend: ; preds = %_llgo_7, %_llgo_6, %_llgo_5, %clean - %1 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) + %2 = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none) ret ptr %promise trap: ; preds = %_llgo_7 @@ -165,27 +166,27 @@ trap: ; preds = %_llgo_7 unreachable _llgo_5: ; preds = %alloc, %entry - %frame1 = phi ptr [ null, %entry ], [ %frame, %alloc ] - %hdl = call ptr @llvm.coro.begin(token %id, ptr %frame1) + %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) - %2 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %2, label %suspend [ + %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) - %3 = call i8 @llvm.coro.suspend(token %id, i1 false) - switch i8 %3, label %suspend [ + %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 - %4 = call i8 @llvm.coro.suspend(token %id, i1 true) - switch i8 %4, label %suspend [ + %5 = call i8 @llvm.coro.suspend(token %id, i1 true) + switch i8 %5, label %suspend [ i8 0, label %trap i8 1, label %clean ] @@ -204,17 +205,17 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 ret void } -declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) - ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) -; Function Attrs: nounwind -declare i1 @llvm.coro.alloc(token) - ; 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) diff --git a/ssa/coro.go b/ssa/coro.go index 9864e2f3..955a2b72 100644 --- a/ssa/coro.go +++ b/ssa/coro.go @@ -383,9 +383,14 @@ func (b Builder) EndAsync() { } /* + 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 @@ -408,20 +413,23 @@ func (b Builder) BeginAsync(fn Function, entryBlk, allocBlk, cleanBlk, suspdBlk, b.async = true b.SetBlock(entryBlk) - promiseSize := b.Const(constant.MakeUint64(b.Prog.SizeOf(promiseTy)), b.Prog.Int64()).SetName("promise.size") - promise := b.AllocZ(promiseSize).SetName("promise") - promise.Type = b.Prog.Pointer(promiseTy) - b.promise = promise 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) - frameSize := b.CoSizeI64().SetName("frame.size") - frame := b.AllocZ(frameSize).SetName("frame") + frame := b.OffsetPtr(promise, promiseSize) b.Jump(beginBlk) + b.SetBlock(beginBlk) phi := b.Phi(b.Prog.VoidPtr()) phi.SetName("frame") diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 3e5c1762..e6ae0eab 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -594,6 +594,32 @@ _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")