diff --git a/interp/compiler.go b/interp/compiler.go index 8e0cf3549b..babeb50821 100644 --- a/interp/compiler.go +++ b/interp/compiler.go @@ -51,16 +51,16 @@ func (inst *instruction) String() string { for i, op := range inst.operands { operands[i] = op.String() } - return inst.opcodeName() + " " + strings.Join(operands, " ") + return opcodeName(inst.llvmInst) + " " + strings.Join(operands, " ") } -// opcodeName returns the name of the instruction's opcode. -func (inst *instruction) opcodeName() string { +// opcodeName returns the name of the opcode of inst. +func opcodeName(inst llvm.Value) string { name := "" - if inst.branch { + if isBranch(inst) { name = "br" - } else if int(inst.opcode) < len(instructionNameMap) { - name = instructionNameMap[inst.opcode] + } else if opcode := inst.InstructionOpcode(); int(opcode) < len(instructionNameMap) { + name = instructionNameMap[opcode] } if name == "" { name = "" diff --git a/interp/errors.go b/interp/errors.go index 8616299e70..737b8e2f53 100644 --- a/interp/errors.go +++ b/interp/errors.go @@ -12,32 +12,23 @@ import ( ) type recoverableError struct { - msg string - op string // instruction involved, if any - fn string // function being interpreted, if known + reason string } func (e *recoverableError) Error() string { - s := e.msg - if e.op != "" { - s += ": " + e.op - } - if e.fn != "" { - s += " (in " + e.fn + ")" - } - return s + return e.reason } // These errors are expected during normal execution and can be recovered from // by running the affected function at runtime instead of compile time. var ( - errIntegerAsPointer = &recoverableError{msg: "interp: trying to use an integer as a pointer (memory-mapped I/O?)"} - errUnsupportedInst = &recoverableError{msg: "interp: unsupported instruction"} + errIntegerAsPointer = &recoverableError{reason: "interp: trying to use an integer as a pointer (memory-mapped I/O?)"} + errUnsupportedInst = &recoverableError{reason: "interp: unsupported instruction"} // This is one of the errors that can be returned from toLLVMValue when the // passed type does not fit the data to serialize. It is recoverable by // serializing without a type (using rawValue.rawLLVMValue). - errInvalidPtrToIntSize = &recoverableError{msg: "interp: ptrtoint integer size does not equal pointer size"} + errInvalidPtrToIntSize = &recoverableError{reason: "interp: ptrtoint integer size does not equal pointer size"} ) func isRecoverableError(err error) bool { @@ -68,9 +59,14 @@ type Error struct { inst llvm.Value } -// Error returns the string of the first error in the list of errors. +// Error returns the string of the first error in the list of errors. A +// recoverable error also names the function it happened in. func (e *Error) Error() string { - return e.Pos.String() + ": " + e.Err.Error() + s := e.Pos.String() + ": " + e.Err.Error() + if isRecoverableError(e.Err) && !e.inst.IsNil() { + s += " (in " + e.inst.InstructionParent().Parent().Name() + ")" + } + return s } // errorAt returns an error value for the currently interpreted package at the @@ -87,6 +83,12 @@ func (r *runner) errorAt(inst instruction, err error) *Error { } } +// rollbackAt returns a recoverable error at the location of the instruction, +// which makes interp run the function at runtime instead. +func (r *runner) rollbackAt(inst instruction, reason string) *Error { + return r.errorAt(inst, &recoverableError{reason: "interp: " + reason}) +} + // materialize fills in the printed instructions of e and its traceback. It // must be called before the error is returned out of the interp package. func (e *Error) materialize() *Error { diff --git a/interp/interp_test.go b/interp/interp_test.go index b3c77b5e4a..ea1fb8578c 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -279,7 +279,7 @@ exit: if !isRecoverableError(runErr.Err) { t.Errorf("expected a recoverable error, got %v", runErr.Err) } - if want := "interp: loop ran too many iterations (in fill)"; runErr.Err.Error() != want { - t.Errorf("expected %q, got %q", want, runErr.Err.Error()) + if want := ": interp: loop ran too many iterations (in fill)"; !strings.HasSuffix(runErr.Error(), want) { + t.Errorf("expected an error ending in %q, got %q", want, runErr.Error()) } } diff --git a/interp/interpreter.go b/interp/interpreter.go index 2b6b5af98b..b3b946c332 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -57,8 +57,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } blockCounts[currentBB]++ if r.maxLoopIterations > 0 && blockCounts[currentBB] > r.maxLoopIterations { - err := &recoverableError{msg: "interp: loop ran too many iterations", fn: fn.name} - return nil, mem, r.errorAt(bb.instructions[0], err) + return nil, mem, r.rollbackAt(bb.instructions[0], "loop ran too many iterations") } if len(mem.instructions) != startRTInsts { @@ -67,8 +66,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // Avoid doing this, as it can result in a large amount of extra machine code. // This currently uses the branch from the last block, as there is no available information to give a better location. lastBBInsts := fn.blocks[lastBB].instructions - err := &recoverableError{msg: "interp: loop unrolled", fn: fn.name} - return nil, mem, r.errorAt(lastBBInsts[len(lastBBInsts)-1], err) + return nil, mem, r.rollbackAt(lastBBInsts[len(lastBBInsts)-1], "loop unrolled") } // Flag the last block as having run stuff at runtime. @@ -223,8 +221,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent switch inst.opcode { case llvm.Ret: if !fn.deferFrame.IsNil() && len(mem.instructions) != 0 { - err := &recoverableError{msg: "interp: cannot partially evaluate a function with defer", fn: fn.name} - return nil, mem, r.errorAt(inst, err) + return nil, mem, r.rollbackAt(inst, "cannot partially evaluate a function with defer") } if time.Since(r.start) > r.timeout { // Running for more than the allowed timeout; This shouldn't happen, but it does. @@ -371,8 +368,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } case callFn.name == "internal/task.Pause": // Task scheduling isn't possible at compile time. - err := &recoverableError{msg: "interp: goroutine would block", fn: fn.name} - return nil, mem, r.errorAt(inst, err) + return nil, mem, r.rollbackAt(inst, "goroutine would block") case callFn.name == "runtime.fastrand" || callFn.name == "runtime.fastrand64": // Use a local sequence instead of the global RNG state. The // state is replaced by initRand before initAll runs anyway. @@ -1096,7 +1092,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if r.debug { fmt.Fprintln(os.Stderr, indent+inst.String()) } - return nil, mem, r.errorAt(inst, errUnsupportedInst) + return nil, mem, r.rollbackAt(inst, "unsupported instruction: "+opcodeName(inst.llvmInst)) } } return nil, mem, r.errorAt(bb.instructions[len(bb.instructions)-1], errors.New("interp: reached end of basic block without terminator")) @@ -1181,8 +1177,7 @@ func intOperands(r *runner, lhs, rhs value) (int64, int64, error) { func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error { if !fn.deferFrame.IsNil() { - err := &recoverableError{msg: "interp: cannot partially evaluate a function with defer", op: inst.opcodeName(), fn: fn.name} - return r.errorAt(inst, err) + return r.rollbackAt(inst, "cannot partially evaluate a function with defer: "+opcodeName(inst.llvmInst)) } numOperands := inst.llvmInst.OperandsCount() operands := make([]llvm.Value, numOperands) @@ -1292,11 +1287,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me result = r.builder.CreateZExt(operands[0], inst.llvmInst.Type(), inst.name) default: if inst.branch || inst.opcode == llvm.Switch || inst.opcode == llvm.IndirectBr { - err := &recoverableError{msg: "interp: branch on a value not known at compile time", fn: fn.name} - return r.errorAt(inst, err) + return r.rollbackAt(inst, "branch on a value not known at compile time") } - err := &recoverableError{msg: "interp: cannot emit instruction at runtime", op: inst.opcodeName(), fn: fn.name} - return r.errorAt(inst, err) + return r.rollbackAt(inst, "cannot emit instruction at runtime: "+opcodeName(inst.llvmInst)) } locals[inst.localIndex] = localValue{result} mem.instructions = append(mem.instructions, result)