From d4a72bf661731f616cfd52acb43c01911ad80643 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Tue, 3 Sep 2024 15:58:34 +0800 Subject: [PATCH 1/9] async.Run/Await/Race/All --- x/async/_demo/monad/monad.go | 139 +++++++++++++++++++++++ x/async/async.go | 145 ++++++++++++++++++++++++ x/async/executor.go | 59 ++++++++++ x/cbind/cbind.go | 87 +++++++++++++++ x/io/io.go | 210 +++++++++++++++-------------------- x/tuple/tuple.go | 40 +++++++ 6 files changed, 557 insertions(+), 123 deletions(-) create mode 100644 x/async/_demo/monad/monad.go create mode 100644 x/async/async.go create mode 100644 x/async/executor.go create mode 100644 x/cbind/cbind.go create mode 100644 x/tuple/tuple.go diff --git a/x/async/_demo/monad/monad.go b/x/async/_demo/monad/monad.go new file mode 100644 index 00000000..336e91ea --- /dev/null +++ b/x/async/_demo/monad/monad.go @@ -0,0 +1,139 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/async/timeout" + "github.com/goplus/llgo/x/tuple" +) + +func ReadFile(fileName string) async.IO[tuple.Tuple2[[]byte, error]] { + return async.Async(func(resolve func(tuple.Tuple2[[]byte, error])) { + go func() { + bytes, err := os.ReadFile(fileName) + resolve(tuple.T2(bytes, err)) + }() + }) +} + +func WriteFile(fileName string, content []byte) async.IO[error] { + return async.Async(func(resolve func(error)) { + go func() { + err := os.WriteFile(fileName, content, 0644) + resolve(err) + }() + }) +} + +func sleep(i int, d time.Duration) async.IO[int] { + return async.Async(func(resolve func(int)) { + go func() { + c.Usleep(c.Uint(d.Microseconds())) + resolve(i) + }() + }) +} + +func main() { + RunIO() + RunAllAndRace() + RunTimeout() + RunSocket() +} + +func RunIO() { + async.Run(func() { + content, err := async.Await(ReadFile("1.txt")).Get() + if err != nil { + fmt.Printf("read err: %v\n", err) + return + } + fmt.Printf("read content: %s\n", content) + err = async.Await(WriteFile("2.txt", content)) + if err != nil { + fmt.Printf("write err: %v\n", err) + return + } + fmt.Printf("write done\n") + }) + + // Translated to in Go+: + + async.Run(func() { + async.BindIO(ReadFile("1.txt"), func(v tuple.Tuple2[[]byte, error]) { + content, err := v.Get() + if err != nil { + fmt.Printf("read err: %v\n", err) + return + } + fmt.Printf("read content: %s\n", content) + async.BindIO(WriteFile("2.txt", content), func(v error) { + err = v + if err != nil { + fmt.Printf("write err: %v\n", err) + return + } + fmt.Printf("write done\n") + }) + }) + }) +} + +func RunAllAndRace() { + async.Run(func() { + all := async.All(sleep(1, time.Second), sleep(2, time.Second*2), sleep(3, time.Second*3)) + async.BindIO(all, func(v []int) { + fmt.Printf("All: %v\n", v) + }) + }) + + async.Run(func() { + first := async.Race(sleep(1, time.Second), sleep(2, time.Second*2), sleep(3, time.Second*3)) + v := async.Await(first) + fmt.Printf("Race: %v\n", v) + }) + + // Translated to in Go+: + + async.Run(func() { + all := async.All(sleep(1, time.Second), sleep(2, time.Second*2), sleep(3, time.Second*3)) + async.BindIO(all, func(v []int) { + fmt.Printf("All: %v\n", v) + }) + }) + + async.Run(func() { + first := async.Race(sleep(1, time.Second), sleep(2, time.Second*2), sleep(3, time.Second*3)) + async.BindIO(first, func(v int) { + fmt.Printf("Race: %v\n", v) + }) + }) +} + +func RunTimeout() { + async.Run(func() { + fmt.Printf("Start 1 second timeout\n") + async.Await(timeout.Timeout(1 * time.Second)) + fmt.Printf("timeout\n") + }) + + // Translated to in Go+: + + async.Run(func() { + fmt.Printf("Start 1 second timeout\n") + async.BindIO(timeout.Timeout(1*time.Second), func(async.Void) { + fmt.Printf("timeout\n") + }) + }) +} + +func RunSocket() { + // async.Run(func() { + // tcp := io.NewTcp() + // tcp. + // }) +} diff --git a/x/async/async.go b/x/async/async.go new file mode 100644 index 00000000..cdf1d43a --- /dev/null +++ b/x/async/async.go @@ -0,0 +1,145 @@ +/* + * 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 ( + "context" + "unsafe" + _ "unsafe" + + "github.com/goplus/llgo/c/libuv" +) + +type Void = [0]byte + +type Future[T any] func() T + +type IO[T any] func(e *AsyncContext) Future[T] + +type Chain[T any] func(callback func(T)) + +func (f Future[T]) Do(callback func(T)) { + callback(f()) +} + +type AsyncContext struct { + context.Context + *Executor + Complete func() +} + +func Async[T any](fn func(resolve func(T))) IO[T] { + return func(ctx *AsyncContext) Future[T] { + var result T + var done bool + fn(func(t T) { + result = t + done = true + ctx.Complete() + }) + return func() T { + if !done { + panic("AsyncIO: Future accessed before completion") + } + return result + } + } +} + +type bindAsync struct { + libuv.Async + cb func() +} + +func BindIO[T any](call IO[T], callback func(T)) { + loop := Exec().L + a := &bindAsync{} + loop.Async(&a.Async, func(p *libuv.Async) { + (*bindAsync)(unsafe.Pointer(p)).cb() + }) + ctx := &AsyncContext{ + Context: context.Background(), + Executor: Exec(), + Complete: func() { + a.Async.Send() + }, + } + f := call(ctx) + a.cb = func() { + a.Async.Close(nil) + result := f() + callback(result) + } +} + +// ----------------------------------------------------------------------------- + +func Await[T1 any](call IO[T1]) (ret T1) { + ch := make(chan struct{}) + f := call(&AsyncContext{ + Context: context.Background(), + Executor: Exec(), + Complete: func() { + close(ch) + }, + }) + <-ch + return f() +} + +func Race[T1 any](calls ...IO[T1]) IO[T1] { + return Async(func(resolve func(T1)) { + done := false + for _, call := range calls { + var f Future[T1] + f = call(&AsyncContext{ + Context: context.Background(), + Executor: Exec(), + Complete: func() { + if done { + return + } + done = true + resolve(f()) + }, + }) + } + }) +} + +func All[T1 any](calls ...IO[T1]) IO[[]T1] { + return Async(func(resolve func([]T1)) { + n := len(calls) + results := make([]T1, n) + done := 0 + for i, call := range calls { + i := i + var f Future[T1] + f = call(&AsyncContext{ + Context: context.Background(), + Executor: Exec(), + Complete: func() { + results[i] = f() + done++ + if done == n { + resolve(results) + } + }, + }) + } + }) +} diff --git a/x/async/executor.go b/x/async/executor.go new file mode 100644 index 00000000..3306b541 --- /dev/null +++ b/x/async/executor.go @@ -0,0 +1,59 @@ +/* + * 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" + + "github.com/goplus/llgo/c/libuv" + "github.com/goplus/llgo/c/pthread" +) + +var execKey pthread.Key + +func init() { + execKey.Create(nil) +} + +type Executor struct { + L *libuv.Loop +} + +func Exec() *Executor { + v := execKey.Get() + if v == nil { + panic("async.Exec: no executor") + } + return (*Executor)(v) +} + +func setExec(e *Executor) { + execKey.Set(unsafe.Pointer(e)) +} + +func (e *Executor) Run() { + e.L.Run(libuv.RUN_DEFAULT) +} + +func Run(fn func()) { + loop := libuv.LoopNew() + exec := &Executor{loop} + setExec(exec) + fn() + exec.Run() + loop.Close() +} diff --git a/x/cbind/cbind.go b/x/cbind/cbind.go new file mode 100644 index 00000000..60e6d24a --- /dev/null +++ b/x/cbind/cbind.go @@ -0,0 +1,87 @@ +/* + * 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 cbind + +import "unsafe" + +type bind[Base any] struct { + b Base + fn func() +} + +type bind1[Base any, A any] struct { + b Base + fn func(A) +} + +type bind2[Base any, A any, B any] struct { + b Base + fn func(A, B) +} + +type bind3[Base any, A any, B any, C any] struct { + b Base + fn func(A, B, C) +} + +func callback[Base any](b *Base) { + bind := (*bind[Base])(unsafe.Pointer(b)) + bind.fn() +} + +func callback1[Base any, A any](b *Base, a A) { + bind := (*bind1[Base, A])(unsafe.Pointer(b)) + bind.fn(a) +} + +func callback2[Base any, A any, B any](b *Base, a A, c B) { + bind := (*bind2[Base, A, B])(unsafe.Pointer(b)) + bind.fn(a, c) +} + +func callback3[Base any, A any, B any, C any](b *Base, a A, c B, d C) { + bind := (*bind3[Base, A, B, C])(unsafe.Pointer(b)) + bind.fn(a, c, d) +} + +func Bind[T any](call func()) (p *T, fn func(*T)) { + bb := &bind[T]{fn: func() { call() }} + p = (*T)(unsafe.Pointer(bb)) + fn = callback[T] + return +} + +func Bind1[T any, A any](call func(A)) (p *T, fn func(*T, A)) { + bb := &bind1[T, A]{fn: func(a A) { call(a) }} + p = (*T)(unsafe.Pointer(bb)) + fn = callback1[T, A] + return +} + +func Bind2[T any, A any, B any](call func(A, B)) (p *T, fn func(*T, A, B)) { + bb := &bind2[T, A, B]{fn: func(a A, b B) { call(a, b) }} + p = (*T)(unsafe.Pointer(bb)) + fn = callback2[T, A, B] + return +} + +func Bind3[T any, A any, B any, C any](call func(A, B, C), a A, b B, c C) (p *T, fn func(*T, A, B, C)) { + bb := &bind3[T, A, B, C]{fn: func(a A, b B, c C) { call(a, b, c) }} + p = (*T)(unsafe.Pointer(bb)) + fn = callback3[T, A, B, C] + return +} diff --git a/x/io/io.go b/x/io/io.go index f4b4c18b..053da3bf 100644 --- a/x/io/io.go +++ b/x/io/io.go @@ -17,154 +17,118 @@ package io import ( + "unsafe" _ "unsafe" - "time" + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/libuv" + "github.com/goplus/llgo/c/net" + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/cbind" + "github.com/goplus/llgo/x/tuple" ) -const ( - LLGoPackage = "decl" -) - -type Void = [0]byte - -// ----------------------------------------------------------------------------- - -type AsyncCall[OutT any] interface { - Await(timeout ...time.Duration) (ret OutT, err error) - Chan() <-chan OutT - EnsureDone() +type Tcp struct { + tcp *libuv.Tcp } -// llgo:link AsyncCall.Await llgo.await -func Await[OutT any](call AsyncCall[OutT], timeout ...time.Duration) (ret OutT, err error) { - return +type libuvError libuv.Errno + +func (e libuvError) Error() string { + s := libuv.Strerror(libuv.Errno(e)) + return c.GoString(s, c.Strlen(s)) } -//go:linkname Timeout llgo.timeout -func Timeout(time.Duration) (ret AsyncCall[Void]) +func NewTcp() *Tcp { + t := &Tcp{&libuv.Tcp{}} + libuv.InitTcp(async.Exec().L, t.tcp) + return t +} -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 (t *Tcp) Bind(addr *net.SockAddr, flags uint) error { + if res := t.tcp.Bind(addr, c.Uint(flags)); res != 0 { + return libuvError(res) } - return P -} - -// llgo:link Race llgo.race -func Race[OutT any](acs ...AsyncCall[OutT]) (ret *PromiseImpl[OutT]) { - return -} - -func All[OutT any](acs []AsyncCall[OutT]) (ret *PromiseImpl[[]OutT]) { return nil } -// llgo:link Await2 llgo.await -func Await2[OutT1, OutT2 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], - timeout ...time.Duration) (ret1 OutT1, ret2 OutT2, err error) { - return -} - -type Await2Result[T1 any, T2 any] struct { - V1 T1 - V2 T2 - Err error -} - -func Await2Compiled[OutT1, OutT2 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], - timeout ...time.Duration) (ret *PromiseImpl[Await2Result[OutT1, OutT2]]) { - return -} - -// llgo:link Await3 llgo.await -func Await3[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], - timeout ...time.Duration) (ret1 OutT1, ret2 OutT2, ret3 OutT3, err error) { - return -} - -type Await3Result[T1 any, T2 any, T3 any] struct { - V1 T1 - V2 T2 - V3 T3 - Err error -} - -func Await3Compiled[OutT1, OutT2, OutT3 any]( - ac1 AsyncCall[OutT1], ac2 AsyncCall[OutT2], ac3 AsyncCall[OutT3], - timeout ...time.Duration) (ret *PromiseImpl[Await3Result[OutT1, OutT2, OutT3]]) { - return -} - -func Run(ac AsyncCall[Void]) { - p := ac.(*PromiseImpl[Void]) - p.Resume() - <-ac.Chan() -} - -// ----------------------------------------------------------------------------- - -type Promise[OutT any] func(OutT, error) - -// llgo:link Promise.Await llgo.await -func (p Promise[OutT]) Await(timeout ...time.Duration) (ret OutT, err error) { - return -} - -func (p Promise[OutT]) Chan() <-chan OutT { +func (t *Tcp) Listen(backlog int, cb libuv.ConnectionCb) error { + if res := (*libuv.Stream)(t.tcp).Listen(c.Int(backlog), cb); res != 0 { + return libuvError(res) + } return nil } -func (p Promise[OutT]) EnsureDone() { - +func (t *Tcp) Accept() (client *Tcp, err error) { + tcp := &libuv.Tcp{} + if res := libuv.InitTcp(async.Exec().L, tcp); res != 0 { + return nil, libuvError(res) + } + if res := (*libuv.Stream)(t.tcp).Accept((*libuv.Stream)(client.tcp)); res != 0 { + return nil, libuvError(res) + } + return &Tcp{tcp}, nil } -// ----------------------------------------------------------------------------- - -type PromiseImpl[TOut any] struct { - Func func(resolve func(TOut, error)) - Value TOut - Err error - Prev int - Next int - - c chan TOut -} - -func (p *PromiseImpl[TOut]) Resume() { - p.Func(func(v TOut, err error) { - p.Value = v - p.Err = err +func Connect(addr *net.SockAddr) async.IO[tuple.Tuple2[*Tcp, error]] { + return async.Async(func(resolve func(tuple.Tuple2[*Tcp, error])) { + tcp := &libuv.Tcp{} + if res := libuv.InitTcp(async.Exec().L, tcp); res != 0 { + resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) + return + } + req, cb := cbind.Bind1[libuv.Connect](func(status c.Int) { + if status != 0 { + resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(libuv.Errno(status)))) + } else { + resolve(tuple.T2[*Tcp, error](&Tcp{tcp}, nil)) + } + }) + if res := libuv.TcpConnect(req, tcp, addr, cb); res != 0 { + resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) + } }) } -func (p *PromiseImpl[TOut]) EnsureDone() { - if p.Next == -1 { - panic("Promise already done") - } +func allocBuffer(handle *libuv.Handle, suggestedSize uintptr, buf *libuv.Buf) { + buf.Base = (*c.Char)(c.Malloc(suggestedSize)) + buf.Len = suggestedSize } -func (p *PromiseImpl[TOut]) Chan() <-chan TOut { - if p.c == nil { - p.c = make(chan TOut, 1) - p.Func(func(v TOut, err error) { - p.Value = v - p.Err = err - p.c <- v +type slice struct { + data unsafe.Pointer + len int +} + +func goBytes(buf *int8, n int) []byte { + return *(*[]byte)(unsafe.Pointer(&slice{unsafe.Pointer(buf), n})) +} + +func (t *Tcp) Read() async.IO[tuple.Tuple2[[]byte, error]] { + return func(ctx *async.AsyncContext) async.Future[tuple.Tuple2[[]byte, error]] { + var result tuple.Tuple2[[]byte, error] + var done bool + tcp := (*libuv.Stream)(t.tcp) + libuv.ReadStart(tcp, allocBuffer, func(client *libuv.Stream, nread c.Long, buf *libuv.Buf) { + if nread > 0 { + result = tuple.T2[[]byte, error](goBytes(buf.Base, int(nread)), nil) + } else if nread < 0 { + result = tuple.T2[[]byte, error](nil, libuvError(libuv.Errno(nread))) + } else { + result = tuple.T2[[]byte, error](nil, nil) + } + done = true + ctx.Complete() }) + return func() tuple.Tuple2[[]byte, error] { + if !done { + panic("Tcp.Read: Future accessed before completion") + } + return result + } } - return p.c } -func (p *PromiseImpl[TOut]) Await(timeout ...time.Duration) (ret TOut, err error) { - panic("should not called") +func (t *Tcp) Close(cb libuv.CloseCb) { + (*libuv.Handle)(unsafe.Pointer(t.tcp)).Close(cb) } - -// ----------------------------------------------------------------------------- diff --git a/x/tuple/tuple.go b/x/tuple/tuple.go new file mode 100644 index 00000000..8613cc10 --- /dev/null +++ b/x/tuple/tuple.go @@ -0,0 +1,40 @@ +package tuple + +type Tuple[T any] struct { + V T +} + +func T[T any](v T) Tuple[T] { + return Tuple[T]{V: v} +} + +func (t Tuple[T]) Get() T { + return t.V +} + +type Tuple2[T1 any, T2 any] struct { + V1 T1 + V2 T2 +} + +func T2[T1 any, T2 any](v1 T1, v2 T2) Tuple2[T1, T2] { + return Tuple2[T1, T2]{V1: v1, V2: v2} +} + +func (t Tuple2[T1, T2]) Get() (T1, T2) { + return t.V1, t.V2 +} + +type Tuple3[T1 any, T2 any, T3 any] struct { + V1 T1 + V2 T2 + V3 T3 +} + +func T3[T1 any, T2 any, T3 any](v1 T1, v2 T2, v3 T3) Tuple3[T1, T2, T3] { + return Tuple3[T1, T2, T3]{V1: v1, V2: v2, V3: v3} +} + +func (t Tuple3[T1, T2, T3]) Get() (T1, T2, T3) { + return t.V1, t.V2, t.V3 +} From 1a158b5de321c9ad0996dd88d27801a85a280750 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Wed, 4 Sep 2024 17:08:08 +0800 Subject: [PATCH 2/9] async: work both go and llgo --- x/async/_demo/monad/monad.go | 41 +++++--- x/async/async.go | 103 ++------------------ x/async/async_go.go | 91 +++++++++++++++++ x/async/async_llgo.go | 113 ++++++++++++++++++++++ x/async/executor_go.go | 33 +++++++ x/async/{executor.go => executor_llgo.go} | 4 + x/async/timeout/timeout_go.go | 35 +++++++ x/async/timeout/timeout_llgo.go | 44 +++++++++ x/cbind/buf.go | 12 +++ x/cbind/cbind.go | 65 ++++++++++--- x/io/io.go | 51 +++++----- 11 files changed, 441 insertions(+), 151 deletions(-) create mode 100644 x/async/async_go.go create mode 100644 x/async/async_llgo.go create mode 100644 x/async/executor_go.go rename x/async/{executor.go => executor_llgo.go} (96%) create mode 100644 x/async/timeout/timeout_go.go create mode 100644 x/async/timeout/timeout_llgo.go create mode 100644 x/cbind/buf.go diff --git a/x/async/_demo/monad/monad.go b/x/async/_demo/monad/monad.go index 336e91ea..4e326932 100644 --- a/x/async/_demo/monad/monad.go +++ b/x/async/_demo/monad/monad.go @@ -5,7 +5,6 @@ import ( "os" "time" - "github.com/goplus/llgo/c" "github.com/goplus/llgo/x/async" "github.com/goplus/llgo/x/async/timeout" "github.com/goplus/llgo/x/tuple" @@ -31,10 +30,9 @@ func WriteFile(fileName string, content []byte) async.IO[error] { func sleep(i int, d time.Duration) async.IO[int] { return async.Async(func(resolve func(int)) { - go func() { - c.Usleep(c.Uint(d.Microseconds())) + async.BindIO(timeout.Timeout(d), func(async.Void) { resolve(i) - }() + }) }) } @@ -46,6 +44,8 @@ func main() { } func RunIO() { + println("RunIO with Await") + async.Run(func() { content, err := async.Await(ReadFile("1.txt")).Get() if err != nil { @@ -62,6 +62,7 @@ func RunIO() { }) // Translated to in Go+: + println("RunIO with BindIO") async.Run(func() { async.BindIO(ReadFile("1.txt"), func(v tuple.Tuple2[[]byte, error]) { @@ -84,30 +85,42 @@ func RunIO() { } func RunAllAndRace() { + ms100 := 100 * time.Millisecond + ms200 := 200 * time.Millisecond + ms300 := 300 * time.Millisecond + + println("Run All with Await") + async.Run(func() { - all := async.All(sleep(1, time.Second), sleep(2, time.Second*2), sleep(3, time.Second*3)) + all := async.All(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) async.BindIO(all, func(v []int) { fmt.Printf("All: %v\n", v) }) }) + println("Run Race with Await") + async.Run(func() { - first := async.Race(sleep(1, time.Second), sleep(2, time.Second*2), sleep(3, time.Second*3)) + first := async.Race(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) v := async.Await(first) fmt.Printf("Race: %v\n", v) }) // Translated to in Go+: + println("Run All with BindIO") + async.Run(func() { - all := async.All(sleep(1, time.Second), sleep(2, time.Second*2), sleep(3, time.Second*3)) + all := async.All(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) async.BindIO(all, func(v []int) { fmt.Printf("All: %v\n", v) }) }) + println("Run Race with BindIO") + async.Run(func() { - first := async.Race(sleep(1, time.Second), sleep(2, time.Second*2), sleep(3, time.Second*3)) + first := async.Race(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) async.BindIO(first, func(v int) { fmt.Printf("Race: %v\n", v) }) @@ -115,17 +128,21 @@ func RunAllAndRace() { } func RunTimeout() { + println("Run Timeout with Await") + async.Run(func() { - fmt.Printf("Start 1 second timeout\n") - async.Await(timeout.Timeout(1 * time.Second)) + fmt.Printf("Start 100 ms timeout\n") + async.Await(timeout.Timeout(100 * time.Millisecond)) fmt.Printf("timeout\n") }) // Translated to in Go+: + println("Run Timeout with BindIO") + async.Run(func() { - fmt.Printf("Start 1 second timeout\n") - async.BindIO(timeout.Timeout(1*time.Second), func(async.Void) { + fmt.Printf("Start 100 ms timeout\n") + async.BindIO(timeout.Timeout(100*time.Millisecond), func(async.Void) { fmt.Printf("timeout\n") }) }) diff --git a/x/async/async.go b/x/async/async.go index cdf1d43a..8f7f0d64 100644 --- a/x/async/async.go +++ b/x/async/async.go @@ -17,11 +17,7 @@ package async import ( - "context" - "unsafe" _ "unsafe" - - "github.com/goplus/llgo/c/libuv" ) type Void = [0]byte @@ -30,16 +26,13 @@ type Future[T any] func() T type IO[T any] func(e *AsyncContext) Future[T] -type Chain[T any] func(callback func(T)) - -func (f Future[T]) Do(callback func(T)) { - callback(f()) +type AsyncContext struct { + *Executor + complete func() } -type AsyncContext struct { - context.Context - *Executor - Complete func() +func (ctx *AsyncContext) Complete() { + ctx.complete() } func Async[T any](fn func(resolve func(T))) IO[T] { @@ -53,93 +46,9 @@ func Async[T any](fn func(resolve func(T))) IO[T] { }) return func() T { if !done { - panic("AsyncIO: Future accessed before completion") + panic("async.Async: Future accessed before completion") } return result } } } - -type bindAsync struct { - libuv.Async - cb func() -} - -func BindIO[T any](call IO[T], callback func(T)) { - loop := Exec().L - a := &bindAsync{} - loop.Async(&a.Async, func(p *libuv.Async) { - (*bindAsync)(unsafe.Pointer(p)).cb() - }) - ctx := &AsyncContext{ - Context: context.Background(), - Executor: Exec(), - Complete: func() { - a.Async.Send() - }, - } - f := call(ctx) - a.cb = func() { - a.Async.Close(nil) - result := f() - callback(result) - } -} - -// ----------------------------------------------------------------------------- - -func Await[T1 any](call IO[T1]) (ret T1) { - ch := make(chan struct{}) - f := call(&AsyncContext{ - Context: context.Background(), - Executor: Exec(), - Complete: func() { - close(ch) - }, - }) - <-ch - return f() -} - -func Race[T1 any](calls ...IO[T1]) IO[T1] { - return Async(func(resolve func(T1)) { - done := false - for _, call := range calls { - var f Future[T1] - f = call(&AsyncContext{ - Context: context.Background(), - Executor: Exec(), - Complete: func() { - if done { - return - } - done = true - resolve(f()) - }, - }) - } - }) -} - -func All[T1 any](calls ...IO[T1]) IO[[]T1] { - return Async(func(resolve func([]T1)) { - n := len(calls) - results := make([]T1, n) - done := 0 - for i, call := range calls { - i := i - var f Future[T1] - f = call(&AsyncContext{ - Context: context.Background(), - Executor: Exec(), - Complete: func() { - results[i] = f() - done++ - if done == n { - resolve(results) - } - }, - }) - } - }) -} diff --git a/x/async/async_go.go b/x/async/async_go.go new file mode 100644 index 00000000..77d0cada --- /dev/null +++ b/x/async/async_go.go @@ -0,0 +1,91 @@ +//go:build !llgo +// +build !llgo + +/* + * 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 "sync" + +func BindIO[T any](call IO[T], callback func(T)) { + callback(Await(call)) +} + +func Await[T1 any](call IO[T1]) (ret T1) { + ch := make(chan struct{}) + f := call(&AsyncContext{ + Executor: Exec(), + complete: func() { + close(ch) + }, + }) + <-ch + return f() +} + +// ----------------------------------------------------------------------------- + +func Race[T1 any](calls ...IO[T1]) IO[T1] { + return Async(func(resolve func(T1)) { + ch := make(chan int, len(calls)) + futures := make([]Future[T1], len(calls)) + for i, call := range calls { + i := i + call := call + go func() { + f := call(&AsyncContext{ + Executor: Exec(), + complete: func() { + defer func() { + _ = recover() + }() + ch <- i + }, + }) + futures[i] = f + }() + } + i := <-ch + close(ch) + resolve(futures[i]()) + }) +} + +func All[T1 any](calls ...IO[T1]) IO[[]T1] { + return Async(func(resolve func([]T1)) { + n := len(calls) + results := make([]T1, n) + futures := make([]Future[T1], n) + wg := sync.WaitGroup{} + wg.Add(n) + for i, call := range calls { + i := i + f := call(&AsyncContext{ + Executor: Exec(), + complete: func() { + wg.Done() + }, + }) + futures[i] = f + } + wg.Wait() + for i, f := range futures { + results[i] = f() + } + resolve(results) + }) +} diff --git a/x/async/async_llgo.go b/x/async/async_llgo.go new file mode 100644 index 00000000..5b63434a --- /dev/null +++ b/x/async/async_llgo.go @@ -0,0 +1,113 @@ +//go:build llgo +// +build llgo + +/* + * 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 ( + "sync/atomic" + "unsafe" + + "github.com/goplus/llgo/c/libuv" +) + +type bindAsync struct { + libuv.Async + cb func() +} + +func BindIO[T any](call IO[T], callback func(T)) { + loop := Exec().L + a := &bindAsync{} + loop.Async(&a.Async, func(p *libuv.Async) { + (*bindAsync)(unsafe.Pointer(p)).cb() + }) + done := atomic.Bool{} + ctx := &AsyncContext{ + Executor: Exec(), + complete: func() { + done.Store(true) + a.Async.Send() + }, + } + f := call(ctx) + called := false + a.cb = func() { + if called { + return + } + a.Async.Close(nil) + result := f() + callback(result) + } + // don't delay the callback if the future is already done + if done.Load() { + called = true + a.cb() + } +} + +func Await[T1 any](call IO[T1]) (ret T1) { + BindIO(call, func(v T1) { + ret = v + }) + return +} + +// ----------------------------------------------------------------------------- + +func Race[T1 any](calls ...IO[T1]) IO[T1] { + return Async(func(resolve func(T1)) { + done := false + for _, call := range calls { + var f Future[T1] + f = call(&AsyncContext{ + Executor: Exec(), + complete: func() { + if done { + return + } + done = true + resolve(f()) + }, + }) + } + }) +} + +func All[T1 any](calls ...IO[T1]) IO[[]T1] { + return Async(func(resolve func([]T1)) { + n := len(calls) + results := make([]T1, n) + done := 0 + for i, call := range calls { + i := i + var f Future[T1] + f = call(&AsyncContext{ + Executor: Exec(), + complete: func() { + results[i] = f() + done++ + if done == n { + resolve(results) + } + }, + }) + } + }) +} diff --git a/x/async/executor_go.go b/x/async/executor_go.go new file mode 100644 index 00000000..95151eeb --- /dev/null +++ b/x/async/executor_go.go @@ -0,0 +1,33 @@ +//go:build !llgo +// +build !llgo + +/* + * 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 + +var exec = &Executor{} + +type Executor struct { +} + +func Exec() *Executor { + return exec +} + +func Run(fn func()) { + fn() +} diff --git a/x/async/executor.go b/x/async/executor_llgo.go similarity index 96% rename from x/async/executor.go rename to x/async/executor_llgo.go index 3306b541..10611185 100644 --- a/x/async/executor.go +++ b/x/async/executor_llgo.go @@ -1,3 +1,6 @@ +//go:build llgo +// +build llgo + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -56,4 +59,5 @@ func Run(fn func()) { fn() exec.Run() loop.Close() + setExec(nil) } diff --git a/x/async/timeout/timeout_go.go b/x/async/timeout/timeout_go.go new file mode 100644 index 00000000..b58121f2 --- /dev/null +++ b/x/async/timeout/timeout_go.go @@ -0,0 +1,35 @@ +//go:build !llgo +// +build !llgo + +/* + * 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 timeout + +import ( + "time" + + "github.com/goplus/llgo/x/async" +) + +func Timeout(d time.Duration) async.IO[async.Void] { + return async.Async(func(resolve func(async.Void)) { + go func() { + time.Sleep(d) + resolve(async.Void{}) + }() + }) +} diff --git a/x/async/timeout/timeout_llgo.go b/x/async/timeout/timeout_llgo.go new file mode 100644 index 00000000..42ed91e5 --- /dev/null +++ b/x/async/timeout/timeout_llgo.go @@ -0,0 +1,44 @@ +//go:build llgo +// +build llgo + +/* + * 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 timeout + +import ( + "time" + + "github.com/goplus/llgo/c/libuv" + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/cbind" +) + +func Timeout(d time.Duration) async.IO[async.Void] { + return async.Async(func(resolve func(async.Void)) { + t, _ := cbind.Bind[libuv.Timer](func() { + resolve(async.Void{}) + }) + r := libuv.InitTimer(async.Exec().L, t) + if r != 0 { + panic("InitTimer failed") + } + r = t.Start(cbind.Callback[libuv.Timer], uint64(d/time.Millisecond), 0) + if r != 0 { + panic("Start failed") + } + }) +} diff --git a/x/cbind/buf.go b/x/cbind/buf.go new file mode 100644 index 00000000..3e1f2e67 --- /dev/null +++ b/x/cbind/buf.go @@ -0,0 +1,12 @@ +package cbind + +import "unsafe" + +type slice struct { + data unsafe.Pointer + len int +} + +func GoBytes(buf *int8, n int) []byte { + return *(*[]byte)(unsafe.Pointer(&slice{unsafe.Pointer(buf), n})) +} diff --git a/x/cbind/cbind.go b/x/cbind/cbind.go index 60e6d24a..b34cb05a 100644 --- a/x/cbind/cbind.go +++ b/x/cbind/cbind.go @@ -16,7 +16,21 @@ package cbind -import "unsafe" +import ( + "unsafe" +) + +// llgo:type C +type Cb[T any] func(*T) + +// llgo:type C +type Cb1[T any, A any] func(*T, A) + +// llgo:type C +type Cb2[T any, A any, B any] func(*T, A, B) + +// llgo:type C +type Cb3[T any, A any, B any, C any] func(*T, A, B, C) type bind[Base any] struct { b Base @@ -38,50 +52,75 @@ type bind3[Base any, A any, B any, C any] struct { fn func(A, B, C) } -func callback[Base any](b *Base) { +func Callback[Base any](b *Base) { bind := (*bind[Base])(unsafe.Pointer(b)) bind.fn() } -func callback1[Base any, A any](b *Base, a A) { +func Callback1[Base any, A any](b *Base, a A) { bind := (*bind1[Base, A])(unsafe.Pointer(b)) bind.fn(a) } -func callback2[Base any, A any, B any](b *Base, a A, c B) { +func Callback2[Base any, A any, B any](b *Base, a A, c B) { bind := (*bind2[Base, A, B])(unsafe.Pointer(b)) bind.fn(a, c) } -func callback3[Base any, A any, B any, C any](b *Base, a A, c B, d C) { +func Callback3[Base any, A any, B any, C any](b *Base, a A, c B, d C) { bind := (*bind3[Base, A, B, C])(unsafe.Pointer(b)) bind.fn(a, c, d) } -func Bind[T any](call func()) (p *T, fn func(*T)) { +/** + * Bind[N] binds a Go function to a C callback function. + * + * Example: + * + * timer, cb := cbind.Bind[libuv.Timer](func() { + * println("hello") + * }) + * libuv.InitTimer(async.Exec().L, timer) + * timer.Start(cb, 1000, 0) + * + * TODO(lijie): fn isn't a C func-ptr, it's closure, should fix the LLGo compiler. + * See: https://github.com/goplus/llgo/issues/766 + * + * Workaround: + * + * timer, _ := cbind.Bind[libuv.Timer](func() { + * println("hello") + * }) + * libuv.InitTimer(async.Exec().L, timer) + * timer.Start(cbind.Callback[libuv.Timer], 1000, 0) + * + * @param call The Go function to bind. + * @return The data pointer and the C callback function. + */ +func Bind[T any](call func()) (p *T, fn Cb[T]) { bb := &bind[T]{fn: func() { call() }} p = (*T)(unsafe.Pointer(bb)) - fn = callback[T] + fn = Callback[T] return } -func Bind1[T any, A any](call func(A)) (p *T, fn func(*T, A)) { +func Bind1[T any, A any](call func(A)) (p *T, fn Cb1[T, A]) { bb := &bind1[T, A]{fn: func(a A) { call(a) }} p = (*T)(unsafe.Pointer(bb)) - fn = callback1[T, A] + fn = Callback1[T, A] return } -func Bind2[T any, A any, B any](call func(A, B)) (p *T, fn func(*T, A, B)) { +func Bind2[T any, A any, B any](call func(A, B)) (p *T, fn Cb2[T, A, B]) { bb := &bind2[T, A, B]{fn: func(a A, b B) { call(a, b) }} p = (*T)(unsafe.Pointer(bb)) - fn = callback2[T, A, B] + fn = Callback2[T, A, B] return } -func Bind3[T any, A any, B any, C any](call func(A, B, C), a A, b B, c C) (p *T, fn func(*T, A, B, C)) { +func Bind3[T any, A any, B any, C any](call func(A, B, C), a A, b B, c C) (p *T, fn Cb3[T, A, B, C]) { bb := &bind3[T, A, B, C]{fn: func(a A, b B, c C) { call(a, b, c) }} p = (*T)(unsafe.Pointer(bb)) - fn = callback3[T, A, B, C] + fn = Callback3[T, A, B, C] return } diff --git a/x/io/io.go b/x/io/io.go index 053da3bf..bc866b68 100644 --- a/x/io/io.go +++ b/x/io/io.go @@ -1,3 +1,6 @@ +//go:build llgo +// +build llgo + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -29,7 +32,7 @@ import ( ) type Tcp struct { - tcp *libuv.Tcp + tcp libuv.Tcp } type libuvError libuv.Errno @@ -40,8 +43,8 @@ func (e libuvError) Error() string { } func NewTcp() *Tcp { - t := &Tcp{&libuv.Tcp{}} - libuv.InitTcp(async.Exec().L, t.tcp) + t := &Tcp{} + libuv.InitTcp(async.Exec().L, &t.tcp) return t } @@ -52,41 +55,40 @@ func (t *Tcp) Bind(addr *net.SockAddr, flags uint) error { return nil } -func (t *Tcp) Listen(backlog int, cb libuv.ConnectionCb) error { - if res := (*libuv.Stream)(t.tcp).Listen(c.Int(backlog), cb); res != 0 { +func (t *Tcp) Listen(backlog int, cb func(server *libuv.Stream, status c.Int)) error { + if res := (*libuv.Stream)(&t.tcp).Listen(c.Int(backlog), cb); res != 0 { return libuvError(res) } return nil } func (t *Tcp) Accept() (client *Tcp, err error) { - tcp := &libuv.Tcp{} - if res := libuv.InitTcp(async.Exec().L, tcp); res != 0 { + tcp := &Tcp{} + if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { return nil, libuvError(res) } - if res := (*libuv.Stream)(t.tcp).Accept((*libuv.Stream)(client.tcp)); res != 0 { + if res := (*libuv.Stream)(&t.tcp).Accept((*libuv.Stream)(&tcp.tcp)); res != 0 { return nil, libuvError(res) } - return &Tcp{tcp}, nil + return tcp, nil } func Connect(addr *net.SockAddr) async.IO[tuple.Tuple2[*Tcp, error]] { return async.Async(func(resolve func(tuple.Tuple2[*Tcp, error])) { - tcp := &libuv.Tcp{} - if res := libuv.InitTcp(async.Exec().L, tcp); res != 0 { + tcp := &Tcp{} + if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) return } - req, cb := cbind.Bind1[libuv.Connect](func(status c.Int) { + req, _ := cbind.Bind1[libuv.Connect](func(status c.Int) { if status != 0 { resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(libuv.Errno(status)))) - } else { - resolve(tuple.T2[*Tcp, error](&Tcp{tcp}, nil)) } }) - if res := libuv.TcpConnect(req, tcp, addr, cb); res != 0 { + if res := libuv.TcpConnect(req, &tcp.tcp, addr, cbind.Callback1[libuv.Connect, c.Int]); res != 0 { resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) } + resolve(tuple.T2[*Tcp, error](tcp, nil)) }) } @@ -95,23 +97,14 @@ func allocBuffer(handle *libuv.Handle, suggestedSize uintptr, buf *libuv.Buf) { buf.Len = suggestedSize } -type slice struct { - data unsafe.Pointer - len int -} - -func goBytes(buf *int8, n int) []byte { - return *(*[]byte)(unsafe.Pointer(&slice{unsafe.Pointer(buf), n})) -} - func (t *Tcp) Read() async.IO[tuple.Tuple2[[]byte, error]] { return func(ctx *async.AsyncContext) async.Future[tuple.Tuple2[[]byte, error]] { var result tuple.Tuple2[[]byte, error] var done bool - tcp := (*libuv.Stream)(t.tcp) - libuv.ReadStart(tcp, allocBuffer, func(client *libuv.Stream, nread c.Long, buf *libuv.Buf) { + tcp := (*libuv.Stream)(&t.tcp) + tcp.StartRead(allocBuffer, func(client *libuv.Stream, nread c.Long, buf *libuv.Buf) { if nread > 0 { - result = tuple.T2[[]byte, error](goBytes(buf.Base, int(nread)), nil) + result = tuple.T2[[]byte, error](cbind.GoBytes(buf.Base, int(nread)), nil) } else if nread < 0 { result = tuple.T2[[]byte, error](nil, libuvError(libuv.Errno(nread))) } else { @@ -129,6 +122,6 @@ func (t *Tcp) Read() async.IO[tuple.Tuple2[[]byte, error]] { } } -func (t *Tcp) Close(cb libuv.CloseCb) { - (*libuv.Handle)(unsafe.Pointer(t.tcp)).Close(cb) +func (t *Tcp) Close() { + (*libuv.Handle)(unsafe.Pointer(&t.tcp)).Close(nil) } From 276f2070eefedf0643906f800d751598ad155081 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 5 Sep 2024 08:31:02 +0800 Subject: [PATCH 3/9] hide tuple fields, only expose tuple.TN(...) and tuple.Get() --- x/tuple/tuple.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/x/tuple/tuple.go b/x/tuple/tuple.go index 8613cc10..1fbea192 100644 --- a/x/tuple/tuple.go +++ b/x/tuple/tuple.go @@ -1,40 +1,40 @@ package tuple type Tuple[T any] struct { - V T + v T } func T[T any](v T) Tuple[T] { - return Tuple[T]{V: v} + return Tuple[T]{v: v} } func (t Tuple[T]) Get() T { - return t.V + return t.v } type Tuple2[T1 any, T2 any] struct { - V1 T1 - V2 T2 + v1 T1 + v2 T2 } func T2[T1 any, T2 any](v1 T1, v2 T2) Tuple2[T1, T2] { - return Tuple2[T1, T2]{V1: v1, V2: v2} + return Tuple2[T1, T2]{v1: v1, v2: v2} } func (t Tuple2[T1, T2]) Get() (T1, T2) { - return t.V1, t.V2 + return t.v1, t.v2 } type Tuple3[T1 any, T2 any, T3 any] struct { - V1 T1 - V2 T2 - V3 T3 + v1 T1 + v2 T2 + v3 T3 } func T3[T1 any, T2 any, T3 any](v1 T1, v2 T2, v3 T3) Tuple3[T1, T2, T3] { - return Tuple3[T1, T2, T3]{V1: v1, V2: v2, V3: v3} + return Tuple3[T1, T2, T3]{v1: v1, v2: v2, v3: v3} } func (t Tuple3[T1, T2, T3]) Get() (T1, T2, T3) { - return t.V1, t.V2, t.V3 + return t.v1, t.v2, t.v3 } From 6e0a9b2b48e3176abb75c5659b667bf15896ccb2 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 5 Sep 2024 10:23:31 +0800 Subject: [PATCH 4/9] cbind.BindF --- x/async/timeout/timeout_llgo.go | 2 +- x/cbind/cbind.go | 52 +++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/x/async/timeout/timeout_llgo.go b/x/async/timeout/timeout_llgo.go index 42ed91e5..9532c413 100644 --- a/x/async/timeout/timeout_llgo.go +++ b/x/async/timeout/timeout_llgo.go @@ -29,7 +29,7 @@ import ( func Timeout(d time.Duration) async.IO[async.Void] { return async.Async(func(resolve func(async.Void)) { - t, _ := cbind.Bind[libuv.Timer](func() { + t, _ := cbind.BindF[libuv.Timer, libuv.TimerCb](func() { resolve(async.Void{}) }) r := libuv.InitTimer(async.Exec().L, t) diff --git a/x/cbind/cbind.go b/x/cbind/cbind.go index b34cb05a..47b68d2f 100644 --- a/x/cbind/cbind.go +++ b/x/cbind/cbind.go @@ -97,30 +97,58 @@ func Callback3[Base any, A any, B any, C any](b *Base, a A, c B, d C) { * @param call The Go function to bind. * @return The data pointer and the C callback function. */ -func Bind[T any](call func()) (p *T, fn Cb[T]) { - bb := &bind[T]{fn: func() { call() }} +func Bind[T any](call func()) (p *T, cb Cb[T]) { + bb := &bind[T]{fn: call} p = (*T)(unsafe.Pointer(bb)) - fn = Callback[T] + cb = Callback[T] return } -func Bind1[T any, A any](call func(A)) (p *T, fn Cb1[T, A]) { - bb := &bind1[T, A]{fn: func(a A) { call(a) }} +func BindF[T any, F ~func(*T)](call func()) (*T, F) { + bb := &bind[T]{fn: call} + p := (*T)(unsafe.Pointer(bb)) + fn := Callback[T] + return p, *(*F)(unsafe.Pointer(&fn)) +} + +func Bind1[T any, A any](call func(A)) (p *T, cb Cb1[T, A]) { + bb := &bind1[T, A]{fn: call} p = (*T)(unsafe.Pointer(bb)) - fn = Callback1[T, A] + cb = Callback1[T, A] return } -func Bind2[T any, A any, B any](call func(A, B)) (p *T, fn Cb2[T, A, B]) { - bb := &bind2[T, A, B]{fn: func(a A, b B) { call(a, b) }} +func Bind1F[T any, A any, F ~func(A)](call func(A)) (*T, F) { + bb := &bind1[T, A]{fn: call} + p := (*T)(unsafe.Pointer(bb)) + fn := Callback1[T, A] + return p, *(*F)(unsafe.Pointer(&fn)) +} + +func Bind2[T any, A any, B any](call func(A, B)) (p *T, cb Cb2[T, A, B]) { + bb := &bind2[T, A, B]{fn: call} p = (*T)(unsafe.Pointer(bb)) - fn = Callback2[T, A, B] + cb = Callback2[T, A, B] return } -func Bind3[T any, A any, B any, C any](call func(A, B, C), a A, b B, c C) (p *T, fn Cb3[T, A, B, C]) { - bb := &bind3[T, A, B, C]{fn: func(a A, b B, c C) { call(a, b, c) }} +func Bind2F[T any, A any, B any, F ~func(A, B)](call func(A, B)) (*T, F) { + bb := &bind2[T, A, B]{fn: call} + p := (*T)(unsafe.Pointer(bb)) + fn := Callback2[T, A, B] + return p, *(*F)(unsafe.Pointer(&fn)) +} + +func Bind3[T any, A any, B any, C any](call func(A, B, C), a A, b B, c C) (p *T, cb Cb3[T, A, B, C]) { + bb := &bind3[T, A, B, C]{fn: call} p = (*T)(unsafe.Pointer(bb)) - fn = Callback3[T, A, B, C] + cb = Callback3[T, A, B, C] return } + +func Bind3F[T any, A any, B any, C any, F ~func(A, B, C)](call func(A, B, C), a A, b B, c C) (*T, F) { + bb := &bind3[T, A, B, C]{fn: call} + p := (*T)(unsafe.Pointer(bb)) + fn := Callback3[T, A, B, C] + return p, *(*F)(unsafe.Pointer(&fn)) +} From a2d4e79c20c74ebec83c955f806cd91621e61cb1 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 5 Sep 2024 16:00:26 +0800 Subject: [PATCH 5/9] new future IO and demo --- x/async/_demo/all/all.go | 308 ++++++++++++++++++++++++++++++++ x/async/_demo/monad/monad.go | 156 ---------------- x/async/async.go | 51 +++--- x/async/async_go.go | 4 +- x/async/async_llgo.go | 92 ++-------- x/async/executor_go.go | 4 +- x/async/executor_llgo.go | 22 ++- x/async/timeout/timeout_go.go | 4 +- x/async/timeout/timeout_llgo.go | 9 +- x/cbind/buf.go | 4 + x/cbind/cbind.go | 42 ++--- x/io/README.md | 99 ++-------- x/io/io.go | 127 ------------- x/io/io_llgo.go | 291 ++++++++++++++++++++++++++++++ 14 files changed, 706 insertions(+), 507 deletions(-) create mode 100644 x/async/_demo/all/all.go delete mode 100644 x/async/_demo/monad/monad.go delete mode 100644 x/io/io.go create mode 100644 x/io/io_llgo.go diff --git a/x/async/_demo/all/all.go b/x/async/_demo/all/all.go new file mode 100644 index 00000000..56e0c4b6 --- /dev/null +++ b/x/async/_demo/all/all.go @@ -0,0 +1,308 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/net" + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/async/timeout" + "github.com/goplus/llgo/x/io" + "github.com/goplus/llgo/x/tuple" +) + +func ReadFile(fileName string) async.Future[tuple.Tuple2[[]byte, error]] { + return async.Async(func(resolve func(tuple.Tuple2[[]byte, error])) { + go func() { + println(async.Gettid(), "read file", fileName) + bytes, err := os.ReadFile(fileName) + resolve(tuple.T2(bytes, err)) + }() + }) +} + +func WriteFile(fileName string, content []byte) async.Future[error] { + return async.Async(func(resolve func(error)) { + go func() { + err := os.WriteFile(fileName, content, 0644) + resolve(err) + }() + }) +} + +func sleep(i int, d time.Duration) async.Future[int] { + return async.Async(func(resolve func(int)) { + timeout.Timeout(d)(func(async.Void) { + resolve(i) + }) + }) +} + +func main() { + RunIO() + RunAllAndRace() + RunTimeout() + RunSocket() +} + +func RunIO() { + println("RunIO with Await") + + async.Run(async.Async(func(resolve func(async.Void)) { + println("read file") + defer resolve(async.Void{}) + content, err := async.Await(ReadFile("1.txt")).Get() + if err != nil { + fmt.Printf("read err: %v\n", err) + return + } + fmt.Printf("read content: %s\n", content) + err = async.Await(WriteFile("2.txt", content)) + if err != nil { + fmt.Printf("write err: %v\n", err) + return + } + fmt.Printf("write done\n") + })) + + // Translated Await to BindIO in Go+: + println("RunIO with BindIO") + + async.Run(async.Async(func(resolve func(async.Void)) { + ReadFile("1.txt")(func(v tuple.Tuple2[[]byte, error]) { + content, err := v.Get() + if err != nil { + fmt.Printf("read err: %v\n", err) + resolve(async.Void{}) + return + } + fmt.Printf("read content: %s\n", content) + WriteFile("2.txt", content)(func(v error) { + err = v + if err != nil { + fmt.Printf("write err: %v\n", err) + resolve(async.Void{}) + return + } + println("write done") + resolve(async.Void{}) + }) + }) + })) +} + +func RunAllAndRace() { + ms100 := 100 * time.Millisecond + ms200 := 200 * time.Millisecond + ms300 := 300 * time.Millisecond + + println("Run All with Await") + + async.Run(async.Async(func(resolve func(async.Void)) { + async.All(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300))(func(v []int) { + fmt.Printf("All: %v\n", v) + resolve(async.Void{}) + }) + })) + + println("Run Race with Await") + + async.Run(async.Async(func(resolve func(async.Void)) { + first := async.Race(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) + v := async.Await(first) + fmt.Printf("Race: %v\n", v) + resolve(async.Void{}) + })) + + // Translated to in Go+: + + println("Run All with BindIO") + + async.Run(async.Async(func(resolve func(async.Void)) { + async.All(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300))(func(v []int) { + fmt.Printf("All: %v\n", v) + resolve(async.Void{}) + }) + })) + + println("Run Race with BindIO") + + async.Run(async.Async(func(resolve func(async.Void)) { + async.Race(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300))(func(v int) { + fmt.Printf("Race: %v\n", v) + resolve(async.Void{}) + }) + })) +} + +func RunTimeout() { + println("Run Timeout with Await") + + async.Run(async.Async(func(resolve func(async.Void)) { + fmt.Printf("Start 100 ms timeout\n") + async.Await(timeout.Timeout(100 * time.Millisecond)) + fmt.Printf("timeout\n") + resolve(async.Void{}) + })) + + // Translated to in Go+: + + println("Run Timeout with BindIO") + + async.Run(async.Async(func(resolve func(async.Void)) { + fmt.Printf("Start 100 ms timeout\n") + timeout.Timeout(100 * time.Millisecond)(func(async.Void) { + fmt.Printf("timeout\n") + resolve(async.Void{}) + }) + })) +} + +func RunSocket() { + println("Run Socket") + + async.Run(async.Async(func(resolve func(async.Void)) { + println("RunServer") + + RunServer()(func(async.Void) { + println("RunServer done") + resolve(async.Void{}) + }) + + println("RunClient") + + RunClient()(func(async.Void) { + println("RunClient done") + resolve(async.Void{}) + }) + })) +} + +func RunClient() async.Future[async.Void] { + return async.Async(func(resolve func(async.Void)) { + bindAddr := "127.0.0.1:3927" + io.ParseAddr(bindAddr)(func(v tuple.Tuple2[*net.SockAddr, error]) { + addr, err := v.Get() + println("Connect to", addr, err) + if err != nil { + panic(err) + } + io.Connect(addr)(func(v tuple.Tuple2[*io.Tcp, error]) { + client, err := v.Get() + println("Connected", client, err) + if err != nil { + panic(err) + } + var loop func(client *io.Tcp) + loop = func(client *io.Tcp) { + client.Write([]byte("Hello"))(func(err error) { + if err != nil { + panic(err) + } + client.Read()(func(v tuple.Tuple2[[]byte, error]) { + data, err := v.Get() + if err != nil { + panic(err) + } + println("Read:", string(data)) + timeout.Timeout(1 * time.Second)(func(async.Void) { + loop(client) + }) + }) + }) + } + loop(client) + }) + }) + }) +} + +func RunServer() async.Future[async.Void] { + return async.Async(func(resolve func(async.Void)) { + server, err := io.NewTcp() + if err != nil { + panic(err) + } + + bindAddr := "0.0.0.0:3927" + io.ParseAddr(bindAddr)(func(v tuple.Tuple2[*net.SockAddr, error]) { + addr, err := v.Get() + if err != nil { + panic(err) + } + + if err = server.Bind(addr, 0); err != nil { + panic(err) + } + c.Printf(c.Str("Listening on %s\n"), c.AllocaCStr(bindAddr)) + + err = server.Listen(128, func(server *io.Tcp, err error) { + if err != nil { + panic(err) + } + client, err := server.Accept() + println("Accept", client, err) + + var loop func(client *io.Tcp) + loop = func(client *io.Tcp) { + client.Read()(func(v tuple.Tuple2[[]byte, error]) { + data, err := v.Get() + if err != nil { + println("Read error", err) + } else { + println("Read:", string(data)) + client.Write(data)(func(err error) { + if err != nil { + println("Write error", err) + } else { + println("Write done") + loop(client) + } + }) + } + }) + } + loop(client) + }) + if err != nil { + panic(err) + } + }) + }) +} + +func RunServer1() async.Future[async.Void] { + return async.Async(func(resolve func(async.Void)) { + io.Listen("tcp", "0.0.0.0:3927")(func(v tuple.Tuple2[*io.Tcp, error]) { + server, err := v.Get() + if err != nil { + panic(err) + } + client, err := server.Accept() + println("Accept", client, err) + + var loop func(client *io.Tcp) + loop = func(client *io.Tcp) { + client.Read()(func(v tuple.Tuple2[[]byte, error]) { + data, err := v.Get() + if err != nil { + println("Read error", err) + } else { + println("Read:", string(data)) + client.Write(data)(func(err error) { + if err != nil { + println("Write error", err) + } else { + println("Write done") + loop(client) + } + }) + } + }) + } + loop(client) + }) + }) +} diff --git a/x/async/_demo/monad/monad.go b/x/async/_demo/monad/monad.go deleted file mode 100644 index 4e326932..00000000 --- a/x/async/_demo/monad/monad.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "fmt" - "os" - "time" - - "github.com/goplus/llgo/x/async" - "github.com/goplus/llgo/x/async/timeout" - "github.com/goplus/llgo/x/tuple" -) - -func ReadFile(fileName string) async.IO[tuple.Tuple2[[]byte, error]] { - return async.Async(func(resolve func(tuple.Tuple2[[]byte, error])) { - go func() { - bytes, err := os.ReadFile(fileName) - resolve(tuple.T2(bytes, err)) - }() - }) -} - -func WriteFile(fileName string, content []byte) async.IO[error] { - return async.Async(func(resolve func(error)) { - go func() { - err := os.WriteFile(fileName, content, 0644) - resolve(err) - }() - }) -} - -func sleep(i int, d time.Duration) async.IO[int] { - return async.Async(func(resolve func(int)) { - async.BindIO(timeout.Timeout(d), func(async.Void) { - resolve(i) - }) - }) -} - -func main() { - RunIO() - RunAllAndRace() - RunTimeout() - RunSocket() -} - -func RunIO() { - println("RunIO with Await") - - async.Run(func() { - content, err := async.Await(ReadFile("1.txt")).Get() - if err != nil { - fmt.Printf("read err: %v\n", err) - return - } - fmt.Printf("read content: %s\n", content) - err = async.Await(WriteFile("2.txt", content)) - if err != nil { - fmt.Printf("write err: %v\n", err) - return - } - fmt.Printf("write done\n") - }) - - // Translated to in Go+: - println("RunIO with BindIO") - - async.Run(func() { - async.BindIO(ReadFile("1.txt"), func(v tuple.Tuple2[[]byte, error]) { - content, err := v.Get() - if err != nil { - fmt.Printf("read err: %v\n", err) - return - } - fmt.Printf("read content: %s\n", content) - async.BindIO(WriteFile("2.txt", content), func(v error) { - err = v - if err != nil { - fmt.Printf("write err: %v\n", err) - return - } - fmt.Printf("write done\n") - }) - }) - }) -} - -func RunAllAndRace() { - ms100 := 100 * time.Millisecond - ms200 := 200 * time.Millisecond - ms300 := 300 * time.Millisecond - - println("Run All with Await") - - async.Run(func() { - all := async.All(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) - async.BindIO(all, func(v []int) { - fmt.Printf("All: %v\n", v) - }) - }) - - println("Run Race with Await") - - async.Run(func() { - first := async.Race(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) - v := async.Await(first) - fmt.Printf("Race: %v\n", v) - }) - - // Translated to in Go+: - - println("Run All with BindIO") - - async.Run(func() { - all := async.All(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) - async.BindIO(all, func(v []int) { - fmt.Printf("All: %v\n", v) - }) - }) - - println("Run Race with BindIO") - - async.Run(func() { - first := async.Race(sleep(1, ms200), sleep(2, ms100), sleep(3, ms300)) - async.BindIO(first, func(v int) { - fmt.Printf("Race: %v\n", v) - }) - }) -} - -func RunTimeout() { - println("Run Timeout with Await") - - async.Run(func() { - fmt.Printf("Start 100 ms timeout\n") - async.Await(timeout.Timeout(100 * time.Millisecond)) - fmt.Printf("timeout\n") - }) - - // Translated to in Go+: - - println("Run Timeout with BindIO") - - async.Run(func() { - fmt.Printf("Start 100 ms timeout\n") - async.BindIO(timeout.Timeout(100*time.Millisecond), func(async.Void) { - fmt.Printf("timeout\n") - }) - }) -} - -func RunSocket() { - // async.Run(func() { - // tcp := io.NewTcp() - // tcp. - // }) -} diff --git a/x/async/async.go b/x/async/async.go index 8f7f0d64..b1d4f220 100644 --- a/x/async/async.go +++ b/x/async/async.go @@ -17,38 +17,45 @@ package async import ( + "unsafe" _ "unsafe" + + "github.com/goplus/llgo/c/libuv" ) type Void = [0]byte -type Future[T any] func() T +type Future[T any] func(func(T)) -type IO[T any] func(e *AsyncContext) Future[T] - -type AsyncContext struct { - *Executor - complete func() +type asyncBind[T any] struct { + libuv.Async + result T + chain func(T) } -func (ctx *AsyncContext) Complete() { - ctx.complete() +func asyncCb[T any](a *libuv.Async) { + a.Close(nil) + aa := (*asyncBind[T])(unsafe.Pointer(a)) + aa.chain(aa.result) } -func Async[T any](fn func(resolve func(T))) IO[T] { - return func(ctx *AsyncContext) Future[T] { - var result T - var done bool - fn(func(t T) { - result = t - done = true - ctx.Complete() +func Async[T any](fn func(func(T))) Future[T] { + return func(chain func(T)) { + loop := Exec().L + // var result T + // var a *libuv.Async + // var cb libuv.AsyncCb + // a, cb = cbind.BindF[libuv.Async, libuv.AsyncCb](func() { + // a.Close(nil) + // chain(result) + // }) + // loop.Async(a, cb) + + aa := &asyncBind[T]{chain: chain} + loop.Async(&aa.Async, asyncCb[T]) + fn(func(v T) { + aa.result = v + aa.Send() }) - return func() T { - if !done { - panic("async.Async: Future accessed before completion") - } - return result - } } } diff --git a/x/async/async_go.go b/x/async/async_go.go index 77d0cada..cec40669 100644 --- a/x/async/async_go.go +++ b/x/async/async_go.go @@ -1,5 +1,5 @@ -//go:build !llgo -// +build !llgo +//go:build llgo11 +// +build llgo11 /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. diff --git a/x/async/async_llgo.go b/x/async/async_llgo.go index 5b63434a..1c1b0cb6 100644 --- a/x/async/async_llgo.go +++ b/x/async/async_llgo.go @@ -1,6 +1,3 @@ -//go:build llgo -// +build llgo - /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -21,92 +18,39 @@ package async import ( "sync/atomic" - "unsafe" - - "github.com/goplus/llgo/c/libuv" ) -type bindAsync struct { - libuv.Async - cb func() -} - -func BindIO[T any](call IO[T], callback func(T)) { - loop := Exec().L - a := &bindAsync{} - loop.Async(&a.Async, func(p *libuv.Async) { - (*bindAsync)(unsafe.Pointer(p)).cb() - }) - done := atomic.Bool{} - ctx := &AsyncContext{ - Executor: Exec(), - complete: func() { - done.Store(true) - a.Async.Send() - }, - } - f := call(ctx) - called := false - a.cb = func() { - if called { - return - } - a.Async.Close(nil) - result := f() - callback(result) - } - // don't delay the callback if the future is already done - if done.Load() { - called = true - a.cb() - } -} - -func Await[T1 any](call IO[T1]) (ret T1) { - BindIO(call, func(v T1) { - ret = v - }) - return +func Await[T1 any](call Future[T1]) (ret T1) { + return Run(call) } // ----------------------------------------------------------------------------- -func Race[T1 any](calls ...IO[T1]) IO[T1] { +func Race[T1 any](futures ...Future[T1]) Future[T1] { return Async(func(resolve func(T1)) { - done := false - for _, call := range calls { - var f Future[T1] - f = call(&AsyncContext{ - Executor: Exec(), - complete: func() { - if done { - return - } - done = true - resolve(f()) - }, + done := atomic.Bool{} + for _, future := range futures { + future(func(v T1) { + if !done.Swap(true) { + resolve(v) + } }) } }) } -func All[T1 any](calls ...IO[T1]) IO[[]T1] { +func All[T1 any](futures ...Future[T1]) Future[[]T1] { return Async(func(resolve func([]T1)) { - n := len(calls) + n := len(futures) results := make([]T1, n) - done := 0 - for i, call := range calls { + var done uint32 + for i, future := range futures { i := i - var f Future[T1] - f = call(&AsyncContext{ - Executor: Exec(), - complete: func() { - results[i] = f() - done++ - if done == n { - resolve(results) - } - }, + future(func(v T1) { + results[i] = v + if atomic.AddUint32(&done, 1) == uint32(n) { + resolve(results) + } }) } }) diff --git a/x/async/executor_go.go b/x/async/executor_go.go index 95151eeb..8ef70dc3 100644 --- a/x/async/executor_go.go +++ b/x/async/executor_go.go @@ -1,5 +1,5 @@ -//go:build !llgo -// +build !llgo +//go:build llgo11 +// +build llgo11 /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. diff --git a/x/async/executor_llgo.go b/x/async/executor_llgo.go index 10611185..3018cc3a 100644 --- a/x/async/executor_llgo.go +++ b/x/async/executor_llgo.go @@ -1,6 +1,3 @@ -//go:build llgo -// +build llgo - /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -22,10 +19,14 @@ package async import ( "unsafe" + "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/libuv" "github.com/goplus/llgo/c/pthread" ) +//go:linkname Gettid C.pthread_self +func Gettid() c.Pointer + var execKey pthread.Key func init() { @@ -44,20 +45,25 @@ func Exec() *Executor { return (*Executor)(v) } -func setExec(e *Executor) { +func setExec(e *Executor) (old *Executor) { + old = (*Executor)(execKey.Get()) execKey.Set(unsafe.Pointer(e)) + return } func (e *Executor) Run() { e.L.Run(libuv.RUN_DEFAULT) } -func Run(fn func()) { +func Run[T any](future Future[T]) (ret T) { loop := libuv.LoopNew() exec := &Executor{loop} - setExec(exec) - fn() + oldExec := setExec(exec) + future(func(v T) { + ret = v + }) exec.Run() loop.Close() - setExec(nil) + setExec(oldExec) + return } diff --git a/x/async/timeout/timeout_go.go b/x/async/timeout/timeout_go.go index b58121f2..2315c9a3 100644 --- a/x/async/timeout/timeout_go.go +++ b/x/async/timeout/timeout_go.go @@ -1,5 +1,5 @@ -//go:build !llgo -// +build !llgo +//go:build llgo11 +// +build llgo11 /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. diff --git a/x/async/timeout/timeout_llgo.go b/x/async/timeout/timeout_llgo.go index 9532c413..0bc07ab3 100644 --- a/x/async/timeout/timeout_llgo.go +++ b/x/async/timeout/timeout_llgo.go @@ -1,6 +1,3 @@ -//go:build llgo -// +build llgo - /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -27,16 +24,16 @@ import ( "github.com/goplus/llgo/x/cbind" ) -func Timeout(d time.Duration) async.IO[async.Void] { +func Timeout(d time.Duration) async.Future[async.Void] { return async.Async(func(resolve func(async.Void)) { - t, _ := cbind.BindF[libuv.Timer, libuv.TimerCb](func() { + t, cb := cbind.BindF[libuv.Timer, libuv.TimerCb](func(t *libuv.Timer) { resolve(async.Void{}) }) r := libuv.InitTimer(async.Exec().L, t) if r != 0 { panic("InitTimer failed") } - r = t.Start(cbind.Callback[libuv.Timer], uint64(d/time.Millisecond), 0) + r = t.Start(cb, uint64(d/time.Millisecond), 0) if r != 0 { panic("Start failed") } diff --git a/x/cbind/buf.go b/x/cbind/buf.go index 3e1f2e67..b142d6d3 100644 --- a/x/cbind/buf.go +++ b/x/cbind/buf.go @@ -10,3 +10,7 @@ type slice struct { func GoBytes(buf *int8, n int) []byte { return *(*[]byte)(unsafe.Pointer(&slice{unsafe.Pointer(buf), n})) } + +func CBuffer(data []byte) (*int8, int) { + return (*int8)(unsafe.Pointer(&data[0])), len(data) +} diff --git a/x/cbind/cbind.go b/x/cbind/cbind.go index 47b68d2f..7f267428 100644 --- a/x/cbind/cbind.go +++ b/x/cbind/cbind.go @@ -52,24 +52,24 @@ type bind3[Base any, A any, B any, C any] struct { fn func(A, B, C) } -func Callback[Base any](b *Base) { - bind := (*bind[Base])(unsafe.Pointer(b)) +func Callback[Base any](base *Base) { + bind := (*bind[Base])(unsafe.Pointer(base)) bind.fn() } -func Callback1[Base any, A any](b *Base, a A) { - bind := (*bind1[Base, A])(unsafe.Pointer(b)) +func Callback1[Base any, A any](base *Base, a A) { + bind := (*bind1[Base, A])(unsafe.Pointer(base)) bind.fn(a) } -func Callback2[Base any, A any, B any](b *Base, a A, c B) { - bind := (*bind2[Base, A, B])(unsafe.Pointer(b)) - bind.fn(a, c) +func Callback2[Base any, A any, B any](base *Base, a A, b B) { + bind := (*bind2[Base, A, B])(unsafe.Pointer(base)) + bind.fn(a, b) } -func Callback3[Base any, A any, B any, C any](b *Base, a A, c B, d C) { - bind := (*bind3[Base, A, B, C])(unsafe.Pointer(b)) - bind.fn(a, c, d) +func Callback3[Base any, A any, B any, C any](base *Base, a A, b B, c C) { + bind := (*bind3[Base, A, B, C])(unsafe.Pointer(base)) + bind.fn(a, b, c) } /** @@ -107,8 +107,8 @@ func Bind[T any](call func()) (p *T, cb Cb[T]) { func BindF[T any, F ~func(*T)](call func()) (*T, F) { bb := &bind[T]{fn: call} p := (*T)(unsafe.Pointer(bb)) - fn := Callback[T] - return p, *(*F)(unsafe.Pointer(&fn)) + var fn F = Callback[T] + return p, fn } func Bind1[T any, A any](call func(A)) (p *T, cb Cb1[T, A]) { @@ -118,11 +118,11 @@ func Bind1[T any, A any](call func(A)) (p *T, cb Cb1[T, A]) { return } -func Bind1F[T any, A any, F ~func(A)](call func(A)) (*T, F) { +func Bind1F[T any, F ~func(*T, A), A any](call func(A)) (*T, F) { bb := &bind1[T, A]{fn: call} p := (*T)(unsafe.Pointer(bb)) - fn := Callback1[T, A] - return p, *(*F)(unsafe.Pointer(&fn)) + var fn F = Callback1[T, A] + return p, fn } func Bind2[T any, A any, B any](call func(A, B)) (p *T, cb Cb2[T, A, B]) { @@ -132,11 +132,11 @@ func Bind2[T any, A any, B any](call func(A, B)) (p *T, cb Cb2[T, A, B]) { return } -func Bind2F[T any, A any, B any, F ~func(A, B)](call func(A, B)) (*T, F) { +func Bind2F[T any, F ~func(*T, A, B), A any, B any](call func(A, B)) (*T, F) { bb := &bind2[T, A, B]{fn: call} p := (*T)(unsafe.Pointer(bb)) - fn := Callback2[T, A, B] - return p, *(*F)(unsafe.Pointer(&fn)) + var fn F = Callback2[T, A, B] + return p, fn } func Bind3[T any, A any, B any, C any](call func(A, B, C), a A, b B, c C) (p *T, cb Cb3[T, A, B, C]) { @@ -146,9 +146,9 @@ func Bind3[T any, A any, B any, C any](call func(A, B, C), a A, b B, c C) (p *T, return } -func Bind3F[T any, A any, B any, C any, F ~func(A, B, C)](call func(A, B, C), a A, b B, c C) (*T, F) { +func Bind3F[T any, F ~func(*T, A, B, C), A any, B any, C any](call func(A, B, C), a A, b B, c C) (*T, F) { bb := &bind3[T, A, B, C]{fn: call} p := (*T)(unsafe.Pointer(bb)) - fn := Callback3[T, A, B, C] - return p, *(*F)(unsafe.Pointer(&fn)) + var fn F = Callback3[T, A, B, C] + return p, fn } diff --git a/x/io/README.md b/x/io/README.md index 9681b3ab..da6a4c8e 100644 --- a/x/io/README.md +++ b/x/io/README.md @@ -367,98 +367,23 @@ In some situations, you may want to get the first result of multiple async opera ## Design -Introduce `Promise` type to represent an asynchronous operation and its resulting value. `Promise` can be resolved with a value with an error. `Promise` can be awaited to get the value and error. - -`Promise` just a type indicating the asynchronous operation, it can't be created and assigned directly. It be replaced to `PromiseImpl` by the LLGo compiler. +Introduce `async.IO[T]` type to represent an asynchronous operation, `async.Future[T]` type to represent the result of an asynchronous operation. `async.IO[T]` can be `bind` to a function that accepts `T` as an argument to chain multiple asynchronous operations. `async.IO[T]` can be `await` to get the value of the asynchronous operation. ```go -// Some native async functions -func timeoutAsync(d time.Duration, cb func()) { - go func() { - time.Sleep(d) - cb() - }() -} +package async -// Wrap callback-based async function into Promise -func resolveAfter1Second() (resolve Promise[string]) { - timeoutAsync(1 * time.Second, func() { - resolve("Resolved after 1 second", nil) - }) -} +type Future[T any] func() T +type IO[T any] func() Future[T] -// Compiled to: -func resolveAfter1Second() (resolve PromiseImpl[string]) { - promise := io.NewPromiseImpl[string](resolve func(value string, err error) { - resolve: func(value string, err error) { - for true { - switch (promise.prev = promise.next) { - case 0: - timeoutAsync(1 * time.Second, func() { - resolve("Resolved after 1 second", nil) - }) - } - } - }, - } - return promise -} - -func asyncCall() (resolve Promise[string]) { - str, err := resolveAfter1Second().Await() - resolve("AsyncCall: " + str, err) -} - -// Compiled to: -func asyncCall() (resolve PromiseImpl[string]) { - promise := io.NewPromiseImpl[string](resolve func(value string, err error) { - for true { - switch (promise.prev = promise.next) { - case 0: - resolveAfter1Second() - return - case 1: - str, err := promise.value, promise.err - resolve("AsyncCall: " + str, err) - return - } +func main() { + io := func() Future[string] { + return func() string { + return "Hello, World!" } - }) - return promise -} + } -// Directly return Promise -func asyncCall2() Promise[string] { - return resolveAfter1Second() -} - -// Compiled to: -func asyncCall2() PromiseImpl[string] { - return resolveAfter1Second() -} - -// Don't wait for Promise to complete -func asyncCall3() { - resolveAfter1Second().Then(func(result string) { - fmt.Println("AsyncCall3: " + result) - }) -} - -func asyncMain() { - fmt.Println("Starting AsyncCall") - result1 := asyncCall().Await() - fmt.Println(result1) - - fmt.Println("Starting AsyncCall2") - result2 := asyncCall2().Await() - fmt.Println(result2) - - fmt.Println("Starting AsyncCall3") - asyncCall3() - - // Wait for AsyncCall3 to complete - time.Sleep(2 * time.Second) - - fmt.Println("Main function completed") + future := io() + value := future() + println(value) } ``` diff --git a/x/io/io.go b/x/io/io.go deleted file mode 100644 index bc866b68..00000000 --- a/x/io/io.go +++ /dev/null @@ -1,127 +0,0 @@ -//go:build llgo -// +build llgo - -/* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io - -import ( - "unsafe" - _ "unsafe" - - "github.com/goplus/llgo/c" - "github.com/goplus/llgo/c/libuv" - "github.com/goplus/llgo/c/net" - "github.com/goplus/llgo/x/async" - "github.com/goplus/llgo/x/cbind" - "github.com/goplus/llgo/x/tuple" -) - -type Tcp struct { - tcp libuv.Tcp -} - -type libuvError libuv.Errno - -func (e libuvError) Error() string { - s := libuv.Strerror(libuv.Errno(e)) - return c.GoString(s, c.Strlen(s)) -} - -func NewTcp() *Tcp { - t := &Tcp{} - libuv.InitTcp(async.Exec().L, &t.tcp) - return t -} - -func (t *Tcp) Bind(addr *net.SockAddr, flags uint) error { - if res := t.tcp.Bind(addr, c.Uint(flags)); res != 0 { - return libuvError(res) - } - return nil -} - -func (t *Tcp) Listen(backlog int, cb func(server *libuv.Stream, status c.Int)) error { - if res := (*libuv.Stream)(&t.tcp).Listen(c.Int(backlog), cb); res != 0 { - return libuvError(res) - } - return nil -} - -func (t *Tcp) Accept() (client *Tcp, err error) { - tcp := &Tcp{} - if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { - return nil, libuvError(res) - } - if res := (*libuv.Stream)(&t.tcp).Accept((*libuv.Stream)(&tcp.tcp)); res != 0 { - return nil, libuvError(res) - } - return tcp, nil -} - -func Connect(addr *net.SockAddr) async.IO[tuple.Tuple2[*Tcp, error]] { - return async.Async(func(resolve func(tuple.Tuple2[*Tcp, error])) { - tcp := &Tcp{} - if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { - resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) - return - } - req, _ := cbind.Bind1[libuv.Connect](func(status c.Int) { - if status != 0 { - resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(libuv.Errno(status)))) - } - }) - if res := libuv.TcpConnect(req, &tcp.tcp, addr, cbind.Callback1[libuv.Connect, c.Int]); res != 0 { - resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) - } - resolve(tuple.T2[*Tcp, error](tcp, nil)) - }) -} - -func allocBuffer(handle *libuv.Handle, suggestedSize uintptr, buf *libuv.Buf) { - buf.Base = (*c.Char)(c.Malloc(suggestedSize)) - buf.Len = suggestedSize -} - -func (t *Tcp) Read() async.IO[tuple.Tuple2[[]byte, error]] { - return func(ctx *async.AsyncContext) async.Future[tuple.Tuple2[[]byte, error]] { - var result tuple.Tuple2[[]byte, error] - var done bool - tcp := (*libuv.Stream)(&t.tcp) - tcp.StartRead(allocBuffer, func(client *libuv.Stream, nread c.Long, buf *libuv.Buf) { - if nread > 0 { - result = tuple.T2[[]byte, error](cbind.GoBytes(buf.Base, int(nread)), nil) - } else if nread < 0 { - result = tuple.T2[[]byte, error](nil, libuvError(libuv.Errno(nread))) - } else { - result = tuple.T2[[]byte, error](nil, nil) - } - done = true - ctx.Complete() - }) - return func() tuple.Tuple2[[]byte, error] { - if !done { - panic("Tcp.Read: Future accessed before completion") - } - return result - } - } -} - -func (t *Tcp) Close() { - (*libuv.Handle)(unsafe.Pointer(&t.tcp)).Close(nil) -} diff --git a/x/io/io_llgo.go b/x/io/io_llgo.go new file mode 100644 index 00000000..a598bea8 --- /dev/null +++ b/x/io/io_llgo.go @@ -0,0 +1,291 @@ +/* + * 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 ( + "strings" + "syscall" + "unsafe" + _ "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/libuv" + "github.com/goplus/llgo/c/net" + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/cbind" + "github.com/goplus/llgo/x/tuple" +) + +type Tcp struct { + tcp libuv.Tcp + listenCb func(server *Tcp, err error) + readCb func([]byte, error) + writeCb func(int, error) +} + +type libuvError libuv.Errno + +func (e libuvError) Error() string { + s := libuv.Strerror(libuv.Errno(e)) + return c.GoString(s, c.Strlen(s)) +} + +type getAddrInfoBind struct { + libuv.GetAddrInfo + resolve func(tuple.Tuple2[*net.SockAddr, error]) +} + +func getAddrInfoCb(p *libuv.GetAddrInfo, status c.Int, addr *net.AddrInfo) { + bind := (*getAddrInfoBind)(unsafe.Pointer(p)) + if status != 0 { + bind.resolve(tuple.T2[*net.SockAddr, error](nil, libuvError(status))) + return + } + bind.resolve(tuple.T2[*net.SockAddr, error](addr.Addr, nil)) +} + +func ParseAddr(addr string) async.Future[tuple.Tuple2[*net.SockAddr, error]] { + return async.Async(func(resolve func(tuple.Tuple2[*net.SockAddr, error])) { + host := "127.0.0.1" + var port string + // split host and service by last colon + idx := strings.LastIndex(addr, ":") + if idx < 0 { + port = addr + } else { + host = addr[:idx] + port = addr[idx+1:] + } + + hints := &net.AddrInfo{ + Family: net.AF_INET, + SockType: net.SOCK_STREAM, + Protocol: syscall.IPPROTO_TCP, + Flags: 0, + } + + // TODO(lijie): closure problem, instead with a struct to hold the resolve function. + // req, cb := cbind.Bind2F[libuv.GetAddrInfo, libuv.GetaddrinfoCb](func(status c.Int, addr *net.AddrInfo) { + // if status != 0 { + // resolve(tuple.T2[*net.SockAddr, error](nil, libuvError(status))) + // return + // } + // resolve(tuple.T2[*net.SockAddr, error](addr.Addr, nil)) + // }) + // if res := libuv.Getaddrinfo(async.Exec().L, req, cb, c.AllocaCStr(host), c.AllocaCStr(port), hints); res != 0 { + // resolve(tuple.T2[*net.SockAddr, error](nil, libuvError(res))) + // return + // } + bind := &getAddrInfoBind{ + resolve: resolve, + } + if res := libuv.Getaddrinfo(async.Exec().L, &bind.GetAddrInfo, getAddrInfoCb, c.AllocaCStr(host), c.AllocaCStr(port), hints); res != 0 { + resolve(tuple.T2[*net.SockAddr, error](nil, libuvError(res))) + return + } + }) +} + +func Listen(protocol, bindAddr string) async.Future[tuple.Tuple2[*Tcp, error]] { + return async.Async(func(resolve func(tuple.Tuple2[*Tcp, error])) { + tcp, err := NewTcp() + if err != nil { + resolve(tuple.T2[*Tcp, error](nil, err)) + return + } + ParseAddr(bindAddr)(func(v tuple.Tuple2[*net.SockAddr, error]) { + addr, err := v.Get() + if err != nil { + resolve(tuple.T2[*Tcp, error](nil, err)) + return + } + if err := tcp.Bind(addr, 0); err != nil { + resolve(tuple.T2[*Tcp, error](nil, err)) + return + } + if err := tcp.Listen(128, func(server *Tcp, err error) { + resolve(tuple.T2[*Tcp, error](server, err)) + }); err != nil { + resolve(tuple.T2[*Tcp, error](nil, err)) + } + }) + }) +} + +func NewTcp() (*Tcp, error) { + t := &Tcp{} + if res := libuv.InitTcp(async.Exec().L, &t.tcp); res != 0 { + return nil, libuvError(res) + } + return t, nil +} + +func (t *Tcp) Bind(addr *net.SockAddr, flags uint) error { + if res := t.tcp.Bind(addr, c.Uint(flags)); res != 0 { + return libuvError(res) + } + return nil +} + +func (t *Tcp) Listen(backlog int, cb func(server *Tcp, err error)) error { + t.listenCb = cb + res := (*libuv.Stream)(&t.tcp).Listen(c.Int(backlog), func(s *libuv.Stream, status c.Int) { + server := (*Tcp)(unsafe.Pointer(s)) + if status != 0 { + server.listenCb(server, libuvError(libuv.Errno(status))) + } else { + server.listenCb(server, nil) + } + }) + if res != 0 { + return libuvError(res) + } + return nil +} + +func (t *Tcp) Accept() (client *Tcp, err error) { + tcp := &Tcp{} + if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { + return nil, libuvError(res) + } + if res := (*libuv.Stream)(&t.tcp).Accept((*libuv.Stream)(&tcp.tcp)); res != 0 { + return nil, libuvError(res) + } + return tcp, nil +} + +type connectBind struct { + libuv.Connect + tcp *Tcp + resolve func(tuple.Tuple2[*Tcp, error]) +} + +func connectCb(p *libuv.Connect, status c.Int) { + bind := (*connectBind)(unsafe.Pointer(p)) + if status != 0 { + bind.resolve(tuple.T2[*Tcp, error](nil, libuvError(libuv.Errno(status)))) + } else { + bind.resolve(tuple.T2[*Tcp, error](bind.tcp, nil)) + } +} + +func Connect(addr *net.SockAddr) async.Future[tuple.Tuple2[*Tcp, error]] { + return async.Async(func(resolve func(tuple.Tuple2[*Tcp, error])) { + tcp := &Tcp{} + if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { + resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) + return + } + // req, _ := cbind.Bind1[libuv.Connect](func(status c.Int) { + // if status != 0 { + // resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(libuv.Errno(status)))) + // } else { + // resolve(tuple.T2[*Tcp, error](tcp, nil)) + // } + // }) + req := &connectBind{ + tcp: tcp, + resolve: resolve, + } + if res := libuv.TcpConnect(&req.Connect, &req.tcp.tcp, addr, connectCb); res != 0 { + resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) + return + } + }) +} + +func allocBuffer(handle *libuv.Handle, suggestedSize uintptr, buf *libuv.Buf) { + buf.Base = (*c.Char)(c.Malloc(suggestedSize)) + buf.Len = suggestedSize +} + +func (t *Tcp) StartRead(fn func(data []byte, err error)) { + t.readCb = func(data []byte, err error) { + fn(data, err) + } + tcp := (*libuv.Stream)(&t.tcp) + res := tcp.StartRead(allocBuffer, func(client *libuv.Stream, nread c.Long, buf *libuv.Buf) { + tcp := (*Tcp)(unsafe.Pointer(client)) + if nread > 0 { + tcp.readCb(cbind.GoBytes(buf.Base, int(nread)), nil) + } else if nread < 0 { + tcp.readCb(nil, libuvError(libuv.Errno(nread))) + } else { + tcp.readCb(nil, nil) + } + }) + if res != 0 { + t.readCb(nil, libuvError(libuv.Errno(res))) + } +} + +func (t *Tcp) StopRead() error { + tcp := (*libuv.Stream)(&t.tcp) + if res := tcp.StopRead(); res != 0 { + return libuvError(libuv.Errno(res)) + } + return nil +} + +// Read once from the TCP connection. +func (t *Tcp) Read() async.Future[tuple.Tuple2[[]byte, error]] { + return async.Async(func(resolve func(tuple.Tuple2[[]byte, error])) { + t.StartRead(func(data []byte, err error) { + if err := t.StopRead(); err != nil { + panic(err) + } + resolve(tuple.T2[[]byte, error](data, err)) + }) + }) +} + +func (t *Tcp) Write(data []byte) async.Future[error] { + return async.Async(func(resolve func(error)) { + writer, _ := cbind.Bind1[libuv.Write](func(req *libuv.Write, status c.Int) { + var result error + if status != 0 { + result = libuvError(libuv.Errno(status)) + } + resolve(result) + }) + tcp := (*libuv.Stream)(&t.tcp) + buf, len := cbind.CBuffer(data) + bufs := &libuv.Buf{Base: buf, Len: uintptr(len)} + writer.Write(tcp, bufs, 1, cbind.Callback1[libuv.Write, c.Int]) + }) +} + +// Don't use this funciton, just for deubg closure problem. +func (t *Tcp) Write1(data []byte) async.Future[error] { + return async.Async(func(resolve func(e error)) { + writer, cb := cbind.Bind1F[libuv.Write, libuv.WriteCb](func(req *libuv.Write, status c.Int) { + if status != 0 { + resolve(libuvError(libuv.Errno(status))) + return + } + resolve(nil) + }) + tcp := (*libuv.Stream)(&t.tcp) + buf, len := cbind.CBuffer(data) + bufs := &libuv.Buf{Base: buf, Len: uintptr(len)} + writer.Write(tcp, bufs, 1, cb) + }) +} + +func (t *Tcp) Close() { + (*libuv.Handle)(unsafe.Pointer(&t.tcp)).Close(nil) +} From 69a2a01bc746d4f67ad6d5e2c507b59ec7745edb Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 6 Sep 2024 16:24:10 +0800 Subject: [PATCH 6/9] cbind.Bind: expose *Base argument --- x/cbind/cbind.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/x/cbind/cbind.go b/x/cbind/cbind.go index 7f267428..562d5595 100644 --- a/x/cbind/cbind.go +++ b/x/cbind/cbind.go @@ -34,42 +34,42 @@ type Cb3[T any, A any, B any, C any] func(*T, A, B, C) type bind[Base any] struct { b Base - fn func() + fn func(*Base) } type bind1[Base any, A any] struct { b Base - fn func(A) + fn func(*Base, A) } type bind2[Base any, A any, B any] struct { b Base - fn func(A, B) + fn func(*Base, A, B) } type bind3[Base any, A any, B any, C any] struct { b Base - fn func(A, B, C) + fn func(*Base, A, B, C) } func Callback[Base any](base *Base) { bind := (*bind[Base])(unsafe.Pointer(base)) - bind.fn() + bind.fn(base) } func Callback1[Base any, A any](base *Base, a A) { bind := (*bind1[Base, A])(unsafe.Pointer(base)) - bind.fn(a) + bind.fn(base, a) } func Callback2[Base any, A any, B any](base *Base, a A, b B) { bind := (*bind2[Base, A, B])(unsafe.Pointer(base)) - bind.fn(a, b) + bind.fn(base, a, b) } func Callback3[Base any, A any, B any, C any](base *Base, a A, b B, c C) { bind := (*bind3[Base, A, B, C])(unsafe.Pointer(base)) - bind.fn(a, b, c) + bind.fn(base, a, b, c) } /** @@ -97,56 +97,56 @@ func Callback3[Base any, A any, B any, C any](base *Base, a A, b B, c C) { * @param call The Go function to bind. * @return The data pointer and the C callback function. */ -func Bind[T any](call func()) (p *T, cb Cb[T]) { +func Bind[T any](call func(*T)) (p *T, cb Cb[T]) { bb := &bind[T]{fn: call} p = (*T)(unsafe.Pointer(bb)) cb = Callback[T] return } -func BindF[T any, F ~func(*T)](call func()) (*T, F) { +func BindF[T any, F ~func(*T)](call func(*T)) (*T, F) { bb := &bind[T]{fn: call} p := (*T)(unsafe.Pointer(bb)) var fn F = Callback[T] return p, fn } -func Bind1[T any, A any](call func(A)) (p *T, cb Cb1[T, A]) { +func Bind1[T any, A any](call func(*T, A)) (p *T, cb Cb1[T, A]) { bb := &bind1[T, A]{fn: call} p = (*T)(unsafe.Pointer(bb)) cb = Callback1[T, A] return } -func Bind1F[T any, F ~func(*T, A), A any](call func(A)) (*T, F) { +func Bind1F[T any, F ~func(*T, A), A any](call func(*T, A)) (*T, F) { bb := &bind1[T, A]{fn: call} p := (*T)(unsafe.Pointer(bb)) var fn F = Callback1[T, A] return p, fn } -func Bind2[T any, A any, B any](call func(A, B)) (p *T, cb Cb2[T, A, B]) { +func Bind2[T any, A any, B any](call func(*T, A, B)) (p *T, cb Cb2[T, A, B]) { bb := &bind2[T, A, B]{fn: call} p = (*T)(unsafe.Pointer(bb)) cb = Callback2[T, A, B] return } -func Bind2F[T any, F ~func(*T, A, B), A any, B any](call func(A, B)) (*T, F) { +func Bind2F[T any, F ~func(*T, A, B), A any, B any](call func(*T, A, B)) (*T, F) { bb := &bind2[T, A, B]{fn: call} p := (*T)(unsafe.Pointer(bb)) var fn F = Callback2[T, A, B] return p, fn } -func Bind3[T any, A any, B any, C any](call func(A, B, C), a A, b B, c C) (p *T, cb Cb3[T, A, B, C]) { +func Bind3[T any, A any, B any, C any](call func(*T, A, B, C), a A, b B, c C) (p *T, cb Cb3[T, A, B, C]) { bb := &bind3[T, A, B, C]{fn: call} p = (*T)(unsafe.Pointer(bb)) cb = Callback3[T, A, B, C] return } -func Bind3F[T any, F ~func(*T, A, B, C), A any, B any, C any](call func(A, B, C), a A, b B, c C) (*T, F) { +func Bind3F[T any, F ~func(*T, A, B, C), A any, B any, C any](call func(*T, A, B, C), a A, b B, c C) (*T, F) { bb := &bind3[T, A, B, C]{fn: call} p := (*T)(unsafe.Pointer(bb)) var fn F = Callback3[T, A, B, C] From fce06722826051ffb62a45f6fbb14436889046b8 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 6 Sep 2024 22:29:42 +0800 Subject: [PATCH 7/9] make future IO working both on go and llgo --- x/async/_demo/all/all.go | 140 +++------ x/async/async.go | 37 +-- x/async/async_go.go | 74 ++--- x/async/async_llgo.go | 29 +- x/async/executor_go.go | 14 +- x/async/executor_llgo.go | 7 +- x/async/timeout/timeout_go.go | 6 +- x/async/timeout/timeout_llgo.go | 3 + x/cbind/cbind.go | 11 - x/io/_demo/asyncdemo/async.go | 290 ------------------ x/{io => socketio}/README.md | 0 x/socketio/socketio_go.go | 92 ++++++ .../io_llgo.go => socketio/socketio_llgo.go} | 191 +++++------- 13 files changed, 284 insertions(+), 610 deletions(-) delete mode 100644 x/io/_demo/asyncdemo/async.go rename x/{io => socketio}/README.md (100%) create mode 100644 x/socketio/socketio_go.go rename x/{io/io_llgo.go => socketio/socketio_llgo.go} (52%) diff --git a/x/async/_demo/all/all.go b/x/async/_demo/all/all.go index 56e0c4b6..0f78e648 100644 --- a/x/async/_demo/all/all.go +++ b/x/async/_demo/all/all.go @@ -5,18 +5,16 @@ import ( "os" "time" - "github.com/goplus/llgo/c" - "github.com/goplus/llgo/c/net" "github.com/goplus/llgo/x/async" "github.com/goplus/llgo/x/async/timeout" - "github.com/goplus/llgo/x/io" + "github.com/goplus/llgo/x/socketio" "github.com/goplus/llgo/x/tuple" ) func ReadFile(fileName string) async.Future[tuple.Tuple2[[]byte, error]] { return async.Async(func(resolve func(tuple.Tuple2[[]byte, error])) { go func() { - println(async.Gettid(), "read file", fileName) + println("read file", fileName) bytes, err := os.ReadFile(fileName) resolve(tuple.T2(bytes, err)) }() @@ -50,16 +48,17 @@ func main() { func RunIO() { println("RunIO with Await") + // Hide `resolve` in Go+ async.Run(async.Async(func(resolve func(async.Void)) { println("read file") defer resolve(async.Void{}) - content, err := async.Await(ReadFile("1.txt")).Get() + content, err := async.Await(ReadFile("all.go")).Get() if err != nil { fmt.Printf("read err: %v\n", err) return } fmt.Printf("read content: %s\n", content) - err = async.Await(WriteFile("2.txt", content)) + err = async.Await(WriteFile("2.out", content)) if err != nil { fmt.Printf("write err: %v\n", err) return @@ -71,7 +70,7 @@ func RunIO() { println("RunIO with BindIO") async.Run(async.Async(func(resolve func(async.Void)) { - ReadFile("1.txt")(func(v tuple.Tuple2[[]byte, error]) { + ReadFile("all.go")(func(v tuple.Tuple2[[]byte, error]) { content, err := v.Get() if err != nil { fmt.Printf("read err: %v\n", err) @@ -79,7 +78,7 @@ func RunIO() { return } fmt.Printf("read content: %s\n", content) - WriteFile("2.txt", content)(func(v error) { + WriteFile("2.out", content)(func(v error) { err = v if err != nil { fmt.Printf("write err: %v\n", err) @@ -173,129 +172,66 @@ func RunSocket() { println("RunClient") - RunClient()(func(async.Void) { - println("RunClient done") - resolve(async.Void{}) + timeout.Timeout(100 * time.Millisecond)(func(async.Void) { + RunClient()(func(async.Void) { + println("RunClient done") + resolve(async.Void{}) + }) }) })) } func RunClient() async.Future[async.Void] { return async.Async(func(resolve func(async.Void)) { - bindAddr := "127.0.0.1:3927" - io.ParseAddr(bindAddr)(func(v tuple.Tuple2[*net.SockAddr, error]) { - addr, err := v.Get() - println("Connect to", addr, err) + addr := "127.0.0.1:3927" + socketio.Connect("tcp", addr)(func(v tuple.Tuple2[*socketio.Conn, error]) { + client, err := v.Get() + println("Connected", client, err) if err != nil { panic(err) } - io.Connect(addr)(func(v tuple.Tuple2[*io.Tcp, error]) { - client, err := v.Get() - println("Connected", client, err) - if err != nil { - panic(err) - } - var loop func(client *io.Tcp) - loop = func(client *io.Tcp) { - client.Write([]byte("Hello"))(func(err error) { + counter := 0 + var loop func(client *socketio.Conn) + loop = func(client *socketio.Conn) { + counter++ + data := fmt.Sprintf("Hello %d", counter) + client.Write([]byte(data))(func(err error) { + if err != nil { + panic(err) + } + client.Read()(func(v tuple.Tuple2[[]byte, error]) { + data, err := v.Get() if err != nil { panic(err) } - client.Read()(func(v tuple.Tuple2[[]byte, error]) { - data, err := v.Get() - if err != nil { - panic(err) - } - println("Read:", string(data)) - timeout.Timeout(1 * time.Second)(func(async.Void) { - loop(client) - }) + println("Read from server:", string(data)) + timeout.Timeout(1 * time.Second)(func(async.Void) { + loop(client) }) }) - } - loop(client) - }) + }) + } + loop(client) }) }) } func RunServer() async.Future[async.Void] { return async.Async(func(resolve func(async.Void)) { - server, err := io.NewTcp() - if err != nil { - panic(err) - } - - bindAddr := "0.0.0.0:3927" - io.ParseAddr(bindAddr)(func(v tuple.Tuple2[*net.SockAddr, error]) { - addr, err := v.Get() - if err != nil { - panic(err) - } - - if err = server.Bind(addr, 0); err != nil { - panic(err) - } - c.Printf(c.Str("Listening on %s\n"), c.AllocaCStr(bindAddr)) - - err = server.Listen(128, func(server *io.Tcp, err error) { - if err != nil { - panic(err) - } - client, err := server.Accept() - println("Accept", client, err) - - var loop func(client *io.Tcp) - loop = func(client *io.Tcp) { - client.Read()(func(v tuple.Tuple2[[]byte, error]) { - data, err := v.Get() - if err != nil { - println("Read error", err) - } else { - println("Read:", string(data)) - client.Write(data)(func(err error) { - if err != nil { - println("Write error", err) - } else { - println("Write done") - loop(client) - } - }) - } - }) - } - loop(client) - }) - if err != nil { - panic(err) - } - }) - }) -} - -func RunServer1() async.Future[async.Void] { - return async.Async(func(resolve func(async.Void)) { - io.Listen("tcp", "0.0.0.0:3927")(func(v tuple.Tuple2[*io.Tcp, error]) { - server, err := v.Get() - if err != nil { - panic(err) - } - client, err := server.Accept() - println("Accept", client, err) - - var loop func(client *io.Tcp) - loop = func(client *io.Tcp) { + socketio.Listen("tcp", "0.0.0.0:3927", func(client *socketio.Conn, err error) { + println("Client connected", client, err) + var loop func(client *socketio.Conn) + loop = func(client *socketio.Conn) { client.Read()(func(v tuple.Tuple2[[]byte, error]) { data, err := v.Get() if err != nil { println("Read error", err) } else { - println("Read:", string(data)) + println("Read from client:", string(data)) client.Write(data)(func(err error) { if err != nil { println("Write error", err) } else { - println("Write done") loop(client) } }) diff --git a/x/async/async.go b/x/async/async.go index b1d4f220..268bce60 100644 --- a/x/async/async.go +++ b/x/async/async.go @@ -17,45 +17,14 @@ package async import ( - "unsafe" _ "unsafe" - - "github.com/goplus/llgo/c/libuv" ) type Void = [0]byte type Future[T any] func(func(T)) -type asyncBind[T any] struct { - libuv.Async - result T - chain func(T) -} - -func asyncCb[T any](a *libuv.Async) { - a.Close(nil) - aa := (*asyncBind[T])(unsafe.Pointer(a)) - aa.chain(aa.result) -} - -func Async[T any](fn func(func(T))) Future[T] { - return func(chain func(T)) { - loop := Exec().L - // var result T - // var a *libuv.Async - // var cb libuv.AsyncCb - // a, cb = cbind.BindF[libuv.Async, libuv.AsyncCb](func() { - // a.Close(nil) - // chain(result) - // }) - // loop.Async(a, cb) - - aa := &asyncBind[T]{chain: chain} - loop.Async(&aa.Async, asyncCb[T]) - fn(func(v T) { - aa.result = v - aa.Send() - }) - } +// Just for pure LLGo/Go, transpile to callback in Go+ +func Await[T1 any](call Future[T1]) (ret T1) { + return Run(call) } diff --git a/x/async/async_go.go b/x/async/async_go.go index cec40669..c775b7e8 100644 --- a/x/async/async_go.go +++ b/x/async/async_go.go @@ -1,5 +1,5 @@ -//go:build llgo11 -// +build llgo11 +//go:build !llgo +// +build !llgo /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. @@ -21,71 +21,47 @@ package async import "sync" -func BindIO[T any](call IO[T], callback func(T)) { - callback(Await(call)) -} - -func Await[T1 any](call IO[T1]) (ret T1) { - ch := make(chan struct{}) - f := call(&AsyncContext{ - Executor: Exec(), - complete: func() { - close(ch) - }, - }) - <-ch - return f() +func Async[T any](fn func(func(T))) Future[T] { + return func(chain func(T)) { + go fn(chain) + } } // ----------------------------------------------------------------------------- -func Race[T1 any](calls ...IO[T1]) IO[T1] { +func Race[T1 any](futures ...Future[T1]) Future[T1] { return Async(func(resolve func(T1)) { - ch := make(chan int, len(calls)) - futures := make([]Future[T1], len(calls)) - for i, call := range calls { - i := i - call := call - go func() { - f := call(&AsyncContext{ - Executor: Exec(), - complete: func() { - defer func() { - _ = recover() - }() - ch <- i - }, - }) - futures[i] = f - }() + ch := make(chan T1) + for _, future := range futures { + future := future + future(func(v T1) { + defer func() { + // Avoid panic when the channel is closed. + _ = recover() + }() + ch <- v + }) } - i := <-ch + v := <-ch close(ch) - resolve(futures[i]()) + resolve(v) }) } -func All[T1 any](calls ...IO[T1]) IO[[]T1] { +func All[T1 any](futures ...Future[T1]) Future[[]T1] { return Async(func(resolve func([]T1)) { - n := len(calls) + n := len(futures) results := make([]T1, n) - futures := make([]Future[T1], n) wg := sync.WaitGroup{} wg.Add(n) - for i, call := range calls { + for i, future := range futures { i := i - f := call(&AsyncContext{ - Executor: Exec(), - complete: func() { - wg.Done() - }, + future(func(v T1) { + results[i] = v + wg.Done() }) - futures[i] = f } wg.Wait() - for i, f := range futures { - results[i] = f() - } resolve(results) }) } diff --git a/x/async/async_llgo.go b/x/async/async_llgo.go index 1c1b0cb6..d301b20c 100644 --- a/x/async/async_llgo.go +++ b/x/async/async_llgo.go @@ -1,3 +1,6 @@ +//go:build llgo +// +build llgo + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -18,10 +21,30 @@ package async import ( "sync/atomic" + + "github.com/goplus/llgo/c/libuv" + "github.com/goplus/llgo/x/cbind" ) -func Await[T1 any](call Future[T1]) (ret T1) { - return Run(call) +// Currently Async run chain a future that call chain in the goroutine running `async.Run`. +// TODO(lijie): It would better to switch when needed. +func Async[T any](fn func(func(T))) Future[T] { + return func(chain func(T)) { + loop := Exec().L + + var result T + var a *libuv.Async + var cb libuv.AsyncCb + a, cb = cbind.BindF[libuv.Async, libuv.AsyncCb](func(a *libuv.Async) { + a.Close(nil) + chain(result) + }) + loop.Async(a, cb) + fn(func(v T) { + result = v + a.Send() + }) + } } // ----------------------------------------------------------------------------- @@ -32,6 +55,7 @@ func Race[T1 any](futures ...Future[T1]) Future[T1] { for _, future := range futures { future(func(v T1) { if !done.Swap(true) { + // Just resolve the first one. resolve(v) } }) @@ -49,6 +73,7 @@ func All[T1 any](futures ...Future[T1]) Future[[]T1] { future(func(v T1) { results[i] = v if atomic.AddUint32(&done, 1) == uint32(n) { + // All done. resolve(results) } }) diff --git a/x/async/executor_go.go b/x/async/executor_go.go index 8ef70dc3..cd9fb891 100644 --- a/x/async/executor_go.go +++ b/x/async/executor_go.go @@ -1,5 +1,5 @@ -//go:build llgo11 -// +build llgo11 +//go:build !llgo +// +build !llgo /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. @@ -28,6 +28,12 @@ func Exec() *Executor { return exec } -func Run(fn func()) { - fn() +func Run[T any](future Future[T]) (ret T) { + ch := make(chan T) + go func() { + future(func(v T) { + ch <- v + }) + }() + return <-ch } diff --git a/x/async/executor_llgo.go b/x/async/executor_llgo.go index 3018cc3a..fd5e4e14 100644 --- a/x/async/executor_llgo.go +++ b/x/async/executor_llgo.go @@ -1,3 +1,6 @@ +//go:build llgo +// +build llgo + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -19,14 +22,10 @@ package async import ( "unsafe" - "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/libuv" "github.com/goplus/llgo/c/pthread" ) -//go:linkname Gettid C.pthread_self -func Gettid() c.Pointer - var execKey pthread.Key func init() { diff --git a/x/async/timeout/timeout_go.go b/x/async/timeout/timeout_go.go index 2315c9a3..32839079 100644 --- a/x/async/timeout/timeout_go.go +++ b/x/async/timeout/timeout_go.go @@ -1,5 +1,5 @@ -//go:build llgo11 -// +build llgo11 +//go:build !llgo +// +build !llgo /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. @@ -25,7 +25,7 @@ import ( "github.com/goplus/llgo/x/async" ) -func Timeout(d time.Duration) async.IO[async.Void] { +func Timeout(d time.Duration) async.Future[async.Void] { return async.Async(func(resolve func(async.Void)) { go func() { time.Sleep(d) diff --git a/x/async/timeout/timeout_llgo.go b/x/async/timeout/timeout_llgo.go index 0bc07ab3..ed18ce26 100644 --- a/x/async/timeout/timeout_llgo.go +++ b/x/async/timeout/timeout_llgo.go @@ -1,3 +1,6 @@ +//go:build llgo +// +build llgo + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * diff --git a/x/cbind/cbind.go b/x/cbind/cbind.go index 562d5595..84b87528 100644 --- a/x/cbind/cbind.go +++ b/x/cbind/cbind.go @@ -83,17 +83,6 @@ func Callback3[Base any, A any, B any, C any](base *Base, a A, b B, c C) { * libuv.InitTimer(async.Exec().L, timer) * timer.Start(cb, 1000, 0) * - * TODO(lijie): fn isn't a C func-ptr, it's closure, should fix the LLGo compiler. - * See: https://github.com/goplus/llgo/issues/766 - * - * Workaround: - * - * timer, _ := cbind.Bind[libuv.Timer](func() { - * println("hello") - * }) - * libuv.InitTimer(async.Exec().L, timer) - * timer.Start(cbind.Callback[libuv.Timer], 1000, 0) - * * @param call The Go function to bind. * @return The data pointer and the C callback function. */ diff --git a/x/io/_demo/asyncdemo/async.go b/x/io/_demo/asyncdemo/async.go deleted file mode 100644 index 02b4475a..00000000 --- a/x/io/_demo/asyncdemo/async.go +++ /dev/null @@ -1,290 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "time" - - "github.com/goplus/llgo/x/io" -) - -// ----------------------------------------------------------------------------- - -type Response struct { - StatusCode int - - mockBody string -} - -func (r *Response) mock(body string) { - r.mockBody = body -} - -func (r *Response) Text() (resolve io.Promise[string]) { - resolve(r.mockBody, nil) - return -} - -func (r *Response) TextCompiled() *io.PromiseImpl[string] { - P := &io.PromiseImpl[string]{} - P.Func = func(resolve func(string, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - resolve(r.mockBody, nil) - P.Next = -1 - return - default: - panic("Promise already done") - } - } - } - return P -} - -func HttpGet(url string, callback func(resp *Response, err error)) { - resp := &Response{StatusCode: 200} - callback(resp, nil) -} - -func AsyncHttpGet(url string) (resolve io.Promise[*Response]) { - HttpGet(url, resolve) - return -} - -func AsyncHttpGetCompiled(url string) *io.PromiseImpl[*Response] { - P := &io.PromiseImpl[*Response]{} - P.Func = func(resolve func(*Response, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - HttpGet(url, resolve) - P.Next = -1 - return - default: - panic("Promise already done") - } - } - } - return P -} - -// ----------------------------------------------------------------------------- - -type User struct { - Name string -} - -func GetUser(uid string) (resolve io.Promise[User]) { - resp, err := AsyncHttpGet("http://example.com/user/" + uid).Await() - if err != nil { - resolve(User{}, err) - return - } - - if resp.StatusCode != 200 { - resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - resp.mock(`{"name":"Alice"}`) - - body, err := resp.Text().Await() - if err != nil { - resolve(User{}, err) - return - } - user := User{} - if err := json.Unmarshal([]byte(body), &user); err != nil { - resolve(User{}, err) - return - } - - resolve(user, nil) - return -} - -func GetUserCompiled(uid string) *io.PromiseImpl[User] { - var state1 *io.PromiseImpl[*Response] - var state2 *io.PromiseImpl[string] - - P := &io.PromiseImpl[User]{} - P.Func = func(resolve func(User, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - state1 = AsyncHttpGetCompiled("http://example.com/user/" + uid) - P.Next = 1 - return - case 1: - state1.EnsureDone() - resp, err := state1.Value, state1.Err - if err != nil { - resolve(User{}, err) - return - } - - if resp.StatusCode != 200 { - resolve(User{}, fmt.Errorf("http status code: %d", resp.StatusCode)) - return - } - - resp.mock(`{"name":"Alice"}`) - - state2 = resp.TextCompiled() - P.Next = 2 - return - case 2: - state2.EnsureDone() - body, err := state2.Value, state2.Err - if err != nil { - resolve(User{}, err) - return - } - user := User{} - if err := json.Unmarshal([]byte(body), &user); err != nil { - resolve(User{}, err) - return - } - - resolve(user, nil) - P.Next = -1 - return - default: - panic("Promise already done") - } - } - } - return P -} - -func GetScore() *io.Promise[float64] { - panic("todo: GetScore") -} - -func GetScoreCompiled() *io.PromiseImpl[float64] { - P := &io.PromiseImpl[float64]{} - P.Func = func(resolve func(float64, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - panic("todo: GetScore") - default: - panic("Promise already done") - } - } - } - return P -} - -func DoUpdate(op string) *io.Promise[io.Void] { - panic("todo: DoUpdate") -} - -func DoUpdateCompiled(op string) *io.PromiseImpl[io.Void] { - P := &io.PromiseImpl[io.Void]{} - P.Func = func(resolve func(io.Void, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - panic("todo: DoUpdate") - default: - panic("Promise already done") - } - } - } - return P -} - -func Demo() (resolve io.Promise[io.Void]) { - user, err := GetUser("123").Await() - log.Println(user, err) - - user, err = io.Race[User](GetUser("123"), GetUser("456"), GetUser("789")).Await() - log.Println(user, err) - - users, err := io.All[User]([]io.AsyncCall[User]{GetUser("123"), GetUser("456"), GetUser("789")}).Await() - log.Println(users, err) - - user, score, _, err := io.Await3[User, float64, io.Void](GetUser("123"), GetScore(), DoUpdate("update sth.")) - log.Println(user, score, err) - - // TODO(lijie): select from multiple promises without channel - select { - case user := <-GetUser("123").Chan(): - log.Println("user:", user) - case score := <-GetScore().Chan(): - log.Println("score:", score) - case <-io.Timeout(5 * time.Second).Chan(): - log.Println("timeout") - } - return -} - -func DemoCompiled() *io.PromiseImpl[io.Void] { - var state1 *io.PromiseImpl[User] - var state2 *io.PromiseImpl[User] - var state3 *io.PromiseImpl[[]User] - var state4 *io.PromiseImpl[io.Await3Result[User, float64, io.Void]] - - P := &io.PromiseImpl[io.Void]{} - P.Func = func(resolve func(io.Void, error)) { - for { - switch P.Prev = P.Next; P.Prev { - case 0: - state1 = GetUserCompiled("123") - P.Next = 1 - return - case 1: - state1.EnsureDone() - user, err := state1.Value, state1.Err - log.Printf("user: %v, err: %v\n", user, err) - - state2 = io.Race[User](GetUserCompiled("123"), GetUserCompiled("456"), GetUserCompiled("789")) - P.Next = 2 - return - case 2: - state2.EnsureDone() - user, err := state2.Value, state2.Err - log.Println(user, err) - - state3 = io.All[User]([]io.AsyncCall[User]{GetUserCompiled("123"), GetUserCompiled("456"), GetUserCompiled("789")}) - P.Next = 3 - return - case 3: - state3.EnsureDone() - users, err := state3.Value, state3.Err - log.Println(users, err) - - state4 = io.Await3Compiled[User, float64, io.Void](GetUserCompiled("123"), GetScoreCompiled(), DoUpdateCompiled("update sth.")) - P.Next = 4 - return - case 4: - state4.EnsureDone() - user, score, _, err := state4.Value.V1, state4.Value.V2, state4.Value.V3, state4.Value.Err - log.Println(user, score, err) - - select { - case user := <-GetUserCompiled("123").Chan(): - log.Println("user:", user) - case score := <-GetScoreCompiled().Chan(): - log.Println("score:", score) - case <-io.TimeoutCompiled(5 * time.Second).Chan(): - log.Println("timeout") - } - P.Next = -1 - return - default: - panic("Promise already done") - } - } - } - return P -} - -func main() { - log.SetFlags(log.Lshortfile | log.LstdFlags) - // io.Run(Demo()) - io.Run(DemoCompiled()) -} diff --git a/x/io/README.md b/x/socketio/README.md similarity index 100% rename from x/io/README.md rename to x/socketio/README.md diff --git a/x/socketio/socketio_go.go b/x/socketio/socketio_go.go new file mode 100644 index 00000000..9a32e433 --- /dev/null +++ b/x/socketio/socketio_go.go @@ -0,0 +1,92 @@ +//go:build !llgo +// +build !llgo + +/* + * 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 socketio + +import ( + "net" + + "github.com/goplus/llgo/x/async" + "github.com/goplus/llgo/x/tuple" +) + +type Conn struct { + conn net.Conn +} + +func Listen(protocol, bindAddr string, listenCb func(client *Conn, err error)) { + go func() { + listener, err := net.Listen(protocol, bindAddr) + if err != nil { + listenCb(nil, err) + return + } + for { + conn, err := listener.Accept() + if err != nil { + listenCb(nil, err) + return + } + listenCb(&Conn{conn: conn}, nil) + } + }() +} + +func Connect(network, addr string) async.Future[tuple.Tuple2[*Conn, error]] { + return async.Async(func(resolve func(tuple.Tuple2[*Conn, error])) { + go func() { + conn, err := net.Dial(network, addr) + if err != nil { + resolve(tuple.T2[*Conn, error](nil, err)) + return + } + resolve(tuple.T2[*Conn, error](&Conn{conn: conn}, nil)) + }() + }) +} + +// Read once from the TCP connection. +func (t *Conn) Read() async.Future[tuple.Tuple2[[]byte, error]] { + return async.Async(func(resolve func(tuple.Tuple2[[]byte, error])) { + go func() { + buf := make([]byte, 1024) + n, err := t.conn.Read(buf) + if err != nil { + resolve(tuple.T2[[]byte, error](nil, err)) + return + } + resolve(tuple.T2[[]byte, error](buf[:n], nil)) + }() + }) +} + +func (t *Conn) Write(data []byte) async.Future[error] { + return async.Async(func(resolve func(error)) { + go func() { + _, err := t.conn.Write(data) + resolve(err) + }() + }) +} + +func (t *Conn) Close() { + if t.conn != nil { + t.conn.Close() + } +} diff --git a/x/io/io_llgo.go b/x/socketio/socketio_llgo.go similarity index 52% rename from x/io/io_llgo.go rename to x/socketio/socketio_llgo.go index a598bea8..cff5305d 100644 --- a/x/io/io_llgo.go +++ b/x/socketio/socketio_llgo.go @@ -1,3 +1,6 @@ +//go:build llgo +// +build llgo + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -14,7 +17,7 @@ * limitations under the License. */ -package io +package socketio import ( "strings" @@ -30,11 +33,14 @@ import ( "github.com/goplus/llgo/x/tuple" ) -type Tcp struct { +type Listener struct { tcp libuv.Tcp - listenCb func(server *Tcp, err error) - readCb func([]byte, error) - writeCb func(int, error) + listenCb func(server *Listener, err error) +} + +type Conn struct { + tcp libuv.Tcp + readCb func([]byte, error) } type libuvError libuv.Errno @@ -58,7 +64,7 @@ func getAddrInfoCb(p *libuv.GetAddrInfo, status c.Int, addr *net.AddrInfo) { bind.resolve(tuple.T2[*net.SockAddr, error](addr.Addr, nil)) } -func ParseAddr(addr string) async.Future[tuple.Tuple2[*net.SockAddr, error]] { +func parseAddr(addr string) async.Future[tuple.Tuple2[*net.SockAddr, error]] { return async.Async(func(resolve func(tuple.Tuple2[*net.SockAddr, error])) { host := "127.0.0.1" var port string @@ -78,73 +84,64 @@ func ParseAddr(addr string) async.Future[tuple.Tuple2[*net.SockAddr, error]] { Flags: 0, } - // TODO(lijie): closure problem, instead with a struct to hold the resolve function. - // req, cb := cbind.Bind2F[libuv.GetAddrInfo, libuv.GetaddrinfoCb](func(status c.Int, addr *net.AddrInfo) { - // if status != 0 { - // resolve(tuple.T2[*net.SockAddr, error](nil, libuvError(status))) - // return - // } - // resolve(tuple.T2[*net.SockAddr, error](addr.Addr, nil)) - // }) - // if res := libuv.Getaddrinfo(async.Exec().L, req, cb, c.AllocaCStr(host), c.AllocaCStr(port), hints); res != 0 { - // resolve(tuple.T2[*net.SockAddr, error](nil, libuvError(res))) - // return - // } - bind := &getAddrInfoBind{ - resolve: resolve, - } - if res := libuv.Getaddrinfo(async.Exec().L, &bind.GetAddrInfo, getAddrInfoCb, c.AllocaCStr(host), c.AllocaCStr(port), hints); res != 0 { + req, cb := cbind.Bind2F[libuv.GetAddrInfo, libuv.GetaddrinfoCb](func(i *libuv.GetAddrInfo, status c.Int, addr *net.AddrInfo) { + if status != 0 { + resolve(tuple.T2[*net.SockAddr, error](nil, libuvError(status))) + return + } + resolve(tuple.T2[*net.SockAddr, error](addr.Addr, nil)) + }) + if res := libuv.Getaddrinfo(async.Exec().L, req, cb, c.AllocaCStr(host), c.AllocaCStr(port), hints); res != 0 { resolve(tuple.T2[*net.SockAddr, error](nil, libuvError(res))) return } }) } -func Listen(protocol, bindAddr string) async.Future[tuple.Tuple2[*Tcp, error]] { - return async.Async(func(resolve func(tuple.Tuple2[*Tcp, error])) { - tcp, err := NewTcp() +func Listen(protocol, bindAddr string, listenCb func(client *Conn, err error)) { + tcp, err := newListener() + if err != nil { + listenCb(nil, err) + return + } + parseAddr(bindAddr)(func(v tuple.Tuple2[*net.SockAddr, error]) { + addr, err := v.Get() if err != nil { - resolve(tuple.T2[*Tcp, error](nil, err)) + listenCb(nil, err) return } - ParseAddr(bindAddr)(func(v tuple.Tuple2[*net.SockAddr, error]) { - addr, err := v.Get() - if err != nil { - resolve(tuple.T2[*Tcp, error](nil, err)) - return - } - if err := tcp.Bind(addr, 0); err != nil { - resolve(tuple.T2[*Tcp, error](nil, err)) - return - } - if err := tcp.Listen(128, func(server *Tcp, err error) { - resolve(tuple.T2[*Tcp, error](server, err)) - }); err != nil { - resolve(tuple.T2[*Tcp, error](nil, err)) - } - }) + if err := tcp.bind(addr, 0); err != nil { + listenCb(nil, err) + return + } + if err := tcp.listen(128, func(server *Listener, err error) { + client, err := server.accept() + listenCb(client, err) + }); err != nil { + listenCb(nil, err) + } }) } -func NewTcp() (*Tcp, error) { - t := &Tcp{} +func newListener() (*Listener, error) { + t := &Listener{} if res := libuv.InitTcp(async.Exec().L, &t.tcp); res != 0 { return nil, libuvError(res) } return t, nil } -func (t *Tcp) Bind(addr *net.SockAddr, flags uint) error { +func (t *Listener) bind(addr *net.SockAddr, flags uint) error { if res := t.tcp.Bind(addr, c.Uint(flags)); res != 0 { return libuvError(res) } return nil } -func (t *Tcp) Listen(backlog int, cb func(server *Tcp, err error)) error { - t.listenCb = cb - res := (*libuv.Stream)(&t.tcp).Listen(c.Int(backlog), func(s *libuv.Stream, status c.Int) { - server := (*Tcp)(unsafe.Pointer(s)) +func (l *Listener) listen(backlog int, cb func(server *Listener, err error)) error { + l.listenCb = cb + res := (*libuv.Stream)(&l.tcp).Listen(c.Int(backlog), func(s *libuv.Stream, status c.Int) { + server := (*Listener)(unsafe.Pointer(s)) if status != 0 { server.listenCb(server, libuvError(libuv.Errno(status))) } else { @@ -157,54 +154,43 @@ func (t *Tcp) Listen(backlog int, cb func(server *Tcp, err error)) error { return nil } -func (t *Tcp) Accept() (client *Tcp, err error) { - tcp := &Tcp{} +func (l *Listener) accept() (client *Conn, err error) { + tcp := &Conn{} if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { return nil, libuvError(res) } - if res := (*libuv.Stream)(&t.tcp).Accept((*libuv.Stream)(&tcp.tcp)); res != 0 { + if res := (*libuv.Stream)(&l.tcp).Accept((*libuv.Stream)(&tcp.tcp)); res != 0 { return nil, libuvError(res) } return tcp, nil } -type connectBind struct { - libuv.Connect - tcp *Tcp - resolve func(tuple.Tuple2[*Tcp, error]) -} +func Connect(network, addr string) async.Future[tuple.Tuple2[*Conn, error]] { + return async.Async(func(resolve func(tuple.Tuple2[*Conn, error])) { + parseAddr(addr)(func(v tuple.Tuple2[*net.SockAddr, error]) { + addr, err := v.Get() + if err != nil { + resolve(tuple.T2[*Conn, error]((*Conn)(nil), err)) + return + } -func connectCb(p *libuv.Connect, status c.Int) { - bind := (*connectBind)(unsafe.Pointer(p)) - if status != 0 { - bind.resolve(tuple.T2[*Tcp, error](nil, libuvError(libuv.Errno(status)))) - } else { - bind.resolve(tuple.T2[*Tcp, error](bind.tcp, nil)) - } -} - -func Connect(addr *net.SockAddr) async.Future[tuple.Tuple2[*Tcp, error]] { - return async.Async(func(resolve func(tuple.Tuple2[*Tcp, error])) { - tcp := &Tcp{} - if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { - resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) - return - } - // req, _ := cbind.Bind1[libuv.Connect](func(status c.Int) { - // if status != 0 { - // resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(libuv.Errno(status)))) - // } else { - // resolve(tuple.T2[*Tcp, error](tcp, nil)) - // } - // }) - req := &connectBind{ - tcp: tcp, - resolve: resolve, - } - if res := libuv.TcpConnect(&req.Connect, &req.tcp.tcp, addr, connectCb); res != 0 { - resolve(tuple.T2[*Tcp, error]((*Tcp)(nil), libuvError(res))) - return - } + tcp := &Conn{} + if res := libuv.InitTcp(async.Exec().L, &tcp.tcp); res != 0 { + resolve(tuple.T2[*Conn, error]((*Conn)(nil), libuvError(res))) + return + } + req, cb := cbind.Bind1F[libuv.Connect, libuv.ConnectCb](func(c *libuv.Connect, status c.Int) { + if status != 0 { + resolve(tuple.T2[*Conn, error]((*Conn)(nil), libuvError(libuv.Errno(status)))) + } else { + resolve(tuple.T2[*Conn, error](tcp, nil)) + } + }) + if res := libuv.TcpConnect(req, &tcp.tcp, addr, cb); res != 0 { + resolve(tuple.T2[*Conn, error]((*Conn)(nil), libuvError(res))) + return + } + }) }) } @@ -213,13 +199,13 @@ func allocBuffer(handle *libuv.Handle, suggestedSize uintptr, buf *libuv.Buf) { buf.Len = suggestedSize } -func (t *Tcp) StartRead(fn func(data []byte, err error)) { +func (t *Conn) StartRead(fn func(data []byte, err error)) { t.readCb = func(data []byte, err error) { fn(data, err) } tcp := (*libuv.Stream)(&t.tcp) res := tcp.StartRead(allocBuffer, func(client *libuv.Stream, nread c.Long, buf *libuv.Buf) { - tcp := (*Tcp)(unsafe.Pointer(client)) + tcp := (*Conn)(unsafe.Pointer(client)) if nread > 0 { tcp.readCb(cbind.GoBytes(buf.Base, int(nread)), nil) } else if nread < 0 { @@ -233,7 +219,7 @@ func (t *Tcp) StartRead(fn func(data []byte, err error)) { } } -func (t *Tcp) StopRead() error { +func (t *Conn) StopRead() error { tcp := (*libuv.Stream)(&t.tcp) if res := tcp.StopRead(); res != 0 { return libuvError(libuv.Errno(res)) @@ -242,7 +228,7 @@ func (t *Tcp) StopRead() error { } // Read once from the TCP connection. -func (t *Tcp) Read() async.Future[tuple.Tuple2[[]byte, error]] { +func (t *Conn) Read() async.Future[tuple.Tuple2[[]byte, error]] { return async.Async(func(resolve func(tuple.Tuple2[[]byte, error])) { t.StartRead(func(data []byte, err error) { if err := t.StopRead(); err != nil { @@ -253,7 +239,7 @@ func (t *Tcp) Read() async.Future[tuple.Tuple2[[]byte, error]] { }) } -func (t *Tcp) Write(data []byte) async.Future[error] { +func (t *Conn) Write(data []byte) async.Future[error] { return async.Async(func(resolve func(error)) { writer, _ := cbind.Bind1[libuv.Write](func(req *libuv.Write, status c.Int) { var result error @@ -269,23 +255,6 @@ func (t *Tcp) Write(data []byte) async.Future[error] { }) } -// Don't use this funciton, just for deubg closure problem. -func (t *Tcp) Write1(data []byte) async.Future[error] { - return async.Async(func(resolve func(e error)) { - writer, cb := cbind.Bind1F[libuv.Write, libuv.WriteCb](func(req *libuv.Write, status c.Int) { - if status != 0 { - resolve(libuvError(libuv.Errno(status))) - return - } - resolve(nil) - }) - tcp := (*libuv.Stream)(&t.tcp) - buf, len := cbind.CBuffer(data) - bufs := &libuv.Buf{Base: buf, Len: uintptr(len)} - writer.Write(tcp, bufs, 1, cb) - }) -} - -func (t *Tcp) Close() { +func (t *Conn) Close() { (*libuv.Handle)(unsafe.Pointer(&t.tcp)).Close(nil) } From 75fe9d61a315ecc2a5efa5eeb0afcdab231a9d42 Mon Sep 17 00:00:00 2001 From: visualfc Date: Fri, 6 Sep 2024 15:45:49 +0800 Subject: [PATCH 8/9] cl: function fix freevars cache --- cl/_testrt/freevars/in.go | 14 +++++ cl/_testrt/freevars/out.ll | 118 +++++++++++++++++++++++++++++++++++++ ssa/decl.go | 12 ++-- 3 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 cl/_testrt/freevars/in.go create mode 100644 cl/_testrt/freevars/out.ll diff --git a/cl/_testrt/freevars/in.go b/cl/_testrt/freevars/in.go new file mode 100644 index 00000000..35360254 --- /dev/null +++ b/cl/_testrt/freevars/in.go @@ -0,0 +1,14 @@ +package main + +func main() { + func(resolve func(error)) { + func(err error) { + if err != nil { + resolve(err) + return + } + resolve(nil) + }(nil) + }(func(err error) { + }) +} diff --git a/cl/_testrt/freevars/out.ll b/cl/_testrt/freevars/out.ll new file mode 100644 index 00000000..5613d9e8 --- /dev/null +++ b/cl/_testrt/freevars/out.ll @@ -0,0 +1,118 @@ +; ModuleID = 'main' +source_filename = "main" + +%"github.com/goplus/llgo/internal/runtime.iface" = type { ptr, ptr } +%"github.com/goplus/llgo/internal/runtime.eface" = type { ptr, ptr } + +@"main.init$guard" = global i1 false, align 1 +@__llgo_argc = global i32 0, align 4 +@__llgo_argv = global ptr null, align 8 + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"main.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"main.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i32 @main(i32 %0, ptr %1) { +_llgo_0: + store i32 %0, ptr @__llgo_argc, align 4 + store ptr %1, ptr @__llgo_argv, align 8 + call void @"github.com/goplus/llgo/internal/runtime.init"() + call void @main.init() + %2 = alloca { ptr, ptr }, align 8 + %3 = getelementptr inbounds { ptr, ptr }, ptr %2, i32 0, i32 0 + store ptr @"__llgo_stub.main.main$2", ptr %3, align 8 + %4 = getelementptr inbounds { ptr, ptr }, ptr %2, i32 0, i32 1 + store ptr null, ptr %4, align 8 + %5 = load { ptr, ptr }, ptr %2, align 8 + call void @"main.main$1"({ ptr, ptr } %5) + ret i32 0 +} + +define void @"main.main$1"({ ptr, ptr } %0) { +_llgo_0: + %1 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) + store { ptr, ptr } %0, ptr %1, align 8 + %2 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 8) + %3 = getelementptr inbounds { ptr }, ptr %2, i32 0, i32 0 + store ptr %1, ptr %3, align 8 + %4 = alloca { ptr, ptr }, align 8 + %5 = getelementptr inbounds { ptr, ptr }, ptr %4, i32 0, i32 0 + store ptr @"main.main$1$1", ptr %5, align 8 + %6 = getelementptr inbounds { ptr, ptr }, ptr %4, i32 0, i32 1 + store ptr %2, ptr %6, align 8 + %7 = load { ptr, ptr }, ptr %4, align 8 + %8 = extractvalue { ptr, ptr } %7, 1 + %9 = extractvalue { ptr, ptr } %7, 0 + call void %9(ptr %8, %"github.com/goplus/llgo/internal/runtime.iface" zeroinitializer) + ret void +} + +define void @"main.main$1$1"(ptr %0, %"github.com/goplus/llgo/internal/runtime.iface" %1) { +_llgo_0: + %2 = call ptr @"github.com/goplus/llgo/internal/runtime.IfaceType"(%"github.com/goplus/llgo/internal/runtime.iface" %1) + %3 = extractvalue %"github.com/goplus/llgo/internal/runtime.iface" %1, 1 + %4 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %5 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %4, i32 0, i32 0 + store ptr %2, ptr %5, align 8 + %6 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %4, i32 0, i32 1 + store ptr %3, ptr %6, align 8 + %7 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %4, align 8 + %8 = call ptr @"github.com/goplus/llgo/internal/runtime.IfaceType"(%"github.com/goplus/llgo/internal/runtime.iface" zeroinitializer) + %9 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %9, i32 0, i32 0 + store ptr %8, ptr %10, align 8 + %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %9, i32 0, i32 1 + store ptr null, ptr %11, align 8 + %12 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %9, align 8 + %13 = call i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface" %7, %"github.com/goplus/llgo/internal/runtime.eface" %12) + %14 = xor i1 %13, true + br i1 %14, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %15 = load { ptr }, ptr %0, align 8 + %16 = extractvalue { ptr } %15, 0 + %17 = load { ptr, ptr }, ptr %16, align 8 + %18 = extractvalue { ptr, ptr } %17, 1 + %19 = extractvalue { ptr, ptr } %17, 0 + call void %19(ptr %18, %"github.com/goplus/llgo/internal/runtime.iface" %1) + ret void + +_llgo_2: ; preds = %_llgo_0 + %20 = load { ptr }, ptr %0, align 8 + %21 = extractvalue { ptr } %20, 0 + %22 = load { ptr, ptr }, ptr %21, align 8 + %23 = extractvalue { ptr, ptr } %22, 1 + %24 = extractvalue { ptr, ptr } %22, 0 + call void %24(ptr %23, %"github.com/goplus/llgo/internal/runtime.iface" zeroinitializer) + ret void +} + +define void @"main.main$2"(%"github.com/goplus/llgo/internal/runtime.iface" %0) { +_llgo_0: + ret void +} + +declare void @"github.com/goplus/llgo/internal/runtime.init"() + +define linkonce void @"__llgo_stub.main.main$2"(ptr %0, %"github.com/goplus/llgo/internal/runtime.iface" %1) { +_llgo_0: + tail call void @"main.main$2"(%"github.com/goplus/llgo/internal/runtime.iface" %1) + ret void +} + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64) + +declare i1 @"github.com/goplus/llgo/internal/runtime.EfaceEqual"(%"github.com/goplus/llgo/internal/runtime.eface", %"github.com/goplus/llgo/internal/runtime.eface") + +declare ptr @"github.com/goplus/llgo/internal/runtime.IfaceType"(%"github.com/goplus/llgo/internal/runtime.iface") diff --git a/ssa/decl.go b/ssa/decl.go index 96751a37..7d0b6885 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -169,10 +169,11 @@ type aFunction struct { defer_ *aDefer recov BasicBlock - params []Type - freeVars Expr - base int // base = 1 if hasFreeVars; base = 0 otherwise - hasVArg bool + params []Type + freeVars Expr + freeVarsBlock int + base int // base = 1 if hasFreeVars; base = 0 otherwise + hasVArg bool } // Function represents a function or method. @@ -249,12 +250,13 @@ func (p Function) Param(i int) Expr { } func (p Function) closureCtx(b Builder) Expr { - if p.freeVars.IsNil() { + if p.freeVars.IsNil() || (p.freeVarsBlock != 0 && p.freeVarsBlock != b.blk.Index()) { if p.base == 0 { panic("ssa: function has no free variables") } ptr := Expr{p.impl.Param(0), p.params[0]} p.freeVars = b.Load(ptr) + p.freeVarsBlock = b.blk.Index() } return p.freeVars } From d2538d08a7eb370c4ce529fa967f101846fbc3ab Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 7 Sep 2024 10:20:02 +0800 Subject: [PATCH 9/9] code clean --- x/async/async.go | 4 ++-- x/async/executor_go.go | 11 +---------- x/async/executor_llgo.go | 5 +++-- x/cbind/cbind.go | 24 ++++++++++++------------ x/socketio/socketio_llgo.go | 4 ++-- 5 files changed, 20 insertions(+), 28 deletions(-) diff --git a/x/async/async.go b/x/async/async.go index 268bce60..fcd9a2e9 100644 --- a/x/async/async.go +++ b/x/async/async.go @@ -25,6 +25,6 @@ type Void = [0]byte type Future[T any] func(func(T)) // Just for pure LLGo/Go, transpile to callback in Go+ -func Await[T1 any](call Future[T1]) (ret T1) { - return Run(call) +func Await[T1 any](future Future[T1]) T1 { + return Run(future) } diff --git a/x/async/executor_go.go b/x/async/executor_go.go index cd9fb891..60962638 100644 --- a/x/async/executor_go.go +++ b/x/async/executor_go.go @@ -19,16 +19,7 @@ package async -var exec = &Executor{} - -type Executor struct { -} - -func Exec() *Executor { - return exec -} - -func Run[T any](future Future[T]) (ret T) { +func Run[T any](future Future[T]) T { ch := make(chan T) go func() { future(func(v T) { diff --git a/x/async/executor_llgo.go b/x/async/executor_llgo.go index fd5e4e14..e1e03a6a 100644 --- a/x/async/executor_llgo.go +++ b/x/async/executor_llgo.go @@ -54,15 +54,16 @@ func (e *Executor) Run() { e.L.Run(libuv.RUN_DEFAULT) } -func Run[T any](future Future[T]) (ret T) { +func Run[T any](future Future[T]) T { loop := libuv.LoopNew() exec := &Executor{loop} oldExec := setExec(exec) + var ret T future(func(v T) { ret = v }) exec.Run() loop.Close() setExec(oldExec) - return + return ret } diff --git a/x/cbind/cbind.go b/x/cbind/cbind.go index 84b87528..7ec34b99 100644 --- a/x/cbind/cbind.go +++ b/x/cbind/cbind.go @@ -52,22 +52,22 @@ type bind3[Base any, A any, B any, C any] struct { fn func(*Base, A, B, C) } -func Callback[Base any](base *Base) { +func callback[Base any](base *Base) { bind := (*bind[Base])(unsafe.Pointer(base)) bind.fn(base) } -func Callback1[Base any, A any](base *Base, a A) { +func callback1[Base any, A any](base *Base, a A) { bind := (*bind1[Base, A])(unsafe.Pointer(base)) bind.fn(base, a) } -func Callback2[Base any, A any, B any](base *Base, a A, b B) { +func callback2[Base any, A any, B any](base *Base, a A, b B) { bind := (*bind2[Base, A, B])(unsafe.Pointer(base)) bind.fn(base, a, b) } -func Callback3[Base any, A any, B any, C any](base *Base, a A, b B, c C) { +func callback3[Base any, A any, B any, C any](base *Base, a A, b B, c C) { bind := (*bind3[Base, A, B, C])(unsafe.Pointer(base)) bind.fn(base, a, b, c) } @@ -89,55 +89,55 @@ func Callback3[Base any, A any, B any, C any](base *Base, a A, b B, c C) { func Bind[T any](call func(*T)) (p *T, cb Cb[T]) { bb := &bind[T]{fn: call} p = (*T)(unsafe.Pointer(bb)) - cb = Callback[T] + cb = callback[T] return } func BindF[T any, F ~func(*T)](call func(*T)) (*T, F) { bb := &bind[T]{fn: call} p := (*T)(unsafe.Pointer(bb)) - var fn F = Callback[T] + var fn F = callback[T] return p, fn } func Bind1[T any, A any](call func(*T, A)) (p *T, cb Cb1[T, A]) { bb := &bind1[T, A]{fn: call} p = (*T)(unsafe.Pointer(bb)) - cb = Callback1[T, A] + cb = callback1[T, A] return } func Bind1F[T any, F ~func(*T, A), A any](call func(*T, A)) (*T, F) { bb := &bind1[T, A]{fn: call} p := (*T)(unsafe.Pointer(bb)) - var fn F = Callback1[T, A] + var fn F = callback1[T, A] return p, fn } func Bind2[T any, A any, B any](call func(*T, A, B)) (p *T, cb Cb2[T, A, B]) { bb := &bind2[T, A, B]{fn: call} p = (*T)(unsafe.Pointer(bb)) - cb = Callback2[T, A, B] + cb = callback2[T, A, B] return } func Bind2F[T any, F ~func(*T, A, B), A any, B any](call func(*T, A, B)) (*T, F) { bb := &bind2[T, A, B]{fn: call} p := (*T)(unsafe.Pointer(bb)) - var fn F = Callback2[T, A, B] + var fn F = callback2[T, A, B] return p, fn } func Bind3[T any, A any, B any, C any](call func(*T, A, B, C), a A, b B, c C) (p *T, cb Cb3[T, A, B, C]) { bb := &bind3[T, A, B, C]{fn: call} p = (*T)(unsafe.Pointer(bb)) - cb = Callback3[T, A, B, C] + cb = callback3[T, A, B, C] return } func Bind3F[T any, F ~func(*T, A, B, C), A any, B any, C any](call func(*T, A, B, C), a A, b B, c C) (*T, F) { bb := &bind3[T, A, B, C]{fn: call} p := (*T)(unsafe.Pointer(bb)) - var fn F = Callback3[T, A, B, C] + var fn F = callback3[T, A, B, C] return p, fn } diff --git a/x/socketio/socketio_llgo.go b/x/socketio/socketio_llgo.go index cff5305d..e6b799a5 100644 --- a/x/socketio/socketio_llgo.go +++ b/x/socketio/socketio_llgo.go @@ -241,7 +241,7 @@ func (t *Conn) Read() async.Future[tuple.Tuple2[[]byte, error]] { func (t *Conn) Write(data []byte) async.Future[error] { return async.Async(func(resolve func(error)) { - writer, _ := cbind.Bind1[libuv.Write](func(req *libuv.Write, status c.Int) { + writer, cb := cbind.Bind1F[libuv.Write, libuv.WriteCb](func(req *libuv.Write, status c.Int) { var result error if status != 0 { result = libuvError(libuv.Errno(status)) @@ -251,7 +251,7 @@ func (t *Conn) Write(data []byte) async.Future[error] { tcp := (*libuv.Stream)(&t.tcp) buf, len := cbind.CBuffer(data) bufs := &libuv.Buf{Base: buf, Len: uintptr(len)} - writer.Write(tcp, bufs, 1, cbind.Callback1[libuv.Write, c.Int]) + writer.Write(tcp, bufs, 1, cb) }) }