diff --git a/runtime/internal/lib/reflect/value.go b/runtime/internal/lib/reflect/value.go index 9204f093..4e63cdfd 100644 --- a/runtime/internal/lib/reflect/value.go +++ b/runtime/internal/lib/reflect/value.go @@ -2609,7 +2609,7 @@ func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t panic("reflect: " + op + " of method on nil interface value") } rcvrtype = iface.itab.typ - fn = unsafe.Pointer(&iface.itab.fun[i]) + fn = unsafe.Pointer(iface.itab.fun[i]) t = (*funcType)(unsafe.Pointer(m.Typ_)) } else { rcvrtype = v.typ() @@ -3058,6 +3058,5 @@ func MakeMapWithSize(typ Type, n int) Value { return Value{t, m, flag(Map)} } -func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { - panic("todo: reflect.ifaceE2I") -} +//go:linkname ifaceE2I github.com/goplus/llgo/runtime/internal/runtime.IfaceE2I +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) diff --git a/runtime/internal/runtime/errors.go b/runtime/internal/runtime/errors.go index 68790dd5..1516b7ad 100644 --- a/runtime/internal/runtime/errors.go +++ b/runtime/internal/runtime/errors.go @@ -4,6 +4,12 @@ package runtime +import ( + "unsafe" + + "github.com/goplus/llgo/runtime/abi" +) + // A boundsError represents an indexing or slicing operation gone wrong. type boundsError struct { x int64 @@ -114,3 +120,54 @@ func itoa(buf []byte, val uint64) []byte { func PanicSliceConvert(x int, y int) { panic(boundsError{x: int64(x), signed: true, y: y, code: boundsConvert}) } + +// A TypeAssertionError explains a failed type assertion. +type TypeAssertionError struct { + _interface *_type + concrete *_type + asserted *_type + missingMethod string // one method needed by Interface, missing from Concrete +} + +func (*TypeAssertionError) RuntimeError() {} + +func (e *TypeAssertionError) Error() string { + inter := "interface" + if e._interface != nil { + inter = e._interface.String() + } + as := e.asserted.String() + if e.concrete == nil { + return "interface conversion: " + inter + " is nil, not " + as + } + cs := e.concrete.String() + if e.missingMethod == "" { + msg := "interface conversion: " + inter + " is " + cs + ", not " + as + if cs == as { + // provide slightly clearer error message + if pkgpath(e.concrete) != pkgpath(e.asserted) { + msg += " (types from different packages)" + } else { + msg += " (types from different scopes)" + } + } + return msg + } + return "interface conversion: " + cs + " is not " + as + + ": missing method " + e.missingMethod +} + +func pkgpath(t *_type) string { + if u := t.Uncommon(); u != nil { + return u.PkgPath_ + } + switch t.Kind() { + case abi.Struct: + st := (*structtype)(unsafe.Pointer(t)) + return st.PkgPath_ + case abi.Interface: + it := (*interfacetype)(unsafe.Pointer(t)) + return it.PkgPath_ + } + return "" +} diff --git a/runtime/internal/runtime/z_face.go b/runtime/internal/runtime/z_face.go index 6a9f37f2..7f1c61c0 100644 --- a/runtime/internal/runtime/z_face.go +++ b/runtime/internal/runtime/z_face.go @@ -21,6 +21,7 @@ import ( "github.com/goplus/llgo/runtime/abi" c "github.com/goplus/llgo/runtime/internal/clite" + "github.com/goplus/llgo/runtime/internal/clite/pthread/sync" ) type eface struct { @@ -306,11 +307,50 @@ func Interface(pkgPath string, methods []Imethod) *InterfaceType { return ret } +var itabTable struct { + mutex + entries []*Itab +} + +type mutex sync.Mutex + +func (m *mutex) Lock() { + if *(*c.Long)(unsafe.Pointer(m)) == 0 { + (*sync.Mutex)(m).Init(nil) + } + (*sync.Mutex)(m).Lock() +} + +func (m *mutex) Unlock() { + (*sync.Mutex)(m).Unlock() +} + +func findItab(inter *InterfaceType, typ *Type) *Itab { + itabTable.Lock() + for _, i := range itabTable.entries { + if i.inter == inter && i._type == typ { + itabTable.Unlock() + return i + } + } + itabTable.Unlock() + return nil +} + +func addItab(i *Itab) { + itabTable.Lock() + itabTable.entries = append(itabTable.entries, i) + itabTable.Unlock() +} + // NewItab returns a new itab. func NewItab(inter *InterfaceType, typ *Type) *Itab { if typ == nil { return nil } + if i := findItab(inter, typ); i != nil { + return i + } n := len(inter.Methods) size := itabHdrSize + uintptr(n)*pointerSize ptr := AllocU(size) @@ -335,6 +375,9 @@ func NewItab(inter *InterfaceType, typ *Type) *Itab { *c.Advance(data, i) = uintptr(fn) } } + if ret.fun[0] != 0 { + addItab(ret) + } return ret } @@ -632,4 +675,41 @@ func shallowHashTuple(tuple []*Type) uint32 { return hash } +func assertE2I(inter *interfacetype, t *_type) *itab { + if t == nil { + // explicit conversions require non-nil interface value. + panic(&TypeAssertionError{nil, nil, &inter.Type, ""}) + } + return getitab(inter, t, false) +} + +func IfaceE2I(inter *interfacetype, e eface, dst *iface) { + *dst = iface{assertE2I(inter, e._type), e.data} +} + +func getitab(inter *interfacetype, typ *_type, canfail bool) *itab { + if len(inter.Methods) == 0 { + panic("internal error - misuse of itab") + } + + // easy case + if typ.TFlag&abi.TFlagUncommon == 0 { + if canfail { + return nil + } + name := inter.Methods[0].Name() + panic(&TypeAssertionError{nil, typ, &inter.Type, name}) + } + + m := NewItab(inter, typ) + + if m.fun[0] != 0 { + return m + } + if canfail { + return nil + } + panic(&TypeAssertionError{concrete: typ, asserted: &inter.Type, missingMethod: ""}) +} + // -----------------------------------------------------------------------------