Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions interp/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<unknown op>"
Expand Down
34 changes: 18 additions & 16 deletions interp/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
23 changes: 8 additions & 15 deletions interp/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading