interp: replace many panics with error messages
This commit replaces most panics in interp/frame.go and interp/scan.go with real error messages. The remaining ones are panics that should not happen when working with valid IR.
Этот коммит содержится в:
родитель
0db26b0662
коммит
24ff2d1ee2
5 изменённых файлов: 40 добавлений и 27 удалений
|
@ -78,6 +78,9 @@ func errorAt(inst llvm.Value, msg string) scanner.Error {
|
||||||
// getPosition returns the position information for the given instruction, as
|
// getPosition returns the position information for the given instruction, as
|
||||||
// far as it is available.
|
// far as it is available.
|
||||||
func getPosition(inst llvm.Value) token.Position {
|
func getPosition(inst llvm.Value) token.Position {
|
||||||
|
if inst.IsAInstruction().IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
loc := inst.InstructionDebugLoc()
|
loc := inst.InstructionDebugLoc()
|
||||||
if loc.IsNil() {
|
if loc.IsNil() {
|
||||||
return token.Position{}
|
return token.Position{}
|
||||||
|
|
|
@ -97,7 +97,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||||
value = operand.Load()
|
value = operand.Load()
|
||||||
}
|
}
|
||||||
if value.Type() != inst.Type() {
|
if value.Type() != inst.Type() {
|
||||||
panic("interp: load: type does not match")
|
return nil, nil, fr.errorAt(inst, "interp: load: type does not match")
|
||||||
}
|
}
|
||||||
fr.locals[inst] = fr.getValue(value)
|
fr.locals[inst] = fr.getValue(value)
|
||||||
case !inst.IsAStoreInst().IsNil():
|
case !inst.IsAStoreInst().IsNil():
|
||||||
|
@ -121,15 +121,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||||
// Not a constant operation.
|
// Not a constant operation.
|
||||||
// This should be detected by the scanner, but isn't at the
|
// This should be detected by the scanner, but isn't at the
|
||||||
// moment.
|
// moment.
|
||||||
panic("todo: non-const gep")
|
return nil, nil, fr.errorAt(inst, "todo: non-const gep")
|
||||||
}
|
}
|
||||||
indices[i] = uint32(operand.Value().ZExtValue())
|
indices[i] = uint32(operand.Value().ZExtValue())
|
||||||
}
|
}
|
||||||
result := value.GetElementPtr(indices)
|
result := value.GetElementPtr(indices)
|
||||||
if result.Type() != inst.Type() {
|
if result.Type() != inst.Type() {
|
||||||
println(" expected:", inst.Type().String())
|
return nil, nil, fr.errorAt(inst, "interp: gep: type does not match")
|
||||||
println(" actual: ", result.Type().String())
|
|
||||||
panic("interp: gep: type does not match")
|
|
||||||
}
|
}
|
||||||
fr.locals[inst] = result
|
fr.locals[inst] = result
|
||||||
|
|
||||||
|
@ -184,7 +182,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// It is not possible in Go to bitcast a map value to a pointer.
|
// It is not possible in Go to bitcast a map value to a pointer.
|
||||||
panic("unimplemented: bitcast of map")
|
return nil, nil, fr.errorAt(inst, "unimplemented: bitcast of map")
|
||||||
}
|
}
|
||||||
value := fr.getLocal(operand).(*LocalValue)
|
value := fr.getLocal(operand).(*LocalValue)
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||||
|
@ -397,16 +395,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||||
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||||
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||||
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
|
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
|
||||||
panic("interp: expected typecode to be a ptrtoint")
|
return nil, nil, fr.errorAt(inst, "interp: expected typecode to be a ptrtoint")
|
||||||
}
|
}
|
||||||
typecode = typecode.Operand(0)
|
typecode = typecode.Operand(0)
|
||||||
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
|
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
|
||||||
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
|
return nil, nil, fr.errorAt(inst, "interp: expected method set in runtime.interfaceImplements to be a constant gep")
|
||||||
}
|
}
|
||||||
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
|
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
|
||||||
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
|
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
|
||||||
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
|
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
|
||||||
panic("interp: expected method set to be a constant gep")
|
return nil, nil, fr.errorAt(inst, "interp: expected method set to be a constant gep")
|
||||||
}
|
}
|
||||||
methodSet = methodSet.Operand(0).Initializer()
|
methodSet = methodSet.Operand(0).Initializer()
|
||||||
|
|
||||||
|
@ -476,7 +474,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||||
params = append(params, local)
|
params = append(params, local)
|
||||||
}
|
}
|
||||||
var ret Value
|
var ret Value
|
||||||
scanResult := fr.Eval.hasSideEffects(callee)
|
scanResult, err := fr.hasSideEffects(callee)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
|
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
|
||||||
// Side effect is bounded. This means the operation invokes
|
// Side effect is bounded. This means the operation invokes
|
||||||
// side effects (like calling an external function) but it
|
// side effects (like calling an external function) but it
|
||||||
|
@ -545,7 +546,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||||
// conditional branch (if/then/else)
|
// conditional branch (if/then/else)
|
||||||
cond := fr.getLocal(inst.Operand(0)).Value()
|
cond := fr.getLocal(inst.Operand(0)).Value()
|
||||||
if cond.Type() != fr.Mod.Context().Int1Type() {
|
if cond.Type() != fr.Mod.Context().Int1Type() {
|
||||||
panic("expected an i1 in a branch instruction")
|
return nil, nil, fr.errorAt(inst, "expected an i1 in a branch instruction")
|
||||||
}
|
}
|
||||||
thenBB := inst.Operand(1)
|
thenBB := inst.Operand(1)
|
||||||
elseBB := inst.Operand(2)
|
elseBB := inst.Operand(2)
|
||||||
|
@ -563,7 +564,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
|
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
|
||||||
return nil, []llvm.Value{elseBB}, nil // else
|
return nil, []llvm.Value{elseBB}, nil // else
|
||||||
default:
|
default:
|
||||||
panic("branch was not true or false")
|
return nil, nil, fr.errorAt(inst, "branch was not true or false")
|
||||||
}
|
}
|
||||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
||||||
// unconditional branch (goto)
|
// unconditional branch (goto)
|
||||||
|
@ -589,6 +590,7 @@ func (fr *frame) getLocal(v llvm.Value) Value {
|
||||||
} else if value := fr.getValue(v); value != nil {
|
} else if value := fr.getValue(v); value != nil {
|
||||||
return value
|
return value
|
||||||
} else {
|
} else {
|
||||||
|
// This should not happen under normal circumstances.
|
||||||
panic("cannot find value")
|
panic("cannot find value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ type Eval struct {
|
||||||
type evalPackage struct {
|
type evalPackage struct {
|
||||||
*Eval
|
*Eval
|
||||||
packagePath string
|
packagePath string
|
||||||
errors []error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run evaluates the function with the given name and then eliminates all
|
// Run evaluates the function with the given name and then eliminates all
|
||||||
|
|
|
@ -38,33 +38,33 @@ type sideEffectResult struct {
|
||||||
// hasSideEffects scans this function and all descendants, recursively. It
|
// hasSideEffects scans this function and all descendants, recursively. It
|
||||||
// returns whether this function has side effects and if it does, which globals
|
// returns whether this function has side effects and if it does, which globals
|
||||||
// it mentions anywhere in this function or any called functions.
|
// it mentions anywhere in this function or any called functions.
|
||||||
func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
|
||||||
switch fn.Name() {
|
switch fn.Name() {
|
||||||
case "runtime.alloc":
|
case "runtime.alloc":
|
||||||
// Cannot be scanned but can be interpreted.
|
// Cannot be scanned but can be interpreted.
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "runtime.nanotime":
|
case "runtime.nanotime":
|
||||||
// Fixed value at compile time.
|
// Fixed value at compile time.
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "runtime._panic":
|
case "runtime._panic":
|
||||||
return &sideEffectResult{severity: sideEffectLimited}
|
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||||
case "runtime.interfaceImplements":
|
case "runtime.interfaceImplements":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "runtime.sliceCopy":
|
case "runtime.sliceCopy":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "runtime.trackPointer":
|
case "runtime.trackPointer":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "llvm.dbg.value":
|
case "llvm.dbg.value":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
}
|
}
|
||||||
if fn.IsDeclaration() {
|
if fn.IsDeclaration() {
|
||||||
return &sideEffectResult{severity: sideEffectLimited}
|
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||||
}
|
}
|
||||||
if e.sideEffectFuncs == nil {
|
if e.sideEffectFuncs == nil {
|
||||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||||
}
|
}
|
||||||
if se, ok := e.sideEffectFuncs[fn]; ok {
|
if se, ok := e.sideEffectFuncs[fn]; ok {
|
||||||
return se
|
return se, nil
|
||||||
}
|
}
|
||||||
result := &sideEffectResult{
|
result := &sideEffectResult{
|
||||||
severity: sideEffectInProgress,
|
severity: sideEffectInProgress,
|
||||||
|
@ -75,6 +75,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||||
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if inst.IsAInstruction().IsNil() {
|
if inst.IsAInstruction().IsNil() {
|
||||||
|
// Should not happen in valid IR.
|
||||||
panic("not an instruction")
|
panic("not an instruction")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||||
switch inst.InstructionOpcode() {
|
switch inst.InstructionOpcode() {
|
||||||
case llvm.IndirectBr, llvm.Invoke:
|
case llvm.IndirectBr, llvm.Invoke:
|
||||||
// Not emitted by the compiler.
|
// Not emitted by the compiler.
|
||||||
panic("unknown instructions")
|
return nil, e.errorAt(inst, "unknown instructions")
|
||||||
case llvm.Call:
|
case llvm.Call:
|
||||||
child := inst.CalledValue()
|
child := inst.CalledValue()
|
||||||
if !child.IsAInlineAsm().IsNil() {
|
if !child.IsAInlineAsm().IsNil() {
|
||||||
|
@ -117,7 +118,10 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
childSideEffects := e.hasSideEffects(child)
|
childSideEffects, err := e.hasSideEffects(child)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
switch childSideEffects.severity {
|
switch childSideEffects.severity {
|
||||||
case sideEffectInProgress, sideEffectNone:
|
case sideEffectInProgress, sideEffectNone:
|
||||||
// no side effects or recursive function - continue scanning
|
// no side effects or recursive function - continue scanning
|
||||||
|
@ -159,7 +163,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||||
// No side effect was reported for this function.
|
// No side effect was reported for this function.
|
||||||
result.severity = sideEffectNone
|
result.severity = sideEffectNone
|
||||||
}
|
}
|
||||||
return result
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
||||||
|
@ -174,6 +178,7 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
|
||||||
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
|
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
|
||||||
user := use.User()
|
user := use.User()
|
||||||
if user.IsAInstruction().IsNil() {
|
if user.IsAInstruction().IsNil() {
|
||||||
|
// Should not happen in valid IR.
|
||||||
panic("user not an instruction")
|
panic("user not an instruction")
|
||||||
}
|
}
|
||||||
switch user.InstructionOpcode() {
|
switch user.InstructionOpcode() {
|
||||||
|
|
|
@ -59,7 +59,11 @@ func TestScan(t *testing.T) {
|
||||||
t.Errorf("scan test: could not find tested function %s in the IR", tc.name)
|
t.Errorf("scan test: could not find tested function %s in the IR", tc.name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result := e.hasSideEffects(fn)
|
evalPkg := &evalPackage{e, "testdata"}
|
||||||
|
result, err := evalPkg.hasSideEffects(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("scan test: failed to scan %s for side effects: %v", fn.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether the result is what we expect.
|
// Check whether the result is what we expect.
|
||||||
if result.severity != tc.severity {
|
if result.severity != tc.severity {
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче