diff --git a/builder/build.go b/builder/build.go index 035556c3b2..4231980536 100644 --- a/builder/build.go +++ b/builder/build.go @@ -446,7 +446,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if pkgInit.IsNil() { panic("init not found for " + pkg.Pkg.Path()) } - err := interp.RunFunc(pkgInit, config.Options.InterpTimeout, config.Options.InterpMaxDepth, config.DumpSSA()) + err := interp.RunFunc(pkgInit, config.Options.InterpTimeout, config.Options.InterpMaxDepth, config.Options.InterpMaxInstr, config.DumpSSA()) if err != nil { return err } @@ -1043,7 +1043,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.Options.InterpTimeout, config.Options.InterpMaxDepth, config.DumpSSA()) + err := interp.Run(mod, config.Options.InterpTimeout, config.Options.InterpMaxDepth, config.Options.InterpMaxInstr, config.DumpSSA()) if err != nil { return err } diff --git a/compileopts/options.go b/compileopts/options.go index 0b748a5f5d..b5ad42c298 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -33,6 +33,7 @@ type Options struct { Work bool // -work flag to print temporary build directory InterpTimeout time.Duration InterpMaxDepth int + InterpMaxInstr int PrintIR bool DumpSSA bool VerifyIR bool diff --git a/interp/interp.go b/interp/interp.go index 23311ff176..51d13952ee 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -32,10 +32,11 @@ type runner struct { start time.Time timeout time.Duration maxDepth int + maxInstr int callsExecuted uint64 } -func newRunner(mod llvm.Module, timeout time.Duration, maxDepth int, debug bool) *runner { +func newRunner(mod llvm.Module, timeout time.Duration, maxDepth int, maxInstr int, debug bool) *runner { r := runner{ mod: mod, targetData: llvm.NewTargetData(mod.DataLayout()), @@ -46,6 +47,7 @@ func newRunner(mod llvm.Module, timeout time.Duration, maxDepth int, debug bool) start: time.Now(), timeout: timeout, maxDepth: maxDepth, + maxInstr: maxInstr, } r.pointerSize = uint32(r.targetData.PointerSize()) r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0) @@ -62,8 +64,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, timeout time.Duration, maxDepth int, debug bool) error { - r := newRunner(mod, timeout, maxDepth, debug) +func Run(mod llvm.Module, timeout time.Duration, maxDepth int, maxInstr int, debug bool) error { + r := newRunner(mod, timeout, maxDepth, maxInstr, debug) defer r.dispose() initAll := mod.NamedFunction("runtime.initAll") @@ -203,10 +205,10 @@ func Run(mod llvm.Module, timeout time.Duration, maxDepth int, 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, timeout time.Duration, maxDepth int, debug bool) error { +func RunFunc(fn llvm.Value, timeout time.Duration, maxDepth int, maxInstr int, debug bool) error { // Create and initialize *runner object. mod := fn.GlobalParent() - r := newRunner(mod, timeout, maxDepth, debug) + r := newRunner(mod, timeout, maxDepth, maxInstr, 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 71776a3ce4..fdc5b839da 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -53,7 +53,7 @@ func runTest(t *testing.T, pathPrefix string) { defer mod.Dispose() // Perform the transform. - err = Run(mod, 10*time.Minute, 10, false) + err = Run(mod, 10*time.Minute, 10, 0, false) if err != nil { if err, match := err.(*Error); match { println(err.Error()) diff --git a/interp/interpreter.go b/interp/interpreter.go index 84bbc953ac..69b3c320bd 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -33,7 +33,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, depth lastBB := -1 // last basic block is undefined, only defined after a branch var operands []value startRTInsts := len(mem.instructions) + instCount := 0 for instIndex := 0; instIndex < len(bb.instructions); instIndex++ { + instCount++ + if r.maxInstr > 0 && instCount > r.maxInstr { + fmt.Printf("interp: excess instructions evaluated in %v\n", fn.name) + return nil, mem, r.errorAt(fn.blocks[0].instructions[0], errDepthExceeded) + } + if instIndex == 0 { if r.maxDepth > 0 && depth >= r.maxDepth { fmt.Printf("interp: depth exceeded in %v\n", fn.name) diff --git a/main.go b/main.go index 6d7da79be7..70a9da0df2 100644 --- a/main.go +++ b/main.go @@ -1419,6 +1419,7 @@ func main() { 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") interpMaxDepth := flag.Int("interp-maxdepth", 0, "interp optimization max depth (default 0=disabled)") + interpMaxInstr := flag.Int("interp-maxinstr", 100_000, "limit interp optimization max instructions (0=disabled)") var tags buildutil.TagsFlag flag.Var(&tags, "tags", "a space-separated list of extra build tags") target := flag.String("target", "", "chip/board name or JSON target specification file") @@ -1530,6 +1531,7 @@ func main() { Work: *work, InterpTimeout: *interpTimeout, InterpMaxDepth: *interpMaxDepth, + InterpMaxInstr: *interpMaxInstr, PrintIR: *printIR, DumpSSA: *dumpSSA, VerifyIR: *verifyIR,