Skip to content

Commit

Permalink
Allow go statements in certain circumstances (#150)
Browse files Browse the repository at this point in the history
We don't currently allow `go` statements when compiling functions
because there's no structured concurrency in Go; goroutines are executed
independently (there's no hierarchy; no concept of ownership), and we
are unable to suspend execution of related goroutines when another
goroutine explicitly yields.

The compiler only needs to compile a subset of functions in the program;
those that yield, those that may be on the call stack of a coroutine
that yields, or those that contain a function literal that yields or may
end up on the call stack of a coroutine that yields. All other functions
are allowed to use `go`.

This PR relaxes the restrictions a bit so that certain functions are now
allowed to use `go`.

The major use case we unlock is to allow patterns like this, which seems
to come up a lot:

```go
func outer() {
    coroutine := func(...) { /* e.g. explicitly yields here */ }

    go func() { /* do something unrelated */ }()
}
```

If the compiler determines that the `coroutine` function literal yields
explicitly, or may end up on the call stack of a coroutine that yields
explicitly, it needs to compile both the inner function literal and the
outer function (since
#135). We currently reject
the `go` statement in the outer function, even though it may not
interact with any coroutines.

This PR relaxes the restriction; we allow `go` statements in this one
instance. The goroutine could still mutate state from the outer function
which is then read/mutated within the inner function literal. Given that
we do not support serializing synchronization primitives at this time
(e.g. channels, mutexes), the assumption is that the user isn't doing
anything unsafe and that it's fine to allow the `go` statement. It
prints a warning just in case.
  • Loading branch information
chriso authored Jun 25, 2024
2 parents 9fefde4 + af67c21 commit f730716
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 4 deletions.
4 changes: 2 additions & 2 deletions compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ func (c *compiler) compilePackage(p *packages.Package, colors functionColors) er
compiled := false
if color != nil || containsColoredFuncLit(decl, colorsByFunc) {
// Reject certain language features for now.
if err := unsupported(decl, p.TypesInfo); err != nil {
if err := c.unsupported(decl, p.TypesInfo, colorsByFunc); err != nil {
return err
}
scope := &scope{compiler: c, colors: colorsByFunc}
Expand Down Expand Up @@ -433,7 +433,7 @@ func (c *compiler) compilePackage(p *packages.Package, colors functionColors) er
return nil
}

func containsColoredFuncLit(decl *ast.FuncDecl, colorsByFunc map[ast.Node]*types.Signature) (yes bool) {
func containsColoredFuncLit(decl ast.Node, colorsByFunc map[ast.Node]*types.Signature) (yes bool) {
ast.Inspect(decl, func(n ast.Node) bool {
if lit, ok := n.(*ast.FuncLit); ok {
if _, ok := colorsByFunc[lit]; ok {
Expand Down
22 changes: 20 additions & 2 deletions compiler/unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,35 @@ import (
"go/ast"
"go/token"
"go/types"
"log"
)

// unsupported checks a function for unsupported language features.
func unsupported(decl ast.Node, info *types.Info) (err error) {
func (c *compiler) unsupported(decl ast.Node, info *types.Info, colorsByFunc map[ast.Node]*types.Signature) (err error) {
ast.Inspect(decl, func(node ast.Node) bool {
switch nn := node.(type) {
case ast.Stmt:
switch n := nn.(type) {
// Not yet supported:
case *ast.GoStmt:
err = fmt.Errorf("not implemented: go")
if _, inColoredFunc := colorsByFunc[decl]; inColoredFunc {
err = fmt.Errorf("not implemented: go")
} else {
// Allow go statement in certain limited circumstances.
_, goColoredFunc := colorsByFunc[n.Call.Fun]
if !goColoredFunc {
switch fn := n.Call.Fun.(type) {
case *ast.FuncLit:
goColoredFunc = containsColoredFuncLit(fn, colorsByFunc)
}
}
if goColoredFunc {
err = fmt.Errorf("not implemented: go")
} else {
pos := c.fset.Position(n.Pos())
log.Printf("warning: goroutine mutations at %s may not be durable", pos)
}
}

// Partially supported:
case *ast.BranchStmt:
Expand Down

0 comments on commit f730716

Please sign in to comment.