Skip to content
Merged
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
24 changes: 23 additions & 1 deletion cl/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ func (p *context) initFiles(pkgPath string, files []*ast.File, cPkg bool) {
switch decl := decl.(type) {
case *ast.FuncDecl:
fullName, inPkgName := astFuncName(pkgPath, decl)
p.processNoInterfaceByDoc(decl.Doc, fullName)
if !p.processLinknameByDoc(decl.Doc, fullName, inPkgName, false, true) && cPkg {
// package C (https://github.com/goplus/llgo/issues/1165)
if decl.Recv == nil && token.IsExported(inPkgName) {
Expand Down Expand Up @@ -312,6 +313,22 @@ func (p *context) processLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgNam
return false
}

func (p *context) processNoInterfaceByDoc(doc *ast.CommentGroup, fullName string) {
if doc == nil {
return
}
for n := len(doc.List) - 1; n >= 0; n-- {
line := doc.List[n].Text
if line == "//go:nointerface" {
p.prog.SetNoInterfaceMethod(fullName)
return
}
if !strings.HasPrefix(line, "//go:") {
return
}
}
}

const (
noDirective = iota
hasLinkname
Expand Down Expand Up @@ -737,11 +754,16 @@ func (p *context) initPyModule() {
}
}

// ParsePkgSyntax parses AST of a package to check llgo:type in type declaration.
// ParsePkgSyntax parses AST of a package to check package-level compiler directives.
func ParsePkgSyntax(prog llssa.Program, pkg *types.Package, files []*ast.File) {
ctx := &context{prog: prog}
pkgPath := llssa.PathOf(pkg)
for _, file := range files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
fullName, _ := astFuncName(pkgPath, decl)
ctx.processNoInterfaceByDoc(decl.Doc, fullName)
case *ast.GenDecl:
switch decl.Tok {
case token.TYPE:
Expand Down
14 changes: 14 additions & 0 deletions cl/import_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ type (
B int
C int
)

//go:nointerface
func (A) Hidden() {}

//go:other
//go:nointerface
func (A) StackedHidden() {}
`
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "p.go", src, parser.ParseComments)
Expand All @@ -56,6 +63,13 @@ type (
prog := llssa.NewProgram(nil)
pkg := types.NewPackage("example.com/p", "p")
ParsePkgSyntax(prog, pkg, []*ast.File{file})

ctx := &context{prog: prog}
ctx.processNoInterfaceByDoc(nil, "example.com/p.NilDoc")
ctx.processNoInterfaceByDoc(&ast.CommentGroup{List: []*ast.Comment{
{Text: "// not a directive"},
{Text: "//go:nointerface"},
}}, "example.com/p.NonDirectiveStops")
}

func TestPkgSymInfoAddSymAndInitLinknamesCoverage(t *testing.T) {
Expand Down
15 changes: 9 additions & 6 deletions runtime/internal/lib/reflect/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,16 +305,19 @@ func (t *rtype) Method(i int) (m Method) {
}
mt := FuncOf(in, out, ft.Variadic())
m.Type = mt
mtfn := (*funcType)(unsafe.Pointer(&mt.(*rtype).t))
fv := &struct {
fn unsafe.Pointer
env unsafe.Pointer
}{p.Tfn_, nil}
m.Func = Value{closureOf(mtfn), unsafe.Pointer(fv), fl | flagIndir}
m.Func = methodFuncValue(&mt.(*rtype).t, p.Tfn_, fl)
m.Index = i
return m
}

func methodFuncValue(ft *abi.Type, fn unsafe.Pointer, fl flag) Value {
ct := closureOf((*funcType)(unsafe.Pointer(ft)))
c := unsafe_New(ct)
*(*unsafe.Pointer)(c) = fn
*(*unsafe.Pointer)(add(c, goarch.PtrSize, "closure data field")) = nil
return Value{ct, c, fl | flagIndir}
}

func (t *rtype) MethodByName(name string) (m Method, ok bool) {
if t.Kind() == Interface {
tt := (*interfaceType)(unsafe.Pointer(t))
Expand Down
24 changes: 23 additions & 1 deletion runtime/internal/runtime/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,29 @@ func PanicTypeAssert(concrete *_type, asserted string, missingMethod string) {
if missingMethod != "" {
panic(errorString("interface conversion: " + concrete.String() + " is not " + asserted + ": missing method " + missingMethod))
}
panic(errorString("interface conversion: interface is " + concrete.String() + ", not " + asserted))
cs := concrete.String()
msg := "interface conversion: interface is " + cs + ", not " + asserted
if sameTypeAssertName(concrete, cs, asserted) {
msg += " (types from different scopes)"
}
panic(errorString(msg))
}

func sameTypeAssertName(concrete *_type, concreteString, asserted string) bool {
if concreteString == asserted {
return true
}
pkg := pkgpath(concrete)
return pkg != "" && hasPrefix(asserted, pkg+".") && typeNameSuffix(concreteString) == typeNameSuffix(asserted)
}

func typeNameSuffix(name string) string {
for i := len(name) - 1; i >= 0; i-- {
if name[i] == '.' {
return name[i+1:]
}
}
return name
}

func (e *TypeAssertionError) Error() string {
Expand Down
10 changes: 8 additions & 2 deletions runtime/internal/runtime/z_face.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,16 @@ func DirectIfaceData(typ *abi.Type) bool {
func MatchesClosure(T, V *abi.Type) bool {
if T == V {
return true
} else if V == nil || !V.IsClosure() {
} else if T == nil || V == nil {
return false
}
return identicalFuncType(T.StructType().Fields[0].Typ, V.StructType().Fields[0].Typ)
if T.IsClosure() {
T = T.StructType().Fields[0].Typ
}
if V.IsClosure() {
V = V.StructType().Fields[0].Typ
}
return identicalFuncType(T, V)
}

func identicalFuncType(T, V *abi.Type) bool {
Expand Down
39 changes: 27 additions & 12 deletions ssa/abitype.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,16 +398,16 @@ type UncommonType struct {
}
*/

func (b Builder) abiUncommonType(t types.Type, mset *types.MethodSet) llvm.Value {
func (b Builder) abiUncommonType(t types.Type, methods []*types.Selection) llvm.Value {
prog := b.Prog
ft := prog.rtType("uncommonType")
var fields []llvm.Value
_, pkgPath := b.abiUncommonPkg(t)
fields = append(fields, b.Str(pkgPath).impl)
mcount := mset.Len()
mcount := len(methods)
var xcount int
for i := 0; i < mcount; i++ {
if ast.IsExported(mset.At(i).Obj().Name()) {
if ast.IsExported(methods[i].Obj().Name()) {
xcount++
}
}
Expand All @@ -427,18 +427,18 @@ type Method struct {
}
*/

func (b Builder) abiUncommonMethods(t types.Type, mset *types.MethodSet) llvm.Value {
func (b Builder) abiUncommonMethods(t types.Type, methods []*types.Selection) llvm.Value {
prog := b.Prog
ft := prog.rtType("Method")
n := mset.Len()
n := len(methods)
fields := make([]llvm.Value, n)
pkg, _ := b.abiUncommonPkg(t)
anonymous := pkg == nil
if anonymous {
pkg = types.NewPackage(b.Pkg.Path(), "")
}
for i := 0; i < n; i++ {
m := mset.At(i)
m := methods[i]
obj := m.Obj()
mName := obj.Name()
abiName := mName
Expand Down Expand Up @@ -468,6 +468,20 @@ func (b Builder) abiUncommonMethods(t types.Type, mset *types.MethodSet) llvm.Va
return llvm.ConstArray(ft.ll, fields)
}

func (b Builder) abiInterfaceMethods(mset *types.MethodSet) []*types.Selection {
n := mset.Len()
methods := make([]*types.Selection, 0, n)
for i := 0; i < n; i++ {
m := mset.At(i)
fn, _ := m.Obj().(*types.Func)
if b.Prog.isNoInterfaceMethod(fn) {
continue
}
methods = append(methods, m)
}
return methods
}

// closure func type
func funcType(prog Program, typ types.Type) types.Type {
ftyp := prog.Type(typ, InGo)
Expand Down Expand Up @@ -524,10 +538,11 @@ func (b Builder) abiType(t types.Type) Expr {
t = prog.patchType(t)
}
mset, hasUncommon := b.abiUncommonMethodSet(t)
methodCount := 0
if mset != nil {
methodCount = mset.Len()
var methods []*types.Selection
if hasUncommon {
methods = b.abiInterfaceMethods(mset)
}
methodCount := len(methods)
rt := prog.rtNamed(prog.abi.RuntimeName(t))
var typ types.Type = rt
if hasUncommon {
Expand All @@ -552,15 +567,15 @@ func (b Builder) abiType(t types.Type) Expr {
if hasUncommon {
fields = []llvm.Value{
llvm.ConstNamedStruct(prog.Type(rt, InGo).ll, fields),
b.abiUncommonType(t, mset),
b.abiUncommonMethods(t, mset),
b.abiUncommonType(t, methods),
b.abiUncommonMethods(t, methods),
}
}
g.impl.SetInitializer(llvm.ConstNamedStruct(g.impl.GlobalValueType(), fields))
g.impl.SetGlobalConstant(true)
g.impl.SetLinkage(llvm.WeakODRLinkage)
if prog.enableGoGlobalDCE {
prog.addMethodTypeMetadata(g.impl, prog.Type(typ, InGo), mset, methodCount)
prog.addMethodTypeMetadata(g.impl, prog.Type(typ, InGo), methods)
}
prog.abiSymbol[name] = &AbiSymbol{Name: name, PkgPath: pkg.Path(), Raw: t, Typ: g.Type, MSet: mset}
}
Expand Down
7 changes: 3 additions & 4 deletions ssa/globaldce.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ func (p Function) recordFakeUse(v llvm.Value) {
p.fakeUses = append(p.fakeUses, v)
}

func (p Program) addMethodTypeMetadata(global llvm.Value, fullType Type, mset *types.MethodSet, methodCount int) {
if methodCount == 0 {
func (p Program) addMethodTypeMetadata(global llvm.Value, fullType Type, methods []*types.Selection) {
if len(methods) == 0 {
return
}
p.setVCallVisibilityMetadata(global, vcallVisibilityLinkageUnit)
Expand All @@ -287,8 +287,7 @@ func (p Program) addMethodTypeMetadata(global llvm.Value, fullType Type, mset *t
ifnOffset := p.OffsetOf(methodType, abiMethodIFnFieldIndex)
tfnOffset := p.OffsetOf(methodType, abiMethodTFnFieldIndex)
methodStride := p.SizeOf(methodType)
for i := 0; i < methodCount; i++ {
sel := mset.At(i)
for i, sel := range methods {
baseOffset := methodArrayOffset + uint64(i)*methodStride
p.addTypeMetadata(global, baseOffset+ifnOffset, methodCapabilityKey(sel.Obj().(*types.Func)))
if sel.Obj().Exported() {
Expand Down
19 changes: 18 additions & 1 deletion ssa/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ type aProgram struct {

paramObjPtr_ *types.Var
linkname map[string]string // pkgPath.nameInPkg => linkname
noInterface map[string]none // pkgPath.T.method or pkgPath.(*T).method
abiSymbol map[string]*AbiSymbol // abi symbol name => AbiSymbol

ptrSize int
Expand Down Expand Up @@ -310,7 +311,7 @@ func NewProgram(target *Target) Program {
ctx: ctx, gocvt: newGoTypes(),
target: target, td: td, tm: tm, is32Bits: is32Bits,
ptrSize: td.PointerSize(), named: make(map[string]Type), fnnamed: make(map[string]int),
linkname: make(map[string]string), abiSymbol: make(map[string]*AbiSymbol),
linkname: make(map[string]string), noInterface: make(map[string]none), abiSymbol: make(map[string]*AbiSymbol),
}
prog.abi.Init(uintptr(prog.ptrSize), (*goProgram)(unsafe.Pointer(prog)))
return prog
Expand Down Expand Up @@ -359,6 +360,22 @@ func (p Program) EnableLTOPluginMarkers(enable bool) {
p.enableLTOPluginMarker = enable
}

func (p Program) SetNoInterfaceMethod(fullName string) {
p.noInterface[fullName] = none{}
}

func (p Program) isNoInterfaceMethod(fn *types.Func) bool {
if fn == nil {
return false
}
sig, ok := fn.Type().(*types.Signature)
if !ok || sig.Recv() == nil {
return false
}
_, ok = p.noInterface[FuncName(fn.Pkg(), fn.Name(), sig.Recv(), true)]
return ok
}

// SetRuntime sets the runtime.
// Its type can be *types.Package or func() *types.Package.
func (p Program) SetRuntime(runtime any) {
Expand Down
47 changes: 44 additions & 3 deletions ssa/ssa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -734,9 +734,8 @@ func TestDevLTOGlobalDCEAddMethodTypeMetadataEarlyReturns(t *testing.T) {
prog.EnableGoGlobalDCE(true)
pkg := prog.NewPackage("main", "main")
g := pkg.NewVarEx("g", prog.Pointer(prog.Int()))
mset := types.NewMethodSet(types.Typ[types.Int])

prog.addMethodTypeMetadata(g.impl, prog.Pointer(prog.Int()), mset, 0)
prog.addMethodTypeMetadata(g.impl, prog.Pointer(prog.Int()), nil)

ir := pkg.String()
if strings.Contains(ir, "!vcall_visibility") || strings.Contains(ir, "!type !") {
Expand Down Expand Up @@ -767,7 +766,7 @@ func TestDevLTOGlobalDCEAddMethodTypeMetadataMarksIFnAndTFnForReflectContexts(t

methodArray := prog.Type(types.NewArray(prog.rtNamed("Method"), 1), InGo)
fullType := prog.Struct(prog.Int(), prog.Int(), methodArray)
prog.addMethodTypeMetadata(g.impl, fullType, mset, mset.Len())
prog.addMethodTypeMetadata(g.impl, fullType, []*types.Selection{mset.At(0)})

methodType := prog.Type(prog.rtNamed("Method"), InGo)
methodArrayOffset := prog.OffsetOf(fullType, 2)
Expand Down Expand Up @@ -2606,6 +2605,48 @@ func TestInitAbiTypesForEmptySelection(t *testing.T) {
}
}

func TestNoInterfaceMethodRegistryAndFiltering(t *testing.T) {
prog := NewProgram(nil)
if prog.isNoInterfaceMethod(nil) {
t.Fatal("nil function should not be nointerface")
}

pkgTypes := types.NewPackage("example.com/p", "p")
named := types.NewNamed(types.NewTypeName(token.NoPos, pkgTypes, "T", nil), types.NewStruct(nil, nil), nil)
sig := types.NewSignatureType(types.NewVar(token.NoPos, pkgTypes, "", named), nil, nil, nil, nil, false)
hidden := types.NewFunc(token.NoPos, pkgTypes, "Hidden", sig)
visible := types.NewFunc(token.NoPos, pkgTypes, "Visible", sig)
named.AddMethod(hidden)
named.AddMethod(visible)

top := types.NewFunc(token.NoPos, pkgTypes, "Top", types.NewSignatureType(nil, nil, nil, nil, nil, false))
if prog.isNoInterfaceMethod(top) {
t.Fatal("function without receiver should not be nointerface")
}
if prog.isNoInterfaceMethod(hidden) {
t.Fatal("unregistered method should not be nointerface")
}
prog.SetNoInterfaceMethod("example.com/p.T.Hidden")
if !prog.isNoInterfaceMethod(hidden) {
t.Fatal("registered value receiver method should be nointerface")
}
if prog.isNoInterfaceMethod(visible) {
t.Fatal("unregistered sibling method should not be nointerface")
}

methods := (&aBuilder{Prog: prog}).abiInterfaceMethods(types.NewMethodSet(named))
if len(methods) != 1 || methods[0].Obj().Name() != "Visible" {
t.Fatalf("filtered methods = %v, want only Visible", methods)
}

ptrSig := types.NewSignatureType(types.NewVar(token.NoPos, pkgTypes, "", types.NewPointer(named)), nil, nil, nil, nil, false)
ptrHidden := types.NewFunc(token.NoPos, pkgTypes, "PtrHidden", ptrSig)
prog.SetNoInterfaceMethod("example.com/p.(*T).PtrHidden")
if !prog.isNoInterfaceMethod(ptrHidden) {
t.Fatal("registered pointer receiver method should be nointerface")
}
}

func TestRtFuncResolvesLinkname(t *testing.T) {
prog := NewProgram(nil)
rt := types.NewPackage(PkgRuntime, PkgRuntime)
Expand Down
9 changes: 3 additions & 6 deletions test/go/interface_nil_assert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ func TestNilInterfaceSameTypeAssert(t *testing.T) {

func TestNilInterfaceSameTypeAssertPanics(t *testing.T) {
x := nilAssertValue(false)
defer func() {
if recover() == nil {
t.Fatal("expected panic for nil interface same-type assert")
}
}()
_ = x.(nilAssertInterface)
expectPanicContaining(t, "interface conversion", func() {
_ = x.(nilAssertInterface)
})
}
Loading
Loading