diff --git a/interp/errors.go b/interp/errors.go index 37a8e6d7d7..58486cd82d 100644 --- a/interp/errors.go +++ b/interp/errors.go @@ -20,6 +20,7 @@ var ( errMapAlreadyCreated = errors.New("interp: map already created") errLoopUnrolled = errors.New("interp: loop unrolled") errLoopTooLong = errors.New("interp: loop ran too many iterations") + errTimeout = errors.New("interp: timeout exceeded") ) // This is one of the errors that can be returned from toLLVMValue when the @@ -30,7 +31,8 @@ var errInvalidPtrToIntSize = errors.New("interp: ptrtoint integer size does not func isRecoverableError(err error) bool { return err == errIntegerAsPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated || - err == errLoopUnrolled || err == errLoopTooLong || err == errInvalidPtrToIntSize + err == errLoopUnrolled || err == errLoopTooLong || err == errInvalidPtrToIntSize || + err == errTimeout } // ErrorLine is one line in a traceback. The position may be missing. diff --git a/interp/interp.go b/interp/interp.go index fa590ce7f9..d2cc0fdaf9 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -121,6 +121,11 @@ func Run(mod llvm.Module, timeout time.Duration, maxLoopIterations int, debug bo if r.debug { fmt.Fprintln(os.Stderr, "call:", fn.Name()) } + // Give each package initializer its own timeout budget (errTimeout is + // recoverable): one heavy compile-time-uncomputable init — e.g. crypto + // AES/secp256k1 key expansion — reverts to a runtime call without + // starving the remaining packages of precomputation time. + r.start = time.Now() _, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ") call.EraseFromParentAsInstruction() if callErr != nil { diff --git a/interp/interpreter.go b/interp/interpreter.go index 4c8c36fe75..f0813dfd77 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -167,7 +167,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent 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 %s, timing out (executed calls: %d)", r.timeout, r.callsExecuted)) + // errTimeout is recoverable: rather than abort the whole build, + // the caller reverts this package initializer to a runtime call + // (so heavy compile-time-uncomputable init like crypto AES/secp256k1 + // key expansion just runs at program startup, as on native Go). + if r.debug { + fmt.Fprintf(os.Stderr, "interp: %s: timing out after %s (executed calls: %d); leaving as runtime init\n", r.pkgName, r.timeout, r.callsExecuted) + } + return nil, mem, r.errorAt(fn.blocks[0].instructions[0], errTimeout) } if len(operands) != 0 {