interp: replace some panics with error messages

A number of functions now return errors instead of panicking, which
should help greatly when investigating interp errors. It at least shows
the package responsible for it.
Этот коммит содержится в:
Ayke van Laethem 2020-07-24 00:30:02 +02:00 коммит произвёл Ron Evans
родитель d1ac0138e6
коммит ccb803e35d
3 изменённых файлов: 87 добавлений и 37 удалений

Просмотреть файл

@ -95,7 +95,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) { if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
value = fr.builder.CreateLoad(operand.Value(), inst.Name()) value = fr.builder.CreateLoad(operand.Value(), inst.Name())
} else { } else {
value = operand.Load() var err error
value, err = operand.Load()
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
} }
if value.Type() != inst.Type() { if value.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match")) return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match"))
@ -308,7 +312,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
} }
// "key" is a Go string value, which in the TinyGo calling convention is split up // "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters. // into separate pointer and length parameters.
m.PutString(keyBuf, keyLen, valPtr) err := m.PutString(keyBuf, keyLen, valPtr)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
case callee.Name() == "runtime.hashmapBinarySet": case callee.Name() == "runtime.hashmapBinarySet":
// set a binary (int etc.) key in the map // set a binary (int etc.) key in the map
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue) keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
@ -329,15 +336,24 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.builder.CreateCall(callee, llvmParams, "") fr.builder.CreateCall(callee, llvmParams, "")
continue continue
} }
m.PutBinary(keyBuf, valPtr) err := m.PutBinary(keyBuf, valPtr)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
case callee.Name() == "runtime.stringConcat": case callee.Name() == "runtime.stringConcat":
// adding two strings together // adding two strings together
buf1Ptr := fr.getLocal(inst.Operand(0)) buf1Ptr := fr.getLocal(inst.Operand(0))
buf1Len := fr.getLocal(inst.Operand(1)) buf1Len := fr.getLocal(inst.Operand(1))
buf2Ptr := fr.getLocal(inst.Operand(2)) buf2Ptr := fr.getLocal(inst.Operand(2))
buf2Len := fr.getLocal(inst.Operand(3)) buf2Len := fr.getLocal(inst.Operand(3))
buf1 := getStringBytes(buf1Ptr, buf1Len.Value()) buf1, err := getStringBytes(buf1Ptr, buf1Len.Value())
buf2 := getStringBytes(buf2Ptr, buf2Len.Value()) if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
buf2, err := getStringBytes(buf2Ptr, buf2Len.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
result := []byte(string(buf1) + string(buf2)) result := []byte(string(buf1) + string(buf2))
vals := make([]llvm.Value, len(result)) vals := make([]llvm.Value, len(result))
for i := range vals { for i := range vals {
@ -401,9 +417,12 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?")) return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
} }
for i := int64(0); i < length; i++ { for i := int64(0); i < length; i++ {
var err error
// *dst = *src // *dst = *src
dstArray.Store(srcArray.Load()) val, err := srcArray.Load()
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray.Store(val)
// dst++ // dst++
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1}) dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
if err != nil { if err != nil {
@ -421,7 +440,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// convert a string to a []byte // convert a string to a []byte
bufPtr := fr.getLocal(inst.Operand(0)) bufPtr := fr.getLocal(inst.Operand(0))
bufLen := fr.getLocal(inst.Operand(1)) bufLen := fr.getLocal(inst.Operand(1))
result := getStringBytes(bufPtr, bufLen.Value()) result, err := getStringBytes(bufPtr, bufLen.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
vals := make([]llvm.Value, len(result)) vals := make([]llvm.Value, len(result))
for i := range vals { for i := range vals {
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false) vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false)

Просмотреть файл

@ -1,6 +1,8 @@
package interp package interp
import ( import (
"errors"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@ -18,20 +20,23 @@ func getUses(value llvm.Value) []llvm.Value {
// getStringBytes loads the byte slice of a Go string represented as a // getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair. // {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) []byte { func getStringBytes(strPtr Value, strLen llvm.Value) ([]byte, error) {
if !strLen.IsConstant() { if !strLen.IsConstant() {
panic("getStringBytes with a non-constant length") return nil, errors.New("getStringBytes with a non-constant length")
} }
buf := make([]byte, strLen.ZExtValue()) buf := make([]byte, strLen.ZExtValue())
for i := range buf { for i := range buf {
gep, err := strPtr.GetElementPtr([]uint32{uint32(i)}) gep, err := strPtr.GetElementPtr([]uint32{uint32(i)})
if err != nil { if err != nil {
panic(err) // TODO return nil, err
}
c, err := gep.Load()
if err != nil {
return nil, err
} }
c := gep.Load()
buf[i] = byte(c.ZExtValue()) buf[i] = byte(c.ZExtValue())
} }
return buf return buf, nil
} }
// getLLVMIndices converts an []uint32 into an []llvm.Value, for use in // getLLVMIndices converts an []uint32 into an []llvm.Value, for use in

Просмотреть файл

@ -15,7 +15,7 @@ type Value interface {
Value() llvm.Value // returns a LLVM value Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type() Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value IsConstant() bool // returns true if this value is a constant value
Load() llvm.Value // dereference a pointer Load() (llvm.Value, error) // dereference a pointer
Store(llvm.Value) // store to a pointer Store(llvm.Value) // store to a pointer
GetElementPtr([]uint32) (Value, error) // returns an interior pointer GetElementPtr([]uint32) (Value, error) // returns an interior pointer
String() string // string representation, for debugging String() string // string representation, for debugging
@ -44,23 +44,26 @@ func (v *LocalValue) IsConstant() bool {
} }
// Load loads a constant value if this is a constant pointer. // Load loads a constant value if this is a constant pointer.
func (v *LocalValue) Load() llvm.Value { func (v *LocalValue) Load() (llvm.Value, error) {
if !v.Underlying.IsAGlobalVariable().IsNil() { if !v.Underlying.IsAGlobalVariable().IsNil() {
return v.Underlying.Initializer() return v.Underlying.Initializer(), nil
} }
switch v.Underlying.Opcode() { switch v.Underlying.Opcode() {
case llvm.GetElementPtr: case llvm.GetElementPtr:
indices := v.getConstGEPIndices() indices := v.getConstGEPIndices()
if indices[0] != 0 { if indices[0] != 0 {
panic("invalid GEP") return llvm.Value{}, errors.New("invalid GEP")
} }
global := v.Eval.getValue(v.Underlying.Operand(0)) global := v.Eval.getValue(v.Underlying.Operand(0))
agg := global.Load() agg, err := global.Load()
return llvm.ConstExtractValue(agg, indices[1:]) if err != nil {
return llvm.Value{}, err
}
return llvm.ConstExtractValue(agg, indices[1:]), nil
case llvm.BitCast: case llvm.BitCast:
panic("interp: load from a bitcast") return llvm.Value{}, errors.New("interp: load from a bitcast")
default: default:
panic("interp: load from a constant") return llvm.Value{}, errors.New("interp: load from a constant")
} }
} }
@ -88,7 +91,10 @@ func (v *LocalValue) Store(value llvm.Value) {
panic("invalid GEP") panic("invalid GEP")
} }
global := &LocalValue{v.Eval, v.Underlying.Operand(0)} global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
agg := global.Load() agg, err := global.Load()
if err != nil {
panic(err) // TODO
}
agg = llvm.ConstInsertValue(agg, value, indices[1:]) agg = llvm.ConstInsertValue(agg, value, indices[1:])
global.Store(agg) global.Store(agg)
return return
@ -225,7 +231,11 @@ func (v *MapValue) Value() llvm.Value {
keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0}) keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0})
keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1}) keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1})
keyPtrVal := v.Eval.getValue(keyPtr) keyPtrVal := v.Eval.getValue(keyPtr)
keyBuf = getStringBytes(keyPtrVal, keyLen) var err error
keyBuf, err = getStringBytes(keyPtrVal, keyLen)
if err != nil {
panic(err) // TODO
}
} else if key.Type().TypeKind() == llvm.IntegerTypeKind { } else if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type())) keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue() n := key.Value().ZExtValue()
@ -299,7 +309,7 @@ func (v *MapValue) IsConstant() bool {
} }
// Load panics: maps are of reference type so cannot be dereferenced. // Load panics: maps are of reference type so cannot be dereferenced.
func (v *MapValue) Load() llvm.Value { func (v *MapValue) Load() (llvm.Value, error) {
panic("interp: load from a map") panic("interp: load from a map")
} }
@ -316,23 +326,26 @@ func (v *MapValue) GetElementPtr(indices []uint32) (Value, error) {
// PutString does a map assign operation, assuming that the map is of type // PutString does a map assign operation, assuming that the map is of type
// map[string]T. // map[string]T.
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) { func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) error {
if !v.Underlying.IsNil() { if !v.Underlying.IsNil() {
panic("map already created") return errors.New("map already created")
} }
if valPtr.Underlying.Opcode() == llvm.BitCast { if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)} valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
} }
value := valPtr.Load() value, err := valPtr.Load()
if err != nil {
return err
}
if v.ValueType.IsNil() { if v.ValueType.IsNil() {
v.ValueType = value.Type() v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize { if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size") return errors.New("interp: map store value type has the wrong size")
} }
} else { } else {
if value.Type() != v.ValueType { if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent") return errors.New("interp: map store value type is inconsistent")
} }
} }
@ -345,26 +358,31 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
// TODO: avoid duplicate keys // TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key}) v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value}) v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
} }
// PutBinary does a map assign operation. // PutBinary does a map assign operation.
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) { func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) error {
if !v.Underlying.IsNil() { if !v.Underlying.IsNil() {
panic("map already created") return errors.New("map already created")
} }
if valPtr.Underlying.Opcode() == llvm.BitCast { if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)} valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
} }
value := valPtr.Load() value, err := valPtr.Load()
if err != nil {
return err
}
if v.ValueType.IsNil() { if v.ValueType.IsNil() {
v.ValueType = value.Type() v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize { if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size") return errors.New("interp: map store value type has the wrong size")
} }
} else { } else {
if value.Type() != v.ValueType { if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent") return errors.New("interp: map store value type is inconsistent")
} }
} }
@ -375,21 +393,26 @@ func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)} keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
} }
} }
key := keyPtr.Load() key, err := keyPtr.Load()
if err != nil {
return err
}
if v.KeyType.IsNil() { if v.KeyType.IsNil() {
v.KeyType = key.Type() v.KeyType = key.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize { if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
panic("interp: map store key type has the wrong size") return errors.New("interp: map store key type has the wrong size")
} }
} else { } else {
if key.Type() != v.KeyType { if key.Type() != v.KeyType {
panic("interp: map store key type is inconsistent") return errors.New("interp: map store key type is inconsistent")
} }
} }
// TODO: avoid duplicate keys // TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key}) v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value}) v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
} }
// Get FNV-1a hash of this string. // Get FNV-1a hash of this string.