interp: rewrite entire package

For a full explanation, see interp/README.md. In short, this rewrite is
a redesign of the partial evaluator which improves it over the previous
partial evaluator. The main functional difference is that when
interpreting a function, the interpretation can be rolled back when an
unsupported instruction is encountered (for example, an actual unknown
instruction or a branch on a value that's only known at runtime). This
also means that it is no longer necessary to scan functions to see
whether they can be interpreted: instead, this package now just tries to
interpret it and reverts when it can't go further.

This new design has several benefits:

  * Most errors coming from the interp package are avoided, as it can
    simply skip the code it can't handle. This has long been an issue.
  * The memory model has been improved, which means some packages now
    pass all tests that previously didn't pass them.
  * Because of a better design, it is in fact a bit faster than the
    previous version.

This means the following packages now pass tests with `tinygo test`:

  * hash/adler32: previously it would hang in an infinite loop
  * math/cmplx: previously it resulted in errors

This also means that the math/big package can be imported. It would
previously fail with a "interp: branch on a non-constant" error.
This commit is contained in:
Ayke van Laethem
2020-09-27 14:07:46 +02:00
committed by Ron Evans
parent e9d549d211
commit 30df912565
20 changed files with 3044 additions and 1894 deletions
+16 -12
View File
@@ -11,15 +11,19 @@ import (
"tinygo.org/x/go-llvm"
)
// errUnreachable is returned when an unreachable instruction is executed. This
// error should not be visible outside of the interp package.
var errUnreachable = &Error{Err: errors.New("interp: unreachable executed")}
var errLiteralToPointer = errors.New("interp: trying to convert literal value to pointer")
// unsupportedInstructionError returns a new "unsupported instruction" error for
// the given instruction. It includes source location information, when
// available.
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Error {
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
// These errors are expected during normal execution and can be recovered from
// by running the affected function at runtime instead of compile time.
var (
errExpectedPointer = errors.New("interp: trying to use an integer as a pointer (memory-mapped I/O?)")
errUnsupportedInst = errors.New("interp: unsupported instruction")
errUnsupportedRuntimeInst = errors.New("interp: unsupported instruction (to be emitted at runtime)")
errMapAlreadyCreated = errors.New("interp: map already created")
)
func isRecoverableError(err error) bool {
return err == errExpectedPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated
}
// ErrorLine is one line in a traceback. The position may be missing.
@@ -46,13 +50,13 @@ func (e *Error) Error() string {
// errorAt returns an error value for the currently interpreted package at the
// location of the instruction. The location information may not be complete as
// it depends on debug information in the IR.
func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error {
pos := getPosition(inst)
func (r *runner) errorAt(inst instruction, err error) *Error {
pos := getPosition(inst.llvmInst)
return &Error{
ImportPath: e.packagePath,
ImportPath: r.pkgName,
Pos: pos,
Err: err,
Traceback: []ErrorLine{{pos, inst}},
Traceback: []ErrorLine{{pos, inst.llvmInst}},
}
}