Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 38 additions & 10 deletions compiler/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 5 additions & 3 deletions compiler/testdata/interface.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
176 changes: 163 additions & 13 deletions src/internal/reflectlite/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -805,16 +813,16 @@ 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 {
if concreteType == nil {
return false
}

const ptrSize = unsafe.Sizeof((*byte)(nil))
const entrySize = unsafe.Sizeof(methodEntry{})
itfNumMethod := *(*uintptr)(assertedMethodSet)
if itfNumMethod == 0 {
return true
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
Loading
Loading