Skip to content

Commit 09711c9

Browse files
committed
compiler: support Method and MethodByName
1 parent 5ba8766 commit 09711c9

10 files changed

Lines changed: 423 additions & 49 deletions

File tree

compiler/interface.go

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,13 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
195195
for i := 0; i < ms.Len(); i++ {
196196
methods = append(methods, ms.At(i).Obj().(*types.Func))
197197
}
198+
methodEntryType := types.NewStruct([]*types.Var{
199+
types.NewVar(token.NoPos, nil, "signature", types.Typ[types.UnsafePointer]),
200+
types.NewVar(token.NoPos, nil, "name", types.Typ[types.UnsafePointer]),
201+
}, nil)
198202
methodSetType := types.NewStruct([]*types.Var{
199203
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
200-
types.NewVar(token.NoPos, nil, "methods", types.NewArray(types.Typ[types.UnsafePointer], int64(len(methods)))),
204+
types.NewVar(token.NoPos, nil, "methods", types.NewArray(methodEntryType, int64(len(methods)))),
201205
}, nil)
202206
methodSetValue := c.getMethodSetValue(methods)
203207
switch typ := typ.(type) {
@@ -844,12 +848,15 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string {
844848
}
845849

846850
// getMethodSetValue creates the method set struct value for a list of methods.
847-
// The struct contains a length and a sorted array of method signature pointers.
851+
// The struct contains a length and a sorted array of {signature, name} entries.
852+
// Each entry pairs a method signature pointer (for Implements comparison) with
853+
// a pointer to the method's null-terminated name string.
848854
func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
849855
// Create a sorted list of method signature global names.
850856
type methodRef struct {
851-
name string
852-
value llvm.Value
857+
sigGlobalName string
858+
methodName string
859+
sigValue llvm.Value
853860
}
854861
var refs []methodRef
855862
for _, method := range methods {
@@ -880,23 +887,44 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
880887
value.AddMetadata(0, diglobal)
881888
}
882889
}
883-
refs = append(refs, methodRef{globalName, value})
890+
refs = append(refs, methodRef{globalName, name, value})
884891
}
885892
sort.Slice(refs, func(i, j int) bool {
886-
return refs[i].name < refs[j].name
893+
return refs[i].sigGlobalName < refs[j].sigGlobalName
887894
})
888895

889-
var values []llvm.Value
896+
pairType := c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false)
897+
var pairs []llvm.Value
890898
for _, ref := range refs {
891-
values = append(values, ref.value)
899+
nameGlobal := c.getMethodNameGlobal(ref.methodName)
900+
pair := c.ctx.ConstStruct([]llvm.Value{ref.sigValue, nameGlobal}, false)
901+
pairs = append(pairs, pair)
892902
}
893903

894904
return c.ctx.ConstStruct([]llvm.Value{
895-
llvm.ConstInt(c.uintptrType, uint64(len(values)), false),
896-
llvm.ConstArray(c.dataPtrType, values),
905+
llvm.ConstInt(c.uintptrType, uint64(len(pairs)), false),
906+
llvm.ConstArray(pairType, pairs),
897907
}, false)
898908
}
899909

910+
// getMethodNameGlobal returns a global containing the null-terminated method
911+
// name string, creating it if needed.
912+
func (c *compilerContext) getMethodNameGlobal(name string) llvm.Value {
913+
globalName := "reflect/types.methodname:" + name
914+
g := c.mod.NamedGlobal(globalName)
915+
if !g.IsNil() {
916+
return g
917+
}
918+
nameBytes := c.ctx.ConstString(name+"\x00", false)
919+
g = llvm.AddGlobal(c.mod, nameBytes.Type(), globalName)
920+
g.SetInitializer(nameBytes)
921+
g.SetGlobalConstant(true)
922+
g.SetLinkage(llvm.LinkOnceODRLinkage)
923+
g.SetAlignment(1)
924+
g.SetUnnamedAddr(true)
925+
return g
926+
}
927+
900928
// getInvokeFunction returns the thunk to call the given interface method. The
901929
// thunk is declared, not defined: it will be defined by the interface lowering
902930
// pass.

compiler/testdata/interface.ll

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ target triple = "wasm32-unknown-wasi"
1010
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4
1111
@"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4
1212
@"reflect/types.signature:Error:func:{}{basic:string}" = linkonce_odr constant i8 0, align 1
13-
@"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
13+
@"reflect/types.methodname:Error" = linkonce_odr unnamed_addr constant [6 x i8] c"Error\00", align 1
14+
@"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
1415
@"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1
15-
@"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
16+
@"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
1617
@"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
1718
@"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
1819
@"reflect/types.signature:String:func:{}{basic:string}" = linkonce_odr constant i8 0, align 1
19-
@"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
20+
@"reflect/types.methodname:String" = linkonce_odr unnamed_addr constant [7 x i8] c"String\00", align 1
21+
@"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
2022
@"reflect/types.typeid:basic:int" = external constant i8
2123

2224
; Function Attrs: allockind("alloc,zeroed") allocsize(0)

src/internal/reflectlite/type.go

Lines changed: 118 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,18 @@ type structField struct {
252252
data unsafe.Pointer // various bits of information, packed in a byte array
253253
}
254254

255+
// Method set entry, as emitted by the compiler. Each entry pairs a signature
256+
// identity pointer (for Implements/AssignableTo comparison) with a pointer to
257+
// the method's null-terminated name string.
258+
type methodEntry struct {
259+
signature unsafe.Pointer
260+
name *byte
261+
}
262+
255263
// Method set, as emitted by the compiler.
256264
type methodSet struct {
257265
length uintptr
258-
methods [0]unsafe.Pointer // variable number of method signature pointers
266+
methods [0]methodEntry
259267
}
260268

261269
// Equivalent to (go/types.Type).Underlying(): if this is a named type return
@@ -805,16 +813,16 @@ func (t *RawType) Implements(u Type) bool {
805813

806814
// typeImplementsMethodSet checks whether the concrete type (identified by its
807815
// typecode pointer) implements the given method set. Both the concrete type's
808-
// method set and the asserted method set are sorted arrays of method signature
809-
// pointers, so comparison is O(n+m).
816+
// method set and the asserted method set are sorted arrays of {signature, name}
817+
// entries, so comparison is O(n+m). Only the signature field is compared.
810818
//
811819
//go:linkname typeImplementsMethodSet runtime.typeImplementsMethodSet
812820
func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) bool {
813821
if concreteType == nil {
814822
return false
815823
}
816824

817-
const ptrSize = unsafe.Sizeof((*byte)(nil))
825+
const entrySize = unsafe.Sizeof(methodEntry{})
818826
itfNumMethod := *(*uintptr)(assertedMethodSet)
819827
if itfNumMethod == 0 {
820828
return true
@@ -853,13 +861,14 @@ func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) boo
853861
}
854862

855863
concreteTypePtr := unsafe.Pointer(&methods.methods)
856-
concreteTypeEnd := unsafe.Add(concreteTypePtr, uintptr(methods.length)*ptrSize)
864+
concreteTypeEnd := unsafe.Add(concreteTypePtr, uintptr(methods.length)*entrySize)
857865

858866
// Iterate over each method in the interface method set, and check whether
859867
// the method exists in the method set of the concrete type.
860868
// Both method sets are sorted, so we can use a linear scan.
861-
assertedTypePtr := unsafe.Add(assertedMethodSet, ptrSize)
862-
assertedTypeEnd := unsafe.Add(assertedTypePtr, itfNumMethod*ptrSize)
869+
// Each entry is a {signature, name} pair; we compare only the signature.
870+
assertedTypePtr := unsafe.Add(assertedMethodSet, unsafe.Sizeof(uintptr(0)))
871+
assertedTypeEnd := unsafe.Add(assertedTypePtr, itfNumMethod*entrySize)
863872
for assertedTypePtr != assertedTypeEnd {
864873
assertedMethod := *(*unsafe.Pointer)(assertedTypePtr)
865874

@@ -868,13 +877,13 @@ func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) boo
868877
return false
869878
}
870879
concreteMethod := *(*unsafe.Pointer)(concreteTypePtr)
871-
concreteTypePtr = unsafe.Add(concreteTypePtr, ptrSize)
880+
concreteTypePtr = unsafe.Add(concreteTypePtr, entrySize)
872881
if concreteMethod == assertedMethod {
873882
break
874883
}
875884
}
876885

877-
assertedTypePtr = unsafe.Add(assertedTypePtr, ptrSize)
886+
assertedTypePtr = unsafe.Add(assertedTypePtr, entrySize)
878887
}
879888

880889
return true
@@ -921,6 +930,104 @@ func (t *RawType) NumMethod() int {
921930
return 0
922931
}
923932

933+
// getMethodSet returns the method set for a type, or nil if the type has no
934+
// inline method set.
935+
func (t *RawType) getMethodSet() *methodSet {
936+
if t.isNamed() {
937+
ct := (*namedType)(unsafe.Pointer(t))
938+
if ct.numMethod&numMethodHasMethodSet == 0 {
939+
return nil
940+
}
941+
return (*methodSet)(unsafe.Add(unsafe.Pointer(ct), unsafe.Sizeof(*ct)))
942+
}
943+
switch t.Kind() {
944+
case Interface:
945+
ct := (*interfaceType)(unsafe.Pointer(t.underlying()))
946+
return &ct.methods
947+
case Pointer:
948+
ct := (*ptrType)(unsafe.Pointer(t))
949+
if ct.numMethod&numMethodHasMethodSet == 0 {
950+
return nil
951+
}
952+
return &ct.methods
953+
case Struct:
954+
ct := (*structType)(unsafe.Pointer(t))
955+
if ct.numMethod&numMethodHasMethodSet == 0 {
956+
return nil
957+
}
958+
fieldSize := unsafe.Sizeof(structField{})
959+
methodsPtr := unsafe.Add(unsafe.Pointer(&ct.fields[0]), uintptr(ct.numField)*fieldSize)
960+
return (*methodSet)(methodsPtr)
961+
}
962+
return nil
963+
}
964+
965+
// methodSetEntry returns the i-th entry in the method set.
966+
func methodSetEntry(ms *methodSet, i int) *methodEntry {
967+
return (*methodEntry)(unsafe.Add(unsafe.Pointer(&ms.methods), uintptr(i)*unsafe.Sizeof(methodEntry{})))
968+
}
969+
970+
// Method returns the i-th method in the type's method set.
971+
//
972+
//go:linkname reflectTypeMethodByIndex reflect.(*rawType).Method
973+
func (t *RawType) Method(i int) MethodInfo {
974+
methodSetLookup() // Ensure method name data is preserved.
975+
n := t.NumMethod()
976+
if i < 0 || i >= n {
977+
panic("reflect: Method index out of range")
978+
}
979+
ms := t.getMethodSet()
980+
if ms == nil || int(ms.length) <= i {
981+
// Method set was pruned or stripped; name unavailable.
982+
return MethodInfo{Index: i}
983+
}
984+
entry := methodSetEntry(ms, i)
985+
name := readStringZ(unsafe.Pointer(entry.name))
986+
return MethodInfo{
987+
Name: name,
988+
Index: i,
989+
}
990+
}
991+
992+
// MethodByName returns the method with the given name in the type's method
993+
// set, and a boolean indicating if the method was found.
994+
//
995+
//go:linkname reflectTypeMethodByName reflect.(*rawType).MethodByName
996+
func (t *RawType) MethodByName(name string) (MethodInfo, bool) {
997+
methodSetLookup() // Ensure method name data is preserved.
998+
ms := t.getMethodSet()
999+
if ms == nil {
1000+
return MethodInfo{}, false
1001+
}
1002+
n := int(ms.length)
1003+
for i := 0; i < n; i++ {
1004+
entry := methodSetEntry(ms, i)
1005+
ename := readStringZ(unsafe.Pointer(entry.name))
1006+
if ename == name {
1007+
return MethodInfo{
1008+
Name: name,
1009+
Index: i,
1010+
}, true
1011+
}
1012+
}
1013+
return MethodInfo{}, false
1014+
}
1015+
1016+
// MethodInfo describes a single method. This is the internal reflectlite
1017+
// representation; the reflect package wraps this in reflect.Method.
1018+
type MethodInfo struct {
1019+
Name string
1020+
PkgPath string
1021+
Index int
1022+
}
1023+
1024+
// methodSetLookup is a sentinel function whose presence signals to the
1025+
// interface lowering pass that method name data must be preserved.
1026+
//
1027+
//go:linkname methodSetLookup runtime.methodSetLookup
1028+
//go:noinline
1029+
func methodSetLookup() {}
1030+
9241031
// Read and return a null terminated string starting from data.
9251032
func readStringZ(data unsafe.Pointer) string {
9261033
start := data
@@ -942,8 +1049,8 @@ func (t *RawType) name() string {
9421049
ptr := unsafe.Add(unsafe.Pointer(ntype), unsafe.Sizeof(*ntype))
9431050
if ntype.numMethod&numMethodHasMethodSet != 0 {
9441051
ms := (*methodSet)(ptr)
945-
// Skip past the length field and the method pointer entries.
946-
ptr = unsafe.Add(ptr, unsafe.Sizeof(uintptr(0))+uintptr(ms.length)*unsafe.Sizeof(unsafe.Pointer(nil)))
1052+
// Skip past the length field and the method entries.
1053+
ptr = unsafe.Add(ptr, unsafe.Sizeof(uintptr(0))+uintptr(ms.length)*unsafe.Sizeof(methodEntry{}))
9471054
}
9481055
return readStringZ(ptr)
9491056
}

src/internal/reflectlite/value.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2165,11 +2165,34 @@ func (v Value) CallSlice(in []Value) []Value {
21652165
}
21662166

21672167
func (v Value) Method(i int) Value {
2168-
panic("unimplemented: (reflect.Value).Method()")
2168+
if v.Kind() == Invalid {
2169+
panic(&ValueError{Method: "reflect.Value.Method", Kind: Invalid})
2170+
}
2171+
n := v.typecode.NumMethod()
2172+
if i < 0 || i >= n {
2173+
panic("reflect: Method index out of range")
2174+
}
2175+
// Return a valid Value representing the bound method. Without Call()
2176+
// support, this value cannot be invoked but satisfies IsValid() checks.
2177+
return Value{
2178+
typecode: v.typecode,
2179+
value: v.value,
2180+
flags: v.flags & valueFlagExported,
2181+
}
21692182
}
21702183

21712184
func (v Value) MethodByName(name string) Value {
2172-
panic("unimplemented: (reflect.Value).MethodByName()")
2185+
if v.Kind() == Invalid {
2186+
panic(&ValueError{Method: "reflect.Value.MethodByName", Kind: Invalid})
2187+
}
2188+
if _, ok := v.typecode.MethodByName(name); !ok {
2189+
return Value{}
2190+
}
2191+
return Value{
2192+
typecode: v.typecode,
2193+
value: v.value,
2194+
flags: v.flags & valueFlagExported,
2195+
}
21732196
}
21742197

21752198
func (v Value) Recv() (x Value, ok bool) {

src/reflect/type.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,11 +446,24 @@ func (t *rawType) Key() Type {
446446
}
447447

448448
func (t *rawType) Method(i int) Method {
449-
panic("unimplemented: (reflect.Type).Method()")
449+
m := t.RawType.Method(i)
450+
return Method{
451+
Name: m.Name,
452+
PkgPath: m.PkgPath,
453+
Index: m.Index,
454+
}
450455
}
451456

452457
func (t *rawType) MethodByName(name string) (Method, bool) {
453-
panic("unimplemented: (reflect.Type).MethodByName()")
458+
m, ok := t.RawType.MethodByName(name)
459+
if !ok {
460+
return Method{}, false
461+
}
462+
return Method{
463+
Name: m.Name,
464+
PkgPath: m.PkgPath,
465+
Index: m.Index,
466+
}, true
454467
}
455468

456469
func (t *rawType) NumIn() int {

src/reflect/value.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,11 @@ func (v Value) Equal(u Value) bool {
214214
}
215215

216216
func (v Value) Method(i int) Value {
217-
panic("unimplemented: (reflect.Value).Method()")
217+
return Value{v.Value.Method(i)}
218218
}
219219

220220
func (v Value) MethodByName(name string) Value {
221-
panic("unimplemented: (reflect.Value).MethodByName()")
221+
return Value{v.Value.MethodByName(name)}
222222
}
223223

224224
func (v Value) Recv() (x Value, ok bool) {

src/runtime/interface.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ func interfaceTypeAssert(ok bool) {
9393
// Implemented in the internal/reflectlite package.
9494
func typeImplementsMethodSet(actualTypeNum, assertedMethodSet unsafe.Pointer) bool
9595

96+
// methodSetLookup is a sentinel function. Its presence in the program signals
97+
// to the interface lowering pass that method name data must be preserved in
98+
// type descriptors (for reflect.Type.Method / MethodByName).
99+
// Implemented in the internal/reflectlite package.
100+
func methodSetLookup()
101+
96102
// The following declarations are only used during IR construction. They are
97103
// lowered to inline IR in the interface lowering pass.
98104
// See compiler/interface-lowering.go for details.

0 commit comments

Comments
 (0)