From ee150bd902673adb9382487ee94c2ec52462a0ec Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Sat, 6 Jan 2024 10:17:09 +1000 Subject: [PATCH 1/3] Support range over int --- compiler/coroutine_test.go | 6 +++ compiler/desugar.go | 34 ++++++++++++++- compiler/desugar_test.go | 57 ++++++++++++++++++++++++++ compiler/testdata/coroutine.go | 6 +++ compiler/testdata/coroutine_durable.go | 48 ++++++++++++++++++++++ go.mod | 2 +- 6 files changed, 151 insertions(+), 2 deletions(-) diff --git a/compiler/coroutine_test.go b/compiler/coroutine_test.go index 5139d9a..0c2e0e3 100644 --- a/compiler/coroutine_test.go +++ b/compiler/coroutine_test.go @@ -249,6 +249,12 @@ func TestCoroutineYield(t *testing.T) { coro: func() { IndirectClosure(1) }, yields: []int{-1, 1, 2, 3}, }, + + { + name: "range over int", + coro: func() { RangeOverInt(3) }, + yields: []int{0, 1, 2}, + }, } // This emulates the installation of function type information by the diff --git a/compiler/desugar.go b/compiler/desugar.go index 2764b4e..e6b66d0 100644 --- a/compiler/desugar.go +++ b/compiler/desugar.go @@ -246,7 +246,39 @@ func (d *desugarer) desugar(stmt ast.Stmt, breakTo, continueTo, userLabel *ast.I } prologue := d.desugarList([]ast.Stmt{init}, nil, nil) + intType := types.Typ[types.Int] + switch rangeElemType := d.info.TypeOf(s.X).(type) { + case *types.Basic: + switch rangeElemType.Kind() { + case types.Int: + // Rewrite for range loops over int: + // - `for range x {}` => `{ _x := x; for _i := 0; _i < _x; _i++ {} }` + // - `for _ := range x {}` => `{ _x := x; for _i := 0; _i < _x; _i++ {} }` + // - `for i := range x {}` => `{ _x := x; for i := 0; i < _x; i++ {} }` + var i *ast.Ident + if s.Key == nil || isUnderscore(s.Key) { + i = d.newVar(intType) + } else { + i = s.Key.(*ast.Ident) + } + forStmt := &ast.ForStmt{ + Init: &ast.AssignStmt{Lhs: []ast.Expr{i}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "0"}}}, + Post: &ast.IncDecStmt{X: i, Tok: token.INC}, + Cond: &ast.BinaryExpr{X: i, Op: token.LSS, Y: x}, + Body: s.Body, + } + if d.mayYield(s.Body) { + d.nodesThatMayYield[forStmt] = struct{}{} + } + stmt = &ast.BlockStmt{ + List: append(prologue, d.desugar(forStmt, breakTo, continueTo, userLabel)), + } + + default: + panic(fmt.Sprintf("not implemented: for range over %T", rangeElemType)) + } + case *types.Array, *types.Slice: // Rewrite for range loops over arrays/slices: // - `for range x {}` => `{ _x := x; for _i := 0; _i < len(_x); _i++ {} }` @@ -259,7 +291,7 @@ func (d *desugarer) desugar(stmt ast.Stmt, breakTo, continueTo, userLabel *ast.I // Then, desugar loops further (see ast.ForStmt case above). var i *ast.Ident if s.Key == nil || isUnderscore(s.Key) { - i = d.newVar(types.Typ[types.Int]) + i = d.newVar(intType) } else { i = s.Key.(*ast.Ident) } diff --git a/compiler/desugar_test.go b/compiler/desugar_test.go index a084561..a68e748 100644 --- a/compiler/desugar_test.go +++ b/compiler/desugar_test.go @@ -1144,6 +1144,63 @@ defer func() { _v0 := time.Now() _v0.UTC() } +`, + }, + { + name: "for range over int", + body: "for range n { foo }", + info: func(stmts []ast.Stmt, info *types.Info) { + n := stmts[0].(*ast.RangeStmt).X + info.Types[n] = types.TypeAndValue{Type: intType} + }, + expect: ` +{ + _v0 := n + { + _v1 := 0 + for ; _v1 < _v0; _v1++ { + foo + } + } +} +`, + }, + { + name: "for range over int, with underscore index", + body: "for _ := range n { foo }", + info: func(stmts []ast.Stmt, info *types.Info) { + n := stmts[0].(*ast.RangeStmt).X + info.Types[n] = types.TypeAndValue{Type: intType} + }, + expect: ` +{ + _v0 := n + { + _v1 := 0 + for ; _v1 < _v0; _v1++ { + foo + } + } +} +`, + }, + { + name: "for range over int, with index", + body: "for i := range n { foo }", + info: func(stmts []ast.Stmt, info *types.Info) { + n := stmts[0].(*ast.RangeStmt).X + info.Types[n] = types.TypeAndValue{Type: intType} + }, + expect: ` +{ + _v0 := n + { + i := 0 + for ; i < _v0; i++ { + foo + } + } +} `, }, } { diff --git a/compiler/testdata/coroutine.go b/compiler/testdata/coroutine.go index 60e9401..0555fb4 100644 --- a/compiler/testdata/coroutine.go +++ b/compiler/testdata/coroutine.go @@ -656,3 +656,9 @@ func indirectClosure(m interface{ YieldAndInc() }) func() { m.YieldAndInc() } } + +func RangeOverInt(n int) { + for i := range n { + coroutine.Yield[int, any](i) + } +} diff --git a/compiler/testdata/coroutine_durable.go b/compiler/testdata/coroutine_durable.go index 7029423..a9ecb61 100644 --- a/compiler/testdata/coroutine_durable.go +++ b/compiler/testdata/coroutine_durable.go @@ -3634,6 +3634,53 @@ func indirectClosure(_fn0 interface{ YieldAndInc() }) (_ func()) { } panic("unreachable") } + +//go:noinline +func RangeOverInt(_fn0 int) { + _c := coroutine.LoadContext[int, any]() + var _f0 *struct { + IP int + X0 int + X1 int + X2 int + } = coroutine.Push[struct { + IP int + X0 int + X1 int + X2 int + }](&_c.Stack) + if _f0.IP == 0 { + *_f0 = struct { + IP int + X0 int + X1 int + X2 int + }{X0: _fn0} + } + defer func() { + if !_c.Unwinding() { + coroutine.Pop(&_c.Stack) + } + }() + switch { + case _f0.IP < 2: + _f0.X1 = _f0.X0 + _f0.IP = 2 + fallthrough + case _f0.IP < 4: + switch { + case _f0.IP < 3: + _f0.X2 = 0 + _f0.IP = 3 + fallthrough + case _f0.IP < 4: + for ; _f0.X2 < _f0.X1; _f0.X2, _f0.IP = _f0.X2+1, 3 { + + coroutine.Yield[int, any](_f0.X2) + } + } + } +} func init() { _types.RegisterFunc[func(_fn1 int) (_ func(int))]("github.com/stealthrocket/coroutine/compiler/testdata.(*Box).Closure") _types.RegisterClosure[func(_fn0 int), struct { @@ -3741,6 +3788,7 @@ func init() { }]("github.com/stealthrocket/coroutine/compiler/testdata.Range10ClosureHeterogenousCapture.func3") _types.RegisterFunc[func()]("github.com/stealthrocket/coroutine/compiler/testdata.Range10Heterogenous") _types.RegisterFunc[func(_ int)]("github.com/stealthrocket/coroutine/compiler/testdata.RangeArrayIndexValueGenerator") + _types.RegisterFunc[func(_fn0 int)]("github.com/stealthrocket/coroutine/compiler/testdata.RangeOverInt") _types.RegisterFunc[func(_fn0 int)]("github.com/stealthrocket/coroutine/compiler/testdata.RangeOverMaps") _types.RegisterFunc[func(_fn0 int)]("github.com/stealthrocket/coroutine/compiler/testdata.RangeReverseClosureCaptureByValue") _types.RegisterClosure[func(), struct { diff --git a/go.mod b/go.mod index 92244b6..c2c6889 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/stealthrocket/coroutine -go 1.21.0 +go 1.22 require ( golang.org/x/sync v0.5.0 From 8cb88c0d42c145c5da381e5c11a6b85e83185851 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Fri, 9 Feb 2024 11:10:55 +1000 Subject: [PATCH 2/3] Fix go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c2c6889..c1381cc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/stealthrocket/coroutine -go 1.22 +go 1.22.0 require ( golang.org/x/sync v0.5.0 From 175d8b7115f1add8752bbad80e6bc1444b7de7e7 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Fri, 9 Feb 2024 11:16:40 +1000 Subject: [PATCH 3/3] Bump lint --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e6f594b..7e2de15 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: - run: go mod download - uses: golangci/golangci-lint-action@v3 with: - version: v1.55.2 + version: v1.56.1 args: --timeout 5m test: