diff options
author | Ayke van Laethem <[email protected]> | 2021-03-05 14:07:49 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2021-03-05 14:42:43 +0100 |
commit | 3d13f9cfe198e44aec81b7cf0d79061509410e9b (patch) | |
tree | 6d8bfe3c7cb5277d5d897ade32bb9df243c2af6f | |
parent | c4191da2a542cd77b4231638ddac2e0b1ac2d756 (diff) | |
download | tinygo-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.go | 10 | ||||
-rw-r--r-- | transform/coroutines.go | 56 |
2 files changed, 63 insertions, 3 deletions
@@ -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) |