diff options
-rw-r--r-- | builder/build.go | 4 | ||||
-rw-r--r-- | compileopts/options.go | 2 | ||||
-rw-r--r-- | interp/interp.go | 12 | ||||
-rw-r--r-- | interp/interp_test.go | 3 | ||||
-rw-r--r-- | interp/interpreter.go | 9 | ||||
-rw-r--r-- | main.go | 2 | ||||
-rw-r--r-- | main_test.go | 30 |
7 files changed, 34 insertions, 28 deletions
diff --git a/builder/build.go b/builder/build.go index c6d0ff267..b171dc1f2 100644 --- a/builder/build.go +++ b/builder/build.go @@ -431,7 +431,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil if pkgInit.IsNil() { panic("init not found for " + pkg.Pkg.Path()) } - err := interp.RunFunc(pkgInit, config.DumpSSA()) + err := interp.RunFunc(pkgInit, config.Options.InterpTimeout, config.DumpSSA()) if err != nil { return err } @@ -1055,7 +1055,7 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c // needed to convert a program to its final form. Some transformations are not // optional and must be run as the compiler expects them to run. func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { - err := interp.Run(mod, config.DumpSSA()) + err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) if err != nil { return err } diff --git a/compileopts/options.go b/compileopts/options.go index 23320c027..80667b8ee 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strings" + "time" ) var ( @@ -29,6 +30,7 @@ type Options struct { Scheduler string Serial string Work bool // -work flag to print temporary build directory + InterpTimeout time.Duration PrintIR bool DumpSSA bool VerifyIR bool diff --git a/interp/interp.go b/interp/interp.go index 87aadf37e..a1f5f507a 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -30,10 +30,11 @@ type runner struct { objects []object // slice of objects in memory globals map[llvm.Value]int // map from global to index in objects slice start time.Time + timeout time.Duration callsExecuted uint64 } -func newRunner(mod llvm.Module, debug bool) *runner { +func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner { r := runner{ mod: mod, targetData: llvm.NewTargetData(mod.DataLayout()), @@ -42,6 +43,7 @@ func newRunner(mod llvm.Module, debug bool) *runner { objects: []object{{}}, globals: make(map[llvm.Value]int), start: time.Now(), + timeout: timeout, } r.pointerSize = uint32(r.targetData.PointerSize()) r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0) @@ -58,8 +60,8 @@ func (r *runner) dispose() { // Run evaluates runtime.initAll function as much as possible at compile time. // Set debug to true if it should print output while running. -func Run(mod llvm.Module, debug bool) error { - r := newRunner(mod, debug) +func Run(mod llvm.Module, timeout time.Duration, debug bool) error { + r := newRunner(mod, timeout, debug) defer r.dispose() initAll := mod.NamedFunction("runtime.initAll") @@ -199,10 +201,10 @@ func Run(mod llvm.Module, debug bool) error { // RunFunc evaluates a single package initializer at compile time. // Set debug to true if it should print output while running. -func RunFunc(fn llvm.Value, debug bool) error { +func RunFunc(fn llvm.Value, timeout time.Duration, debug bool) error { // Create and initialize *runner object. mod := fn.GlobalParent() - r := newRunner(mod, debug) + r := newRunner(mod, timeout, debug) defer r.dispose() initName := fn.Name() if !strings.HasSuffix(initName, ".init") { diff --git a/interp/interp_test.go b/interp/interp_test.go index c42586533..55ad3eaef 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" "testing" + "time" "tinygo.org/x/go-llvm" ) @@ -52,7 +53,7 @@ func runTest(t *testing.T, pathPrefix string) { defer mod.Dispose() // Perform the transform. - err = Run(mod, false) + err = Run(mod, 10*time.Minute, false) if err != nil { if err, match := err.(*Error); match { println(err.Error()) diff --git a/interp/interpreter.go b/interp/interpreter.go index 317bc6c03..95c99c842 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -17,8 +17,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent locals := make([]value, len(fn.locals)) r.callsExecuted++ - t0 := time.Since(r.start) - // Parameters are considered a kind of local values. for i, param := range params { locals[i] = param @@ -143,11 +141,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } switch inst.opcode { case llvm.Ret: - const maxInterpSeconds = 180 - if t0 > maxInterpSeconds*time.Second { - // Running for more than maxInterpSeconds seconds. This should never happen, but does. + if time.Since(r.start) > r.timeout { + // Running for more than the allowed timeout; This shouldn't happen, but it does. // See github.com/tinygo-org/tinygo/issues/2124 - return nil, mem, r.errorAt(fn.blocks[0].instructions[0], fmt.Errorf("interp: running for more than %d seconds, timing out (executed calls: %d)", maxInterpSeconds, r.callsExecuted)) + return nil, mem, r.errorAt(fn.blocks[0].instructions[0], fmt.Errorf("interp: running for more than %s, timing out (executed calls: %d)", r.timeout, r.callsExecuted)) } if len(operands) != 0 { @@ -1295,6 +1295,7 @@ func main() { scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)") serial := flag.String("serial", "", "which serial output to use (none, uart, usb)") work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") + interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") printIR := flag.Bool("printir", false, "print LLVM IR") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR") @@ -1385,6 +1386,7 @@ func main() { Scheduler: *scheduler, Serial: *serial, Work: *work, + InterpTimeout: *interpTimeout, PrintIR: *printIR, DumpSSA: *dumpSSA, VerifyIR: *verifyIR, diff --git a/main_test.go b/main_test.go index aa96e8564..bc7906482 100644 --- a/main_test.go +++ b/main_test.go @@ -275,14 +275,15 @@ func emuCheck(t *testing.T, options compileopts.Options) { func optionsFromTarget(target string, sema chan struct{}) compileopts.Options { return compileopts.Options{ // GOOS/GOARCH are only used if target == "" - GOOS: goenv.Get("GOOS"), - GOARCH: goenv.Get("GOARCH"), - GOARM: goenv.Get("GOARM"), - Target: target, - Semaphore: sema, - Debug: true, - VerifyIR: true, - Opt: "z", + GOOS: goenv.Get("GOOS"), + GOARCH: goenv.Get("GOARCH"), + GOARM: goenv.Get("GOARM"), + Target: target, + Semaphore: sema, + InterpTimeout: 180 * time.Second, + Debug: true, + VerifyIR: true, + Opt: "z", } } @@ -292,12 +293,13 @@ func optionsFromTarget(target string, sema chan struct{}) compileopts.Options { func optionsFromOSARCH(osarch string, sema chan struct{}) compileopts.Options { parts := strings.Split(osarch, "/") options := compileopts.Options{ - GOOS: parts[0], - GOARCH: parts[1], - Semaphore: sema, - Debug: true, - VerifyIR: true, - Opt: "z", + GOOS: parts[0], + GOARCH: parts[1], + Semaphore: sema, + InterpTimeout: 180 * time.Second, + Debug: true, + VerifyIR: true, + Opt: "z", } if options.GOARCH == "arm" { options.GOARM = parts[2] |