diff --git a/compiler/interface.go b/compiler/interface.go index 5f7e7e345b..ed8d618cf2 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -195,9 +195,13 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { for i := 0; i < ms.Len(); i++ { methods = append(methods, ms.At(i).Obj().(*types.Func)) } + methodEntryType := types.NewStruct([]*types.Var{ + types.NewVar(token.NoPos, nil, "signature", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "name", types.Typ[types.UnsafePointer]), + }, nil) methodSetType := types.NewStruct([]*types.Var{ types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), - types.NewVar(token.NoPos, nil, "methods", types.NewArray(types.Typ[types.UnsafePointer], int64(len(methods)))), + types.NewVar(token.NoPos, nil, "methods", types.NewArray(methodEntryType, int64(len(methods)))), }, nil) methodSetValue := c.getMethodSetValue(methods) switch typ := typ.(type) { @@ -844,12 +848,15 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string { } // getMethodSetValue creates the method set struct value for a list of methods. -// The struct contains a length and a sorted array of method signature pointers. +// The struct contains a length and a sorted array of {signature, name} entries. +// Each entry pairs a method signature pointer (for Implements comparison) with +// a pointer to the method's null-terminated name string. func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value { // Create a sorted list of method signature global names. type methodRef struct { - name string - value llvm.Value + sigGlobalName string + methodName string + sigValue llvm.Value } var refs []methodRef for _, method := range methods { @@ -880,23 +887,44 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value { value.AddMetadata(0, diglobal) } } - refs = append(refs, methodRef{globalName, value}) + refs = append(refs, methodRef{globalName, name, value}) } sort.Slice(refs, func(i, j int) bool { - return refs[i].name < refs[j].name + return refs[i].sigGlobalName < refs[j].sigGlobalName }) - var values []llvm.Value + pairType := c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false) + var pairs []llvm.Value for _, ref := range refs { - values = append(values, ref.value) + nameGlobal := c.getMethodNameGlobal(ref.methodName) + pair := c.ctx.ConstStruct([]llvm.Value{ref.sigValue, nameGlobal}, false) + pairs = append(pairs, pair) } return c.ctx.ConstStruct([]llvm.Value{ - llvm.ConstInt(c.uintptrType, uint64(len(values)), false), - llvm.ConstArray(c.dataPtrType, values), + llvm.ConstInt(c.uintptrType, uint64(len(pairs)), false), + llvm.ConstArray(pairType, pairs), }, false) } +// getMethodNameGlobal returns a global containing the null-terminated method +// name string, creating it if needed. +func (c *compilerContext) getMethodNameGlobal(name string) llvm.Value { + globalName := "reflect/types.methodname:" + name + g := c.mod.NamedGlobal(globalName) + if !g.IsNil() { + return g + } + nameBytes := c.ctx.ConstString(name+"\x00", false) + g = llvm.AddGlobal(c.mod, nameBytes.Type(), globalName) + g.SetInitializer(nameBytes) + g.SetGlobalConstant(true) + g.SetLinkage(llvm.LinkOnceODRLinkage) + g.SetAlignment(1) + g.SetUnnamedAddr(true) + return g +} + // getInvokeFunction returns the thunk to call the given interface method. The // thunk is declared, not defined: it will be defined by the interface lowering // pass. diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index 4bec11c419..89e7d16d88 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -10,13 +10,15 @@ target triple = "wasm32-unknown-wasi" @"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4 @"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4 @"reflect/types.signature:Error:func:{}{basic:string}" = linkonce_odr constant i8 0, align 1 -@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [7 x i8] } { i8 116, i16 -32767, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Error:func:{}{basic:string}"] }, [7 x i8] c".error\00" }, align 4 +@"reflect/types.methodname:Error" = linkonce_odr unnamed_addr constant [6 x i8] c"Error\00", align 1 +@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, { i32, [1 x { ptr, ptr }] }, [7 x i8] } { i8 116, i16 -32767, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", { i32, [1 x { ptr, ptr }] } { i32 1, [1 x { ptr, ptr }] [{ ptr, ptr } { ptr @"reflect/types.signature:Error:func:{}{basic:string}", ptr @"reflect/types.methodname:Error" }] }, [7 x i8] c".error\00" }, align 4 @"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1 -@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr, { i32, [1 x ptr] } } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Error:func:{}{basic:string}"] } }, align 4 +@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr, { i32, [1 x { ptr, ptr }] } } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", { i32, [1 x { ptr, ptr }] } { i32 1, [1 x { ptr, ptr }] [{ ptr, ptr } { ptr @"reflect/types.signature:Error:func:{}{basic:string}", ptr @"reflect/types.methodname:Error" }] } }, align 4 @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4 @"reflect/types.signature:String:func:{}{basic:string}" = linkonce_odr constant i8 0, align 1 -@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr, { i32, [1 x ptr] } } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:String:func:{}{basic:string}"] } }, align 4 +@"reflect/types.methodname:String" = linkonce_odr unnamed_addr constant [7 x i8] c"String\00", align 1 +@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr, { i32, [1 x { ptr, ptr }] } } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", { i32, [1 x { ptr, ptr }] } { i32 1, [1 x { ptr, ptr }] [{ ptr, ptr } { ptr @"reflect/types.signature:String:func:{}{basic:string}", ptr @"reflect/types.methodname:String" }] } }, align 4 @"reflect/types.typeid:basic:int" = external constant i8 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index e282f16f3b..21fafafd74 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -252,10 +252,18 @@ type structField struct { data unsafe.Pointer // various bits of information, packed in a byte array } +// Method set entry, as emitted by the compiler. Each entry pairs a signature +// identity pointer (for Implements/AssignableTo comparison) with a pointer to +// the method's null-terminated name string. +type methodEntry struct { + signature unsafe.Pointer + name *byte +} + // Method set, as emitted by the compiler. type methodSet struct { length uintptr - methods [0]unsafe.Pointer // variable number of method signature pointers + methods [0]methodEntry } // Equivalent to (go/types.Type).Underlying(): if this is a named type return @@ -805,8 +813,8 @@ func (t *RawType) Implements(u Type) bool { // typeImplementsMethodSet checks whether the concrete type (identified by its // typecode pointer) implements the given method set. Both the concrete type's -// method set and the asserted method set are sorted arrays of method signature -// pointers, so comparison is O(n+m). +// method set and the asserted method set are sorted arrays of {signature, name} +// entries, so comparison is O(n+m). Only the signature field is compared. // //go:linkname typeImplementsMethodSet runtime.typeImplementsMethodSet func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) bool { @@ -814,7 +822,7 @@ func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) boo return false } - const ptrSize = unsafe.Sizeof((*byte)(nil)) + const entrySize = unsafe.Sizeof(methodEntry{}) itfNumMethod := *(*uintptr)(assertedMethodSet) if itfNumMethod == 0 { return true @@ -853,13 +861,14 @@ func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) boo } concreteTypePtr := unsafe.Pointer(&methods.methods) - concreteTypeEnd := unsafe.Add(concreteTypePtr, uintptr(methods.length)*ptrSize) + concreteTypeEnd := unsafe.Add(concreteTypePtr, uintptr(methods.length)*entrySize) // Iterate over each method in the interface method set, and check whether // the method exists in the method set of the concrete type. // Both method sets are sorted, so we can use a linear scan. - assertedTypePtr := unsafe.Add(assertedMethodSet, ptrSize) - assertedTypeEnd := unsafe.Add(assertedTypePtr, itfNumMethod*ptrSize) + // Each entry is a {signature, name} pair; we compare only the signature. + assertedTypePtr := unsafe.Add(assertedMethodSet, unsafe.Sizeof(uintptr(0))) + assertedTypeEnd := unsafe.Add(assertedTypePtr, itfNumMethod*entrySize) for assertedTypePtr != assertedTypeEnd { assertedMethod := *(*unsafe.Pointer)(assertedTypePtr) @@ -868,13 +877,13 @@ func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) boo return false } concreteMethod := *(*unsafe.Pointer)(concreteTypePtr) - concreteTypePtr = unsafe.Add(concreteTypePtr, ptrSize) + concreteTypePtr = unsafe.Add(concreteTypePtr, entrySize) if concreteMethod == assertedMethod { break } } - assertedTypePtr = unsafe.Add(assertedTypePtr, ptrSize) + assertedTypePtr = unsafe.Add(assertedTypePtr, entrySize) } return true @@ -913,14 +922,155 @@ func (t *RawType) NumMethod() int { case Struct: return int((*structType)(unsafe.Pointer(t)).numMethod & ^uint16(numMethodHasMethodSet)) case Interface: - //FIXME: Use len(methods) - return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() + ct := (*interfaceType)(unsafe.Pointer(t.underlying())) + return int(ct.methods.length) } // Other types have no methods attached. Note we don't panic here. return 0 } +// getMethodSet returns the method set for a type, or nil if the type has no +// inline method set. +func (t *RawType) getMethodSet() *methodSet { + if t.isNamed() { + ct := (*namedType)(unsafe.Pointer(t)) + if ct.numMethod&numMethodHasMethodSet == 0 { + return nil + } + return (*methodSet)(unsafe.Add(unsafe.Pointer(ct), unsafe.Sizeof(*ct))) + } + switch t.Kind() { + case Interface: + ct := (*interfaceType)(unsafe.Pointer(t.underlying())) + return &ct.methods + case Pointer: + ct := (*ptrType)(unsafe.Pointer(t)) + if ct.numMethod&numMethodHasMethodSet == 0 { + return nil + } + return &ct.methods + case Struct: + ct := (*structType)(unsafe.Pointer(t)) + if ct.numMethod&numMethodHasMethodSet == 0 { + return nil + } + fieldSize := unsafe.Sizeof(structField{}) + methodsPtr := unsafe.Add(unsafe.Pointer(&ct.fields[0]), uintptr(ct.numField)*fieldSize) + return (*methodSet)(methodsPtr) + } + return nil +} + +// methodSetEntry returns the i-th entry in the method set. +func methodSetEntry(ms *methodSet, i int) *methodEntry { + return (*methodEntry)(unsafe.Add(unsafe.Pointer(&ms.methods), uintptr(i)*unsafe.Sizeof(methodEntry{}))) +} + +// isExportedMethod reports whether the method entry has an exported name. +// Unexported method names are stored as "pkg/path.name" by the compiler, +// so they always contain a dot. For interface types, all methods are +// considered exported. +func isExportedMethod(entry *methodEntry) bool { + if entry.name == nil { + return false // name was stripped by DCE + } + // Check first byte: exported Go identifiers start with A-Z. + return *entry.name >= 'A' && *entry.name <= 'Z' +} + +// methodName returns the name and pkgPath of a method entry. +// For exported methods, name is the method name and pkgPath is empty. +// For unexported methods, name is just the method name and pkgPath is +// the package path (stored as "pkg/path.name" by the compiler). +func methodName(entry *methodEntry) (name, pkgPath string) { + if entry.name == nil { + return "", "" + } + full := readStringZ(unsafe.Pointer(entry.name)) + // Unexported methods are stored as "pkg/path.name". + for i := len(full) - 1; i >= 0; i-- { + if full[i] == '.' { + return full[i+1:], full[:i] + } + } + return full, "" +} + +// Method returns the i-th method in the type's method set. +// For non-interface types, this indexes only exported methods. +// For interface types, all methods are included. +// +//go:linkname reflectTypeMethodByIndex reflect.(*rawType).Method +func (t *RawType) Method(i int) MethodInfo { + n := t.NumMethod() + if i < 0 || i >= n { + panic("reflect: Method index out of range") + } + ms := t.getMethodSet() + if ms == nil { + // Method set was pruned or stripped; name unavailable. + return MethodInfo{Index: i} + } + isIface := t.Kind() == Interface + exportedIdx := 0 + for j := 0; j < int(ms.length); j++ { + entry := methodSetEntry(ms, j) + if !isIface && !isExportedMethod(entry) { + continue + } + if exportedIdx == i { + name, pkgPath := methodName(entry) + return MethodInfo{ + Name: name, + PkgPath: pkgPath, + Index: i, + } + } + exportedIdx++ + } + // Method set was pruned; name unavailable. + return MethodInfo{Index: i} +} + +// MethodByName returns the method with the given name in the type's method +// set, and a boolean indicating if the method was found. +// For non-interface types, only exported methods are searched. +// +//go:linkname reflectTypeMethodByName reflect.(*rawType).MethodByName +func (t *RawType) MethodByName(name string) (MethodInfo, bool) { + ms := t.getMethodSet() + if ms == nil { + return MethodInfo{}, false + } + isIface := t.Kind() == Interface + exportedIdx := 0 + for j := 0; j < int(ms.length); j++ { + entry := methodSetEntry(ms, j) + if !isIface && !isExportedMethod(entry) { + continue + } + ename, pkgPath := methodName(entry) + if ename == name { + return MethodInfo{ + Name: name, + PkgPath: pkgPath, + Index: exportedIdx, + }, true + } + exportedIdx++ + } + return MethodInfo{}, false +} + +// MethodInfo describes a single method. This is the internal reflectlite +// representation; the reflect package wraps this in reflect.Method. +type MethodInfo struct { + Name string + PkgPath string + Index int +} + // Read and return a null terminated string starting from data. func readStringZ(data unsafe.Pointer) string { start := data @@ -942,8 +1092,8 @@ func (t *RawType) name() string { ptr := unsafe.Add(unsafe.Pointer(ntype), unsafe.Sizeof(*ntype)) if ntype.numMethod&numMethodHasMethodSet != 0 { ms := (*methodSet)(ptr) - // Skip past the length field and the method pointer entries. - ptr = unsafe.Add(ptr, unsafe.Sizeof(uintptr(0))+uintptr(ms.length)*unsafe.Sizeof(unsafe.Pointer(nil))) + // Skip past the length field and the method entries. + ptr = unsafe.Add(ptr, unsafe.Sizeof(uintptr(0))+uintptr(ms.length)*unsafe.Sizeof(methodEntry{})) } return readStringZ(ptr) } diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index 3c2af94f72..88fda8937d 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -16,6 +16,7 @@ const ( valueFlagExported valueFlagEmbedRO valueFlagStickyRO + valueFlagMethod // set when the Value represents a bound method valueFlagRO = valueFlagEmbedRO | valueFlagStickyRO ) @@ -219,6 +220,9 @@ func (v Value) RawType() *RawType { } func (v Value) Kind() Kind { + if v.flags&valueFlagMethod != 0 { + return Func + } return v.typecode.Kind() } @@ -229,6 +233,9 @@ func (v Value) IsNil() bool { case Chan, Map, Ptr, UnsafePointer: return v.pointer() == nil case Func: + if v.flags&valueFlagMethod != 0 { + return false // bound methods are never nil + } if v.value == nil { return true } @@ -264,6 +271,10 @@ func (v Value) UnsafePointer() unsafe.Pointer { slice := (*sliceHeader)(v.value) return slice.data case Func: + if v.flags&valueFlagMethod != 0 { + // Bound method — no meaningful function pointer. + return v.value + } fn := (*funcHeader)(v.value) if fn.Context != nil { return fn.Context @@ -2165,11 +2176,35 @@ func (v Value) CallSlice(in []Value) []Value { } func (v Value) Method(i int) Value { - panic("unimplemented: (reflect.Value).Method()") + if v.Kind() == Invalid { + panic(&ValueError{Method: "reflect.Value.Method", Kind: Invalid}) + } + n := v.typecode.NumMethod() + if i < 0 || i >= n { + panic("reflect: Method index out of range") + } + // Return a valid Value representing the bound method. Without Call() + // support, this value cannot be invoked but satisfies IsValid() and + // Kind() == Func checks. + return Value{ + typecode: v.typecode, + value: v.value, + flags: (v.flags & valueFlagExported) | valueFlagMethod, + } } func (v Value) MethodByName(name string) Value { - panic("unimplemented: (reflect.Value).MethodByName()") + if v.Kind() == Invalid { + panic(&ValueError{Method: "reflect.Value.MethodByName", Kind: Invalid}) + } + if _, ok := v.typecode.MethodByName(name); !ok { + return Value{} + } + return Value{ + typecode: v.typecode, + value: v.value, + flags: (v.flags & valueFlagExported) | valueFlagMethod, + } } func (v Value) Recv() (x Value, ok bool) { diff --git a/src/reflect/type.go b/src/reflect/type.go index 884f89dc84..be2e62e2a9 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -446,11 +446,24 @@ func (t *rawType) Key() Type { } func (t *rawType) Method(i int) Method { - panic("unimplemented: (reflect.Type).Method()") + m := t.RawType.Method(i) + return Method{ + Name: m.Name, + PkgPath: m.PkgPath, + Index: m.Index, + } } func (t *rawType) MethodByName(name string) (Method, bool) { - panic("unimplemented: (reflect.Type).MethodByName()") + m, ok := t.RawType.MethodByName(name) + if !ok { + return Method{}, false + } + return Method{ + Name: m.Name, + PkgPath: m.PkgPath, + Index: m.Index, + }, true } func (t *rawType) NumIn() int { diff --git a/src/reflect/value.go b/src/reflect/value.go index cf6952a770..a078229213 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -214,11 +214,11 @@ func (v Value) Equal(u Value) bool { } func (v Value) Method(i int) Value { - panic("unimplemented: (reflect.Value).Method()") + return Value{v.Value.Method(i)} } func (v Value) MethodByName(name string) Value { - panic("unimplemented: (reflect.Value).MethodByName()") + return Value{v.Value.MethodByName(name)} } func (v Value) Recv() (x Value, ok bool) { diff --git a/testdata/reflect.go b/testdata/reflect.go index 873d60f787..9b827fe2a4 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -360,6 +360,9 @@ func main() { println("\nv.Interface() method") testInterfaceMethod() + println("\ntype method sets") + testMethodSets() + // Test reflect.DeepEqual. var selfref1, selfref2 selfref selfref1.x = &selfref1 @@ -604,7 +607,7 @@ func (*myWriter) Write(p []byte) (int, error) { return 0, nil } type myReadWriter struct{} func (myReadWriter) Read(p []byte) (int, error) { return 0, nil } -func (*myReadWriter) Write(p []byte) (int, error) { return 0, nil } +func (*myReadWriter) Write(p []byte) (int, error) { return 0, nil } type myStringer struct{} @@ -645,24 +648,24 @@ func testImplements() { println("concrete implements:") // myReader has value receiver Read → implements Reader - println("myReader → Reader:", reflect.TypeOf(myReader{}).Implements(readerType)) // true + println("myReader → Reader:", reflect.TypeOf(myReader{}).Implements(readerType)) // true println("*myReader → Reader:", reflect.TypeOf(new(myReader)).Elem().Implements(readerType)) // true (value method in pointer set) // myWriter has pointer receiver Write → only *myWriter implements Writer - println("myWriter → Writer:", reflect.TypeOf(myWriter{}).Implements(writerType)) // false (pointer receiver) - println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).Implements(writerType)) // true + println("myWriter → Writer:", reflect.TypeOf(myWriter{}).Implements(writerType)) // false (pointer receiver) + println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).Implements(writerType)) // true // myReadWriter: Read on value, Write on pointer - println("myReadWriter → Reader:", reflect.TypeOf(myReadWriter{}).Implements(readerType)) // true - println("myReadWriter → Writer:", reflect.TypeOf(myReadWriter{}).Implements(writerType)) // false (Write is ptr recv) - println("myReadWriter → ReadWriter:", reflect.TypeOf(myReadWriter{}).Implements(readWriterType)) // false - println("*myReadWriter → Reader:", reflect.TypeOf(&myReadWriter{}).Implements(readerType)) // true - println("*myReadWriter → Writer:", reflect.TypeOf(&myReadWriter{}).Implements(writerType)) // true + println("myReadWriter → Reader:", reflect.TypeOf(myReadWriter{}).Implements(readerType)) // true + println("myReadWriter → Writer:", reflect.TypeOf(myReadWriter{}).Implements(writerType)) // false (Write is ptr recv) + println("myReadWriter → ReadWriter:", reflect.TypeOf(myReadWriter{}).Implements(readWriterType)) // false + println("*myReadWriter → Reader:", reflect.TypeOf(&myReadWriter{}).Implements(readerType)) // true + println("*myReadWriter → Writer:", reflect.TypeOf(&myReadWriter{}).Implements(writerType)) // true println("*myReadWriter → ReadWriter:", reflect.TypeOf(&myReadWriter{}).Implements(readWriterType)) // true // Nothing implements Closer (none of our types have Close) - println("myReader → Closer:", reflect.TypeOf(myReader{}).Implements(closerType)) // false - println("*myReadWriter → Closer:", reflect.TypeOf(&myReadWriter{}).Implements(closerType)) // false + println("myReader → Closer:", reflect.TypeOf(myReader{}).Implements(closerType)) // false + println("*myReadWriter → Closer:", reflect.TypeOf(&myReadWriter{}).Implements(closerType)) // false // errorValue (*errors.errorString) implements error but not Stringer println("errorValue → error:", reflect.TypeOf(errorValue).Implements(errorType)) // true @@ -673,23 +676,23 @@ func testImplements() { println("myErrorStringer → Stringer:", reflect.TypeOf(myErrorStringer{}).Implements(stringerType)) // true // Everything implements empty interface - println("myReader → interface{}:", reflect.TypeOf(myReader{}).Implements(emptyItf)) // true - println("int → interface{}:", reflect.TypeOf(0).Implements(emptyItf)) // true + println("myReader → interface{}:", reflect.TypeOf(myReader{}).Implements(emptyItf)) // true + println("int → interface{}:", reflect.TypeOf(0).Implements(emptyItf)) // true // --- Interface implements interface (superset check, issue #3580) --- println("interface implements interface:") // ReadWriter is a superset of Reader and Writer - println("ReadWriter → Reader:", readWriterType.Implements(readerType)) // true - println("ReadWriter → Writer:", readWriterType.Implements(writerType)) // true - println("Reader → ReadWriter:", readerType.Implements(readWriterType)) // false - println("Writer → ReadWriter:", writerType.Implements(readWriterType)) // false + println("ReadWriter → Reader:", readWriterType.Implements(readerType)) // true + println("ReadWriter → Writer:", readWriterType.Implements(writerType)) // true + println("Reader → ReadWriter:", readerType.Implements(readWriterType)) // false + println("Writer → ReadWriter:", writerType.Implements(readWriterType)) // false // ReadCloser has Read+Close, Reader has Read - println("ReadCloser → Reader:", readCloserType.Implements(readerType)) // true - println("ReadCloser → Closer:", readCloserType.Implements(closerType)) // true - println("ReadCloser → Writer:", readCloserType.Implements(writerType)) // false - println("Reader → ReadCloser:", readerType.Implements(readCloserType)) // false + println("ReadCloser → Reader:", readCloserType.Implements(readerType)) // true + println("ReadCloser → Closer:", readCloserType.Implements(closerType)) // true + println("ReadCloser → Writer:", readCloserType.Implements(writerType)) // false + println("Reader → ReadCloser:", readerType.Implements(readCloserType)) // false // Self-implements println("Reader → Reader:", readerType.Implements(readerType)) // true @@ -700,51 +703,51 @@ func testImplements() { println("Stringer → error:", stringerType.Implements(errorType)) // false // Everything implements empty interface - println("Reader → interface{}:", readerType.Implements(emptyItf)) // true + println("Reader → interface{}:", readerType.Implements(emptyItf)) // true println("ReadWriter → interface{}:", readWriterType.Implements(emptyItf)) // true // --- AssignableTo --- println("assignable to:") // Identical types - println("int → int:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(0))) // true - println("string → string:", reflect.TypeOf("").AssignableTo(reflect.TypeOf(""))) // true + println("int → int:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(0))) // true + println("string → string:", reflect.TypeOf("").AssignableTo(reflect.TypeOf(""))) // true // Different types println("int → string:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(""))) // false println("int → int64:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(int64(0)))) // false // Concrete assignable to interface (implements check) - println("myReader → Reader:", reflect.TypeOf(myReader{}).AssignableTo(readerType)) // true - println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).AssignableTo(writerType)) // true - println("myWriter → Writer:", reflect.TypeOf(myWriter{}).AssignableTo(writerType)) // false + println("myReader → Reader:", reflect.TypeOf(myReader{}).AssignableTo(readerType)) // true + println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).AssignableTo(writerType)) // true + println("myWriter → Writer:", reflect.TypeOf(myWriter{}).AssignableTo(writerType)) // false println("*myReadWriter → ReadWriter:", reflect.TypeOf(&myReadWriter{}).AssignableTo(readWriterType)) // true // Interface assignable to interface - println("ReadWriter → Reader:", readWriterType.AssignableTo(readerType)) // true - println("Reader → ReadWriter:", readerType.AssignableTo(readWriterType)) // false + println("ReadWriter → Reader:", readWriterType.AssignableTo(readerType)) // true + println("Reader → ReadWriter:", readerType.AssignableTo(readWriterType)) // false // Everything assignable to empty interface - println("int → interface{}:", reflect.TypeOf(0).AssignableTo(emptyItf)) // true - println("Reader → interface{}:", readerType.AssignableTo(emptyItf)) // true + println("int → interface{}:", reflect.TypeOf(0).AssignableTo(emptyItf)) // true + println("Reader → interface{}:", readerType.AssignableTo(emptyItf)) // true // --- Upstream set_test.go: unexported method interfaces --- println("unexported method interface:") exprType := reflect.TypeOf((*exprLike)(nil)).Elem() - println("*notAnExpr → exprLike:", reflect.TypeOf(new(notAnExpr)).Implements(exprType)) // true - println("notAnExpr → exprLike:", reflect.TypeOf(notAnExpr{}).Implements(exprType)) // true + println("*notAnExpr → exprLike:", reflect.TypeOf(new(notAnExpr)).Implements(exprType)) // true + println("notAnExpr → exprLike:", reflect.TypeOf(notAnExpr{}).Implements(exprType)) // true println("*notAnExpr → exprLike (AssignableTo):", reflect.TypeOf(new(notAnExpr)).AssignableTo(exprType)) // true // --- Upstream set_test.go: channel direction assignability --- println("channel direction:") - println("chan int → <-chan int:", reflect.TypeOf(make(chan int)).AssignableTo(reflect.TypeOf(make(<-chan int)))) // true - println("<-chan int → chan int:", reflect.TypeOf(make(<-chan int)).AssignableTo(reflect.TypeOf(make(chan int)))) // false + println("chan int → <-chan int:", reflect.TypeOf(make(chan int)).AssignableTo(reflect.TypeOf(make(<-chan int)))) // true + println("<-chan int → chan int:", reflect.TypeOf(make(<-chan int)).AssignableTo(reflect.TypeOf(make(chan int)))) // false // --- Upstream set_test.go: named type assignability --- println("named types:") - println("*int → IntPtr:", reflect.TypeOf(new(int)).AssignableTo(reflect.TypeOf(IntPtr(nil)))) // true - println("IntPtr → *int:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(new(int)))) // true - println("IntPtr → IntPtr1:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(IntPtr1(nil)))) // false + println("*int → IntPtr:", reflect.TypeOf(new(int)).AssignableTo(reflect.TypeOf(IntPtr(nil)))) // true + println("IntPtr → *int:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(new(int)))) // true + println("IntPtr → IntPtr1:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(IntPtr1(nil)))) // false println("Ch → <-chan interface{}:", reflect.TypeOf(Ch(nil)).AssignableTo(reflect.TypeOf(make(<-chan interface{})))) // true // --- reflect.Value.Set with interface (issue #4277) --- @@ -807,3 +810,151 @@ func randuint32() uint32 { xorshift32State = xorshift32(xorshift32State) return xorshift32State } + +type HasMethods struct { + Name string +} + +func (h HasMethods) Len() int { + return len(h.Name) +} + +func (h HasMethods) String() string { + return h.Name +} + +// unexported method — should NOT appear in method set +func (h HasMethods) hidden() {} + +// PtrMethod is only on the pointer receiver. +func (h *HasMethods) PtrMethod() {} + +// Embedded type tests. +type Inner struct{ X int } + +func (i Inner) InnerMethod() int { return i.X } + +type Outer struct { + Y int + Inner +} + +func (o Outer) OuterMethod() int { return o.Y } + +// Interface type method set test. +type Iface interface { + Alpha() + Beta() +} + +func testMethodSets() { + // --- Struct value type: only value-receiver methods --- + t := reflect.TypeOf(HasMethods{}) + println("struct NumMethod:", t.NumMethod()) + for i := 0; i < t.NumMethod(); i++ { + m := t.Method(i) + println("struct Method:", m.Name) + } + + // --- Pointer type: includes both value and pointer receiver methods --- + pt := reflect.TypeOf(new(HasMethods)) + println("pointer NumMethod:", pt.NumMethod()) + for i := 0; i < pt.NumMethod(); i++ { + m := pt.Method(i) + println("pointer Method:", m.Name) + } + + // --- MethodByName: found and not found --- + m, ok := t.MethodByName("String") + println("MethodByName(String):", m.Name, ok) + + m, ok = t.MethodByName("Len") + println("MethodByName(Len):", m.Name, ok) + + _, ok = t.MethodByName("Nonexistent") + println("MethodByName(Nonexistent):", ok) + + // MethodByName for pointer-only method on pointer type + m, ok = pt.MethodByName("PtrMethod") + println("pointer MethodByName(PtrMethod):", m.Name, ok) + + // MethodByName for pointer-only method on value type → not found + _, ok = t.MethodByName("PtrMethod") + println("struct MethodByName(PtrMethod):", ok) + + // --- Embedded types --- + ot := reflect.TypeOf(Outer{}) + println("embedded NumMethod:", ot.NumMethod()) + for i := 0; i < ot.NumMethod(); i++ { + m := ot.Method(i) + println("embedded Method:", m.Name) + } + + // --- Interface type --- + ifaceT := reflect.TypeOf((*Iface)(nil)).Elem() + println("interface NumMethod:", ifaceT.NumMethod()) + for i := 0; i < ifaceT.NumMethod(); i++ { + m := ifaceT.Method(i) + println("interface Method:", m.Name) + } + + // --- Value.Method / Value.MethodByName --- + v := reflect.ValueOf(HasMethods{Name: "hello"}) + mv := v.MethodByName("String") + println("Value.MethodByName(String).IsValid():", mv.IsValid()) + println("Value.MethodByName(String).Kind():", mv.Kind().String()) + + mv = v.MethodByName("Nonexistent") + println("Value.MethodByName(Nonexistent).IsValid():", mv.IsValid()) + + // Value.Method by index + mv = v.Method(0) + println("Value.Method(0).IsValid():", mv.IsValid()) + println("Value.Method(0).Kind():", mv.Kind().String()) + + // Bound method values are never nil. + println("Value.Method(0).IsNil():", mv.IsNil()) + + // Pointer value includes pointer receiver methods. + pv := reflect.ValueOf(&HasMethods{Name: "hello"}) + mv = pv.MethodByName("PtrMethod") + println("ptrValue.MethodByName(PtrMethod).IsValid():", mv.IsValid()) + println("ptrValue.MethodByName(PtrMethod).Kind():", mv.Kind().String()) + + // MethodByName should NOT find unexported methods. + _, ok = t.MethodByName("hidden") + println("MethodByName(hidden):", ok) + + // Method.IsExported should be true for exported methods. + em, _ := t.MethodByName("String") + println("Method(String).IsExported():", em.IsExported()) + + // --- Types with no methods --- + noMethodT := reflect.TypeOf(42) + println("int NumMethod:", noMethodT.NumMethod()) + + _, ok = noMethodT.MethodByName("Foo") + println("int MethodByName(Foo):", ok) + + // // --- Out-of-range Method panics --- + // // Commented out: TinyGo's recover() doesn't catch panics on all targets. + // func() { + // defer func() { + // if r := recover(); r != nil { + // println("Method(-1) panicked: true") + // } + // }() + // t.Method(-1) + // println("Method(-1) panicked: false") + // }() + // + // func() { + // defer func() { + // if r := recover(); r != nil { + // println("Method(99) panicked: true") + // } + // }() + // t.Method(99) + // println("Method(99) panicked: false") + // }() +} diff --git a/testdata/reflect.txt b/testdata/reflect.txt index 3024568c3d..73bf1122c8 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -512,3 +512,35 @@ blue gopher v.Interface() method kind: interface int 5 + +type method sets +struct NumMethod: 2 +struct Method: Len +struct Method: String +pointer NumMethod: 3 +pointer Method: Len +pointer Method: PtrMethod +pointer Method: String +MethodByName(String): String true +MethodByName(Len): Len true +MethodByName(Nonexistent): false +pointer MethodByName(PtrMethod): PtrMethod true +struct MethodByName(PtrMethod): false +embedded NumMethod: 2 +embedded Method: InnerMethod +embedded Method: OuterMethod +interface NumMethod: 2 +interface Method: Alpha +interface Method: Beta +Value.MethodByName(String).IsValid(): true +Value.MethodByName(String).Kind(): func +Value.MethodByName(Nonexistent).IsValid(): false +Value.Method(0).IsValid(): true +Value.Method(0).Kind(): func +Value.Method(0).IsNil(): false +ptrValue.MethodByName(PtrMethod).IsValid(): true +ptrValue.MethodByName(PtrMethod).Kind(): func +MethodByName(hidden): false +Method(String).IsExported(): true +int NumMethod: 0 +int MethodByName(Foo): false diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index eb954e1d03..54a01a2f6d 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -90,18 +90,19 @@ type interfaceInfo struct { // pass has been implemented as an object type because of its complexity, but // should be seen as a regular function call (see LowerInterfaces). type lowerInterfacesPass struct { - mod llvm.Module - config *compileopts.Config - builder llvm.Builder - dibuilder *llvm.DIBuilder - difiles map[string]llvm.Metadata - ctx llvm.Context - uintptrType llvm.Type - targetData llvm.TargetData - ptrType llvm.Type - types map[string]*typeInfo - signatures map[string]*signatureInfo - interfaces map[string]*interfaceInfo + mod llvm.Module + config *compileopts.Config + builder llvm.Builder + dibuilder *llvm.DIBuilder + difiles map[string]llvm.Metadata + ctx llvm.Context + uintptrType llvm.Type + targetData llvm.TargetData + ptrType llvm.Type + types map[string]*typeInfo + signatures map[string]*signatureInfo + interfaces map[string]*interfaceInfo + keepMethodNames bool // true when Method/MethodByName are used } // LowerInterfaces lowers all intermediate interface calls and globals that are @@ -342,17 +343,77 @@ func (p *lowerInterfacesPass) run() error { stripMethodSets = true } + // Check whether Method/MethodByName are used by scanning invoke + // thunks for these method signatures. When they are, method sets + // must keep their name data and pruning must be disabled. + // + // We scan invoke thunks rather than checking the concrete + // implementations directly because reflect.Type includes + // Method/MethodByName in its interface, so those implementations + // are always compiled even when user code never calls them. + keepAllMethods := false + for _, fn := range interfaceInvokeFunctions { + invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke") + sig := invokeAttr.GetStringValue() + if strings.HasPrefix(sig, "reflect/methods.Method(") || strings.HasPrefix(sig, "reflect/methods.MethodByName(") { + keepAllMethods = true + break + } + } + // Also check for direct calls to the concrete implementations + // from non-reflect code. + if !keepAllMethods { + // Internal bridge functions that always call Method/MethodByName + // as part of the reflect package plumbing, even when user code + // never invokes them. We ignore calls from these functions. + internalCallers := map[string]struct{}{ + "(*reflect.rawType).Method": {}, + "(*reflect.rawType).MethodByName": {}, + "reflectTypeMethodByIndex": {}, + "reflectTypeMethodByName": {}, + } + for _, name := range []string{ + "(*internal/reflectlite.RawType).Method", + "(*internal/reflectlite.RawType).MethodByName", + } { + fn := p.mod.NamedFunction(name) + if !fn.IsNil() && hasUses(fn) { + for use := fn.FirstUse(); !use.IsNil(); use = use.NextUse() { + user := use.User() + if !user.IsACallInst().IsNil() { + caller := user.InstructionParent().Parent().Name() + if _, ok := internalCallers[caller]; ok { + continue + } + keepAllMethods = true + break + } + } + } + if keepAllMethods { + break + } + } + } + if keepAllMethods { + stripMethodSets = false // Method sets are needed. + } + p.keepMethodNames = keepAllMethods + // Collect all method signatures that appear in any interface type // descriptor. When reflect is imported and method sets are kept, // concrete type method sets are pruned: individual methods not in any // interface are removed, and types that can't fully satisfy at least // one interface have their method sets emptied entirely. // + // When keepAllMethods is true (Method/MethodByName are used), + // pruning is disabled and all methods are kept. + // // When method sets are stripped entirely (reflect not imported), // methodFilter is nil and filterMethodSet replaces with empty. var methodFilter map[string]struct{} var ifaceMethodSets []map[string]struct{} - if !stripMethodSets { + if !stripMethodSets && !keepAllMethods { methodFilter = make(map[string]struct{}) for _, name := range typeNames { if !strings.HasPrefix(name, "interface:") { @@ -398,9 +459,16 @@ func (p *lowerInterfacesPass) run() error { } var newInitializerFields []llvm.Value + numMethodFieldIdx := -1 // index into newInitializerFields for i := 1; i < numFields; i++ { field := p.builder.CreateExtractValue(initializer, i, "") - field = p.filterMethodSet(field, methodFilter, ifaceMethodSets) + if !keepAllMethods { + field = p.filterMethodSet(field, methodFilter, ifaceMethodSets) + } + // Track where the numMethod field lands in the new slice. + if i == 2 && numMethodsIsI16 { + numMethodFieldIdx = len(newInitializerFields) + } // Strip empty inline method sets for Named, Pointer, and // Struct types. When the method set is pruned to empty, we // remove it and clear the numMethodHasMethodSet flag (bit 15 @@ -408,8 +476,10 @@ func (p *lowerInterfacesPass) run() error { if numMethodsIsI16 && numMethodsConst&numMethodHasMethodSet != 0 && p.isMethodSetType(field.Type()) { elems := field.Type().StructElementTypes() if elems[1].ArrayLength() == 0 { - clearedNumMethods := numMethodsConst & ^uint64(numMethodHasMethodSet) - newInitializerFields[1] = llvm.ConstInt(p.ctx.Int16Type(), clearedNumMethods, false) + if numMethodFieldIdx >= 0 && numMethodFieldIdx < len(newInitializerFields) { + clearedNumMethods := numMethodsConst & ^uint64(numMethodHasMethodSet) + newInitializerFields[numMethodFieldIdx] = llvm.ConstInt(p.ctx.Int16Type(), clearedNumMethods, false) + } continue } } @@ -442,6 +512,29 @@ func (p *lowerInterfacesPass) run() error { t.typecode.EraseFromParentAsGlobal() newGlobal.SetName(typecodeName) t.typecode = newGlobal + } else if !keepAllMethods { + // Types without an external method set (e.g., interface types) + // may still have inline method sets with name pointers that + // should be nulled out when Method/MethodByName aren't used. + initializer := t.typecode.Initializer() + if initializer.Type().TypeKind() != llvm.StructTypeKind { + continue + } + numFields := initializer.Type().StructElementTypesCount() + changed := false + var fields []llvm.Value + for i := 0; i < numFields; i++ { + field := p.builder.CreateExtractValue(initializer, i, "") + filtered := p.filterMethodSet(field, methodFilter, ifaceMethodSets) + if filtered.C != field.C { + changed = true + } + fields = append(fields, filtered) + } + if changed { + newInitializer := p.ctx.ConstStruct(fields, false) + t.typecode.SetInitializer(newInitializer) + } } } @@ -658,7 +751,7 @@ func (p *lowerInterfacesPass) defineInterfaceAssertFunc(fn llvm.Value, itf *inte } // isMethodSetType reports whether ty has the shape of a method-set struct: -// { uintptr, [N x ptr] }. +// { uintptr, [N x { ptr, ptr }] } where each entry is a {signature, name} pair. func (p *lowerInterfacesPass) isMethodSetType(ty llvm.Type) bool { if ty.TypeKind() != llvm.StructTypeKind { return false @@ -670,12 +763,25 @@ func (p *lowerInterfacesPass) isMethodSetType(ty llvm.Type) bool { if elems[0] != p.uintptrType { return false } - return elems[1].TypeKind() == llvm.ArrayTypeKind && elems[1].ElementType() == p.ptrType + if elems[1].TypeKind() != llvm.ArrayTypeKind { + return false + } + entryType := elems[1].ElementType() + if entryType.TypeKind() != llvm.StructTypeKind { + return false + } + entryElems := entryType.StructElementTypes() + return len(entryElems) == 2 && entryElems[0] == p.ptrType && entryElems[1] == p.ptrType +} + +// methodEntryType returns the LLVM type for a method set entry: { ptr, ptr }. +func (p *lowerInterfacesPass) methodEntryType() llvm.Type { + return p.ctx.StructType([]llvm.Type{p.ptrType, p.ptrType}, false) } // extractMethodSigs returns the names of method signature globals inside a -// method-set field ({ uintptr, [N x ptr] }). Returns nil if field is not a -// method set. +// method-set field ({ uintptr, [N x {ptr, ptr}] }). Returns nil if field is +// not a method set. The signature is the first element of each pair. func (p *lowerInterfacesPass) extractMethodSigs(field llvm.Value) []string { if !p.isMethodSetType(field.Type()) { return nil @@ -684,7 +790,8 @@ func (p *lowerInterfacesPass) extractMethodSigs(field llvm.Value) []string { n := methodArray.Type().ArrayLength() sigs := make([]string, 0, n) for j := 0; j < n; j++ { - sig := p.builder.CreateExtractValue(methodArray, j, "") + pair := p.builder.CreateExtractValue(methodArray, j, "") + sig := p.builder.CreateExtractValue(pair, 0, "") sig = stripPointerCasts(sig) sigs = append(sigs, sig.Name()) } @@ -707,12 +814,13 @@ func (p *lowerInterfacesPass) filterMethodSet(field llvm.Value, keepSigs map[str methodArray := p.builder.CreateExtractValue(field, 1, "") numMethods := methodArray.Type().ArrayLength() + entryType := p.methodEntryType() // Strip mode: replace with empty method set. if keepSigs == nil { return p.ctx.ConstStruct([]llvm.Value{ llvm.ConstInt(p.uintptrType, 0, false), - llvm.ConstArray(p.ptrType, nil), + llvm.ConstArray(entryType, nil), }, false) } @@ -720,18 +828,19 @@ func (p *lowerInterfacesPass) filterMethodSet(field llvm.Value, keepSigs map[str return field } - // Extract all methods and their signature names. - type methodEntry struct { - value llvm.Value - name string + // Extract all method entries and their signature names. + type methodInfo struct { + pair llvm.Value // the full {sig, name} pair + name string // signature global name (for matching) } - entries := make([]methodEntry, numMethods) + entries := make([]methodInfo, numMethods) nameSet := make(map[string]struct{}, numMethods) for j := 0; j < numMethods; j++ { - sig := p.builder.CreateExtractValue(methodArray, j, "") + pair := p.builder.CreateExtractValue(methodArray, j, "") + sig := p.builder.CreateExtractValue(pair, 0, "") stripped := stripPointerCasts(sig) name := stripped.Name() - entries[j] = methodEntry{sig, name} + entries[j] = methodInfo{pair, name} nameSet[name] = struct{}{} } @@ -748,25 +857,35 @@ func (p *lowerInterfacesPass) filterMethodSet(field llvm.Value, keepSigs map[str if !implementsAny { return p.ctx.ConstStruct([]llvm.Value{ llvm.ConstInt(p.uintptrType, 0, false), - llvm.ConstArray(p.ptrType, nil), + llvm.ConstArray(entryType, nil), }, false) } - // Prune: keep only methods whose signature appears in keepSigs. + // Prune: keep only method entries whose signature appears in keepSigs. + // When Method/MethodByName are not used, null out name pointers so + // LLVM can eliminate the name string globals. var kept []llvm.Value for _, e := range entries { if _, ok := keepSigs[e.name]; ok { - kept = append(kept, e.value) + if p.keepMethodNames { + kept = append(kept, e.pair) + } else { + sig := p.builder.CreateExtractValue(e.pair, 0, "") + kept = append(kept, p.ctx.ConstStruct([]llvm.Value{ + sig, + llvm.ConstNull(p.ptrType), + }, false)) + } } } - if len(kept) == numMethods { + if len(kept) == numMethods && p.keepMethodNames { return field } return p.ctx.ConstStruct([]llvm.Value{ llvm.ConstInt(p.uintptrType, uint64(len(kept)), false), - llvm.ConstArray(p.ptrType, kept), + llvm.ConstArray(entryType, kept), }, false) }