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.
Этот коммит содержится в:
родитель
c4191da2a5
коммит
3d13f9cfe1
2 изменённых файлов: 63 добавлений и 3 удалений
10
main.go
10
main.go
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
"github.com/tinygo-org/tinygo/interp"
|
"github.com/tinygo-org/tinygo/interp"
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
|
"github.com/tinygo-org/tinygo/transform"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
|
|
||||||
"go.bug.st/serial"
|
"go.bug.st/serial"
|
||||||
|
@ -776,6 +777,15 @@ func printCompilerError(logln func(...interface{}), err error) {
|
||||||
logln()
|
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:
|
case loader.Errors:
|
||||||
logln("#", err.Pkg.ImportPath)
|
logln("#", err.Pkg.ImportPath)
|
||||||
for _, err := range err.Errs {
|
for _, err := range err.Errs {
|
||||||
|
|
|
@ -5,6 +5,7 @@ package transform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"go/token"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
|
@ -104,6 +105,31 @@ func LowerCoroutines(mod llvm.Module, needStackSlots bool) error {
|
||||||
return nil
|
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.
|
// asyncFunc is a metadata container for an asynchronous function.
|
||||||
type asyncFunc struct {
|
type asyncFunc struct {
|
||||||
// fn is the underlying function pointer.
|
// fn is the underlying function pointer.
|
||||||
|
@ -168,10 +194,11 @@ type coroutineLoweringPass struct {
|
||||||
|
|
||||||
// findAsyncFuncs finds all asynchronous functions.
|
// findAsyncFuncs finds all asynchronous functions.
|
||||||
// A function is considered asynchronous if it calls an asynchronous function or intrinsic.
|
// 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{}
|
asyncs := map[llvm.Value]*asyncFunc{}
|
||||||
asyncsOrdered := []llvm.Value{}
|
asyncsOrdered := []llvm.Value{}
|
||||||
calls := []llvm.Value{}
|
calls := []llvm.Value{}
|
||||||
|
callsAsyncFunction := map[llvm.Value]asyncCallInfo{}
|
||||||
|
|
||||||
// Use a breadth-first search to find all async functions.
|
// Use a breadth-first search to find all async functions.
|
||||||
worklist := []llvm.Value{c.pause}
|
worklist := []llvm.Value{c.pause}
|
||||||
|
@ -183,7 +210,18 @@ func (c *coroutineLoweringPass) findAsyncFuncs() {
|
||||||
// Get task pointer argument.
|
// Get task pointer argument.
|
||||||
task := fn.LastParam()
|
task := fn.LastParam()
|
||||||
if fn != c.pause && (task.IsNil() || task.Name() != "parentHandle") {
|
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.
|
// Search all uses of the function while collecting callers.
|
||||||
|
@ -218,6 +256,13 @@ func (c *coroutineLoweringPass) findAsyncFuncs() {
|
||||||
asyncs[caller] = nil
|
asyncs[caller] = nil
|
||||||
asyncsOrdered = append(asyncsOrdered, caller)
|
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.
|
// Put the caller on the worklist.
|
||||||
worklist = append(worklist, caller)
|
worklist = append(worklist, caller)
|
||||||
}
|
}
|
||||||
|
@ -243,6 +288,8 @@ func (c *coroutineLoweringPass) findAsyncFuncs() {
|
||||||
c.asyncFuncs = asyncs
|
c.asyncFuncs = asyncs
|
||||||
c.asyncFuncsOrdered = asyncFuncsOrdered
|
c.asyncFuncsOrdered = asyncFuncsOrdered
|
||||||
c.calls = calls
|
c.calls = calls
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *coroutineLoweringPass) load() error {
|
func (c *coroutineLoweringPass) load() error {
|
||||||
|
@ -302,7 +349,10 @@ func (c *coroutineLoweringPass) load() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find async functions.
|
// Find async functions.
|
||||||
c.findAsyncFuncs()
|
err := c.findAsyncFuncs()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Get i8* type.
|
// Get i8* type.
|
||||||
c.i8ptr = llvm.PointerType(c.ctx.Int8Type(), 0)
|
c.i8ptr = llvm.PointerType(c.ctx.Int8Type(), 0)
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче