aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2021-03-05 14:07:49 +0100
committerRon Evans <[email protected]>2021-03-05 14:42:43 +0100
commit3d13f9cfe198e44aec81b7cf0d79061509410e9b (patch)
tree6d8bfe3c7cb5277d5d897ade32bb9df243c2af6f
parentc4191da2a542cd77b4231638ddac2e0b1ac2d756 (diff)
downloadtinygo-3d13f9cfe198e44aec81b7cf0d79061509410e9b.tar.gz
tinygo-3d13f9cfe198e44aec81b7cf0d79061509410e9b.zip
transform: show better error message in coroutines lowering
A common error is when someone tries to export a blocking function. This is not possible with the coroutines scheduler. Previously, it resulted in an error like this: panic: trying to make exported function async: messageHandler With this change, the error is much better and shows where it comes from exactly: /home/ayke/tmp/export-async.go:8: blocking operation in exported function: messageHandler traceback: messageHandler /home/ayke/tmp/export-async.go:9:5 main.foo /home/ayke/tmp/export-async.go:15:2 runtime.chanSend /home/ayke/src/github.com/tinygo-org/tinygo/src/runtime/chan.go:494:12 This should make it easier to identify and fix the problem. And it avoids a compiler panic, which is a really bad way of showing diagnostics.
-rw-r--r--main.go10
-rw-r--r--transform/coroutines.go56
2 files changed, 63 insertions, 3 deletions
diff --git a/main.go b/main.go
index aa81c35ec..2b52c9390 100644
--- a/main.go
+++ b/main.go
@@ -25,6 +25,7 @@ import (
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"
+ "github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
"go.bug.st/serial"
@@ -776,6 +777,15 @@ func printCompilerError(logln func(...interface{}), err error) {
logln()
}
}
+ case transform.CoroutinesError:
+ logln(err.Pos.String() + ": " + err.Msg)
+ logln("\ntraceback:")
+ for _, line := range err.Traceback {
+ logln(line.Name)
+ if line.Position.IsValid() {
+ logln("\t" + line.Position.String())
+ }
+ }
case loader.Errors:
logln("#", err.Pkg.ImportPath)
for _, err := range err.Errs {
diff --git a/transform/coroutines.go b/transform/coroutines.go
index 3756da257..2aeff43c9 100644
--- a/transform/coroutines.go
+++ b/transform/coroutines.go
@@ -5,6 +5,7 @@ package transform
import (
"errors"
+ "go/token"
"strconv"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
@@ -104,6 +105,31 @@ func LowerCoroutines(mod llvm.Module, needStackSlots bool) error {
return nil
}
+// CoroutinesError is an error returned when coroutine lowering failed, for
+// example because an async function is exported.
+type CoroutinesError struct {
+ Msg string
+ Pos token.Position
+ Traceback []CoroutinesErrorLine
+}
+
+// CoroutinesErrorLine is a single line of a CoroutinesError traceback.
+type CoroutinesErrorLine struct {
+ Name string // function name
+ Position token.Position // position in the function
+}
+
+// Error implements the error interface by returning a simple error message
+// without the stack.
+func (err CoroutinesError) Error() string {
+ return err.Msg
+}
+
+type asyncCallInfo struct {
+ fn llvm.Value
+ call llvm.Value
+}
+
// asyncFunc is a metadata container for an asynchronous function.
type asyncFunc struct {
// fn is the underlying function pointer.
@@ -168,10 +194,11 @@ type coroutineLoweringPass struct {
// findAsyncFuncs finds all asynchronous functions.
// A function is considered asynchronous if it calls an asynchronous function or intrinsic.
-func (c *coroutineLoweringPass) findAsyncFuncs() {
+func (c *coroutineLoweringPass) findAsyncFuncs() error {
asyncs := map[llvm.Value]*asyncFunc{}
asyncsOrdered := []llvm.Value{}
calls := []llvm.Value{}
+ callsAsyncFunction := map[llvm.Value]asyncCallInfo{}
// Use a breadth-first search to find all async functions.
worklist := []llvm.Value{c.pause}
@@ -183,7 +210,18 @@ func (c *coroutineLoweringPass) findAsyncFuncs() {
// Get task pointer argument.
task := fn.LastParam()
if fn != c.pause && (task.IsNil() || task.Name() != "parentHandle") {
- panic("trying to make exported function async: " + fn.Name())
+ // Exported functions must not do async operations.
+ err := CoroutinesError{
+ Msg: "blocking operation in exported function: " + fn.Name(),
+ Pos: getPosition(fn),
+ }
+ f := fn
+ for !f.IsNil() && f != c.pause {
+ data := callsAsyncFunction[f]
+ err.Traceback = append(err.Traceback, CoroutinesErrorLine{f.Name(), getPosition(data.call)})
+ f = data.fn
+ }
+ return err
}
// Search all uses of the function while collecting callers.
@@ -218,6 +256,13 @@ func (c *coroutineLoweringPass) findAsyncFuncs() {
asyncs[caller] = nil
asyncsOrdered = append(asyncsOrdered, caller)
+ // Track which calls caused this function to be marked async (for
+ // better diagnostics).
+ callsAsyncFunction[caller] = asyncCallInfo{
+ fn: fn,
+ call: user,
+ }
+
// Put the caller on the worklist.
worklist = append(worklist, caller)
}
@@ -243,6 +288,8 @@ func (c *coroutineLoweringPass) findAsyncFuncs() {
c.asyncFuncs = asyncs
c.asyncFuncsOrdered = asyncFuncsOrdered
c.calls = calls
+
+ return nil
}
func (c *coroutineLoweringPass) load() error {
@@ -302,7 +349,10 @@ func (c *coroutineLoweringPass) load() error {
}
// Find async functions.
- c.findAsyncFuncs()
+ err := c.findAsyncFuncs()
+ if err != nil {
+ return err
+ }
// Get i8* type.
c.i8ptr = llvm.PointerType(c.ctx.Int8Type(), 0)