From e5c7ac2815317961752c5656214e95eb3ea0dbc9 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Sun, 13 Apr 2025 11:28:15 +0800 Subject: [PATCH 01/11] gopls: add CompiledAsmFiles in cache.Package The CompiledAsmFiles field supports storing assembly files. Fixes golang/go#71754 --- gopls/internal/cache/check.go | 25 +++++++++++++++++------ gopls/internal/cache/load.go | 9 ++++++++ gopls/internal/cache/metadata/metadata.go | 1 + gopls/internal/cache/package.go | 2 ++ gopls/internal/cache/parse.go | 21 +++++++++++++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/gopls/internal/cache/check.go b/gopls/internal/cache/check.go index 909003288bc..93518886c7e 100644 --- a/gopls/internal/cache/check.go +++ b/gopls/internal/cache/check.go @@ -1456,12 +1456,12 @@ type typeCheckInputs struct { id PackageID // Used for type checking: - pkgPath PackagePath - name PackageName - goFiles, compiledGoFiles []file.Handle - sizes types.Sizes - depsByImpPath map[ImportPath]PackageID - goVersion string // packages.Module.GoVersion, e.g. "1.18" + pkgPath PackagePath + name PackageName + goFiles, compiledGoFiles, asmFiles []file.Handle + sizes types.Sizes + depsByImpPath map[ImportPath]PackageID + goVersion string // packages.Module.GoVersion, e.g. "1.18" // Used for type check diagnostics: // TODO(rfindley): consider storing less data in gobDiagnostics, and @@ -1491,6 +1491,10 @@ func (s *Snapshot) typeCheckInputs(ctx context.Context, mp *metadata.Package) (* if err != nil { return nil, err } + asmFiles, err := readFiles(ctx, s, mp.AsmFiles) + if err != nil { + return nil, err + } goVersion := "" if mp.Module != nil && mp.Module.GoVersion != "" { @@ -1503,6 +1507,7 @@ func (s *Snapshot) typeCheckInputs(ctx context.Context, mp *metadata.Package) (* name: mp.Name, goFiles: goFiles, compiledGoFiles: compiledGoFiles, + asmFiles: asmFiles, sizes: mp.TypesSizes, depsByImpPath: mp.DepsByImpPath, goVersion: goVersion, @@ -1555,6 +1560,10 @@ func localPackageKey(inputs *typeCheckInputs) file.Hash { for _, fh := range inputs.goFiles { fmt.Fprintln(hasher, fh.Identity()) } + fmt.Fprintf(hasher, "asmFiles:%d\n", len(inputs.asmFiles)) + for _, fh := range inputs.asmFiles { + fmt.Fprintln(hasher, fh.Identity()) + } // types sizes wordSize := inputs.sizes.Sizeof(types.Typ[types.Int]) @@ -1611,6 +1620,10 @@ func (b *typeCheckBatch) checkPackage(ctx context.Context, fset *token.FileSet, pkg.parseErrors = append(pkg.parseErrors, pgf.ParseErr) } } + pkg.asmFiles, err = parseAsmFiles(ctx, inputs.asmFiles...) + if err != nil { + return nil, err + } // Use the default type information for the unsafe package. if inputs.pkgPath == "unsafe" { diff --git a/gopls/internal/cache/load.go b/gopls/internal/cache/load.go index e15e0cef0b6..956ba6d396f 100644 --- a/gopls/internal/cache/load.go +++ b/gopls/internal/cache/load.go @@ -454,6 +454,15 @@ func buildMetadata(updates map[PackageID]*metadata.Package, loadDir string, stan *dst = append(*dst, protocol.URIFromPath(filename)) } } + copyAsmURIs := func(dst *[]protocol.DocumentURI, src []string) { + for _, filename := range src { + if !strings.HasSuffix(filename, ".s") { + continue + } + *dst = append(*dst, protocol.URIFromPath(filename)) + } + } + copyAsmURIs(&mp.AsmFiles, pkg.OtherFiles) copyURIs(&mp.CompiledGoFiles, pkg.CompiledGoFiles) copyURIs(&mp.GoFiles, pkg.GoFiles) copyURIs(&mp.IgnoredFiles, pkg.IgnoredFiles) diff --git a/gopls/internal/cache/metadata/metadata.go b/gopls/internal/cache/metadata/metadata.go index 81b6dc57e1f..5408b5a1105 100644 --- a/gopls/internal/cache/metadata/metadata.go +++ b/gopls/internal/cache/metadata/metadata.go @@ -48,6 +48,7 @@ type Package struct { // These fields are as defined by go/packages.Package GoFiles []protocol.DocumentURI CompiledGoFiles []protocol.DocumentURI + AsmFiles []protocol.DocumentURI IgnoredFiles []protocol.DocumentURI OtherFiles []protocol.DocumentURI diff --git a/gopls/internal/cache/package.go b/gopls/internal/cache/package.go index 3477d522cee..370f0959389 100644 --- a/gopls/internal/cache/package.go +++ b/gopls/internal/cache/package.go @@ -19,6 +19,7 @@ import ( "golang.org/x/tools/gopls/internal/cache/testfuncs" "golang.org/x/tools/gopls/internal/cache/xrefs" "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/asm" ) // Convenient aliases for very heavily used types. @@ -49,6 +50,7 @@ type syntaxPackage struct { fset *token.FileSet // for now, same as the snapshot's FileSet goFiles []*parsego.File compiledGoFiles []*parsego.File + asmFiles []*asm.File diagnostics []*Diagnostic parseErrors []scanner.ErrorList typeErrors []types.Error diff --git a/gopls/internal/cache/parse.go b/gopls/internal/cache/parse.go index d733ca76799..0f41824d7ea 100644 --- a/gopls/internal/cache/parse.go +++ b/gopls/internal/cache/parse.go @@ -13,6 +13,7 @@ import ( "golang.org/x/tools/gopls/internal/cache/parsego" "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/util/asm" ) // ParseGo parses the file whose contents are provided by fh. @@ -43,3 +44,23 @@ func parseGoImpl(ctx context.Context, fset *token.FileSet, fh file.Handle, mode pgf, _ := parsego.Parse(ctx, fset, fh.URI(), content, mode, purgeFuncBodies) // ignore 'fixes' return pgf, nil } + +// parseAsmFiles pases the assembly files whose contents are provided by fhs. +func parseAsmFiles(ctx context.Context, fhs ...file.Handle) ([]*asm.File, error) { + pgfs := make([]*asm.File, len(fhs)) + + for i, fh := range fhs { + var err error + content, err := fh.Content() + if err != nil { + return nil, err + } + // Check for context cancellation before actually doing the parse. + if ctx.Err() != nil { + return nil, ctx.Err() + } + pgfs[i] = asm.Parse(content) + } + return pgfs, nil + +} From 48076194a3a1c2fda562e384f81089b5b3bcaf11 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Mon, 14 Apr 2025 08:05:12 +0800 Subject: [PATCH 02/11] gopls: add asmFiles in cache.Package The asmFiles field supports storing assembly files. Fixes golang/go#71754 --- gopls/internal/cache/load.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/gopls/internal/cache/load.go b/gopls/internal/cache/load.go index 956ba6d396f..b7d8d671168 100644 --- a/gopls/internal/cache/load.go +++ b/gopls/internal/cache/load.go @@ -454,15 +454,13 @@ func buildMetadata(updates map[PackageID]*metadata.Package, loadDir string, stan *dst = append(*dst, protocol.URIFromPath(filename)) } } - copyAsmURIs := func(dst *[]protocol.DocumentURI, src []string) { - for _, filename := range src { - if !strings.HasSuffix(filename, ".s") { - continue - } - *dst = append(*dst, protocol.URIFromPath(filename)) + // Copy SFiles to AsmFiles. + for _, filename := range pkg.OtherFiles { + if !strings.HasSuffix(filename, ".s") { + continue } + mp.AsmFiles = append(mp.AsmFiles, protocol.URIFromPath(filename)) } - copyAsmURIs(&mp.AsmFiles, pkg.OtherFiles) copyURIs(&mp.CompiledGoFiles, pkg.CompiledGoFiles) copyURIs(&mp.GoFiles, pkg.GoFiles) copyURIs(&mp.IgnoredFiles, pkg.IgnoredFiles) From f8deb4d6267c5d0b095f86a13a8de202b096ff61 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Thu, 24 Apr 2025 23:19:05 +0800 Subject: [PATCH 03/11] gopls: augmentation of the xrefs index to include cross-package references from assembly files. This commit enhances the xrefs index to include references from assembly (.s) files. It processes assembly files, extracts cross-package references, and updates the index. Updates golang/go#71754 --- gopls/internal/cache/metadata/metadata.go | 4 +- gopls/internal/cache/package.go | 2 +- gopls/internal/cache/parse.go | 10 ++--- gopls/internal/cache/xrefs/xrefs.go | 45 +++++++++++++++++++++-- gopls/internal/goasm/definition.go | 2 +- gopls/internal/util/asm/parse.go | 13 ++++++- gopls/internal/util/asm/parse_test.go | 2 +- 7 files changed, 62 insertions(+), 16 deletions(-) diff --git a/gopls/internal/cache/metadata/metadata.go b/gopls/internal/cache/metadata/metadata.go index 5408b5a1105..629d25852f6 100644 --- a/gopls/internal/cache/metadata/metadata.go +++ b/gopls/internal/cache/metadata/metadata.go @@ -48,10 +48,12 @@ type Package struct { // These fields are as defined by go/packages.Package GoFiles []protocol.DocumentURI CompiledGoFiles []protocol.DocumentURI - AsmFiles []protocol.DocumentURI IgnoredFiles []protocol.DocumentURI OtherFiles []protocol.DocumentURI + // These fields ares as defined by asm.File + AsmFiles []protocol.DocumentURI + ForTest PackagePath // q in a "p [q.test]" package, else "" TypesSizes types.Sizes Errors []packages.Error // must be set for packages in import cycles diff --git a/gopls/internal/cache/package.go b/gopls/internal/cache/package.go index 370f0959389..71b2a07e3f0 100644 --- a/gopls/internal/cache/package.go +++ b/gopls/internal/cache/package.go @@ -71,7 +71,7 @@ type syntaxPackage struct { func (p *syntaxPackage) xrefs() []byte { p.xrefsOnce.Do(func() { - p._xrefs = xrefs.Index(p.compiledGoFiles, p.types, p.typesInfo) + p._xrefs = xrefs.Index(p.compiledGoFiles, p.types, p.typesInfo, p.asmFiles) }) return p._xrefs } diff --git a/gopls/internal/cache/parse.go b/gopls/internal/cache/parse.go index 0f41824d7ea..ec94b182997 100644 --- a/gopls/internal/cache/parse.go +++ b/gopls/internal/cache/parse.go @@ -45,10 +45,9 @@ func parseGoImpl(ctx context.Context, fset *token.FileSet, fh file.Handle, mode return pgf, nil } -// parseAsmFiles pases the assembly files whose contents are provided by fhs. +// parseAsmFiles parses the assembly files whose contents are provided by fhs. func parseAsmFiles(ctx context.Context, fhs ...file.Handle) ([]*asm.File, error) { - pgfs := make([]*asm.File, len(fhs)) - + pafs := make([]*asm.File, len(fhs)) for i, fh := range fhs { var err error content, err := fh.Content() @@ -59,8 +58,7 @@ func parseAsmFiles(ctx context.Context, fhs ...file.Handle) ([]*asm.File, error) if ctx.Err() != nil { return nil, ctx.Err() } - pgfs[i] = asm.Parse(content) + pafs[i] = asm.Parse(fh.URI(), content) } - return pgfs, nil - + return pafs, nil } diff --git a/gopls/internal/cache/xrefs/xrefs.go b/gopls/internal/cache/xrefs/xrefs.go index d9b7051737a..33fda837af3 100644 --- a/gopls/internal/cache/xrefs/xrefs.go +++ b/gopls/internal/cache/xrefs/xrefs.go @@ -17,13 +17,15 @@ import ( "golang.org/x/tools/gopls/internal/cache/metadata" "golang.org/x/tools/gopls/internal/cache/parsego" "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/asm" "golang.org/x/tools/gopls/internal/util/bug" "golang.org/x/tools/gopls/internal/util/frob" + "golang.org/x/tools/gopls/internal/util/morestrings" ) // Index constructs a serializable index of outbound cross-references // for the specified type-checked package. -func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte { +func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles []*asm.File) []byte { // pkgObjects maps each referenced package Q to a mapping: // from each referenced symbol in Q to the ordered list // of references to that symbol from this package. @@ -70,7 +72,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte { // (e.g. local const/var/type). continue } - gobObj = &gobObject{Path: path} + gobObj = &gobObject{Path: path, isAsm: false} objects[obj] = gobObj } @@ -112,6 +114,37 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte { } } + for fileIndex, af := range asmFiles { + for _, id := range af.Idents { + _, name, ok := morestrings.CutLast(id.Name, ".") + if id.Kind != asm.Text { + continue + } + obj := pkg.Scope().Lookup(name) + if obj == nil { + continue + } + objects := getObjects(pkg) + gobObj, ok := objects[obj] + if !ok { + path, err := objectpathFor(obj) + if err != nil { + // Capitalized but not exported + // (e.g. local const/var/type). + continue + } + gobObj = &gobObject{Path: path, isAsm: true} + objects[obj] = gobObj + } + if rng, err := af.NodeRange(id); err == nil { + gobObj.Refs = append(gobObj.Refs, gobRef{ + FileIndex: fileIndex, + Range: rng, + }) + } + } + } + // Flatten the maps into slices, and sort for determinism. var packages []*gobPackage for p := range pkgObjects { @@ -148,6 +181,9 @@ func Lookup(mp *metadata.Package, data []byte, targets map[metadata.PackagePath] if _, ok := objectSet[gobObj.Path]; ok { for _, ref := range gobObj.Refs { uri := mp.CompiledGoFiles[ref.FileIndex] + if gobObj.isAsm { + uri = mp.AsmFiles[ref.FileIndex] + } locs = append(locs, protocol.Location{ URI: uri, Range: ref.Range, @@ -184,8 +220,9 @@ type gobPackage struct { // A gobObject records all references to a particular symbol. type gobObject struct { - Path objectpath.Path // symbol name within package; "" => import of package itself - Refs []gobRef // locations of references within P, in lexical order + Path objectpath.Path // symbol name within package; "" => import of package itself + Refs []gobRef // locations of references within P, in lexical order + isAsm bool // true if this is an assembly object } type gobRef struct { diff --git a/gopls/internal/goasm/definition.go b/gopls/internal/goasm/definition.go index 903916d265d..3eeb0bd7b9c 100644 --- a/gopls/internal/goasm/definition.go +++ b/gopls/internal/goasm/definition.go @@ -45,7 +45,7 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p // // TODO(adonovan): make this just another // attribute of the type-checked cache.Package. - file := asm.Parse(content) + file := asm.Parse(fh.URI(), content) // Figure out the selected symbol. // For now, just find the identifier around the cursor. diff --git a/gopls/internal/util/asm/parse.go b/gopls/internal/util/asm/parse.go index 11c59a7cc3d..448a26bc6d2 100644 --- a/gopls/internal/util/asm/parse.go +++ b/gopls/internal/util/asm/parse.go @@ -11,6 +11,8 @@ import ( "fmt" "strings" "unicode" + + "golang.org/x/tools/gopls/internal/protocol" ) // Kind describes the nature of an identifier in an assembly file. @@ -43,12 +45,19 @@ var kindString = [...]string{ // A file represents a parsed file of Go assembly language. type File struct { + URI protocol.DocumentURI Idents []Ident + Mapper *protocol.Mapper // may map fixed Src, not file content + // TODO(adonovan): use token.File? This may be important in a // future in which analyzers can report diagnostics in .s files. } +func (f *File) NodeRange(ident Ident) (protocol.Range, error) { + return f.Mapper.OffsetRange(ident.Offset, ident.End()) +} + // Ident represents an identifier in an assembly file. type Ident struct { Name string // symbol name (after correcting [·∕]); Name[0]='.' => current package @@ -61,7 +70,7 @@ func (id Ident) End() int { return id.Offset + len(id.Name) } // Parse extracts identifiers from Go assembly files. // Since it is a best-effort parser, it never returns an error. -func Parse(content []byte) *File { +func Parse(uri protocol.DocumentURI, content []byte) *File { var idents []Ident offset := 0 // byte offset of start of current line @@ -192,7 +201,7 @@ func Parse(content []byte) *File { _ = scan.Err() // ignore scan errors - return &File{Idents: idents} + return &File{Idents: idents, Mapper: protocol.NewMapper(uri, content), URI: uri} } // isIdent reports whether s is a valid Go assembly identifier. diff --git a/gopls/internal/util/asm/parse_test.go b/gopls/internal/util/asm/parse_test.go index 67a1286d28b..6d9bd7b8b93 100644 --- a/gopls/internal/util/asm/parse_test.go +++ b/gopls/internal/util/asm/parse_test.go @@ -39,7 +39,7 @@ TEXT ·g(SB),NOSPLIT,$0 `[1:]) const filename = "asm.s" m := protocol.NewMapper(protocol.URIFromPath(filename), src) - file := asm.Parse(src) + file := asm.Parse("", src) want := ` asm.s:5:6-11: data "hello" From a4113fcdbea08671556a675ee1fabafb73090338 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Sun, 11 May 2025 15:16:44 +0800 Subject: [PATCH 04/11] gopls: Implement reference jumping between Go functions and assembly functions. This commit modifies the addition of reference relationships in assembly files within the references file. Updates golang/go#71754 --- gopls/internal/cache/package.go | 4 ++++ gopls/internal/cache/xrefs/xrefs.go | 3 +++ gopls/internal/golang/references.go | 25 +++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/gopls/internal/cache/package.go b/gopls/internal/cache/package.go index 71b2a07e3f0..0c1c724308b 100644 --- a/gopls/internal/cache/package.go +++ b/gopls/internal/cache/package.go @@ -202,3 +202,7 @@ func (p *Package) ParseErrors() []scanner.ErrorList { func (p *Package) TypeErrors() []types.Error { return p.pkg.typeErrors } + +func (p *Package) AsmFiles() []*asm.File { + return p.pkg.asmFiles +} diff --git a/gopls/internal/cache/xrefs/xrefs.go b/gopls/internal/cache/xrefs/xrefs.go index 33fda837af3..52728d69100 100644 --- a/gopls/internal/cache/xrefs/xrefs.go +++ b/gopls/internal/cache/xrefs/xrefs.go @@ -117,6 +117,9 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles for fileIndex, af := range asmFiles { for _, id := range af.Idents { _, name, ok := morestrings.CutLast(id.Name, ".") + if !ok { + continue + } if id.Kind != asm.Text { continue } diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go index cf24685ca91..7b5afbfab1d 100644 --- a/gopls/internal/golang/references.go +++ b/gopls/internal/golang/references.go @@ -32,6 +32,8 @@ import ( "golang.org/x/tools/gopls/internal/cache/parsego" "golang.org/x/tools/gopls/internal/file" "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/asm" + "golang.org/x/tools/gopls/internal/util/morestrings" "golang.org/x/tools/gopls/internal/util/safetoken" "golang.org/x/tools/internal/event" ) @@ -610,6 +612,29 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo } } } + + for _, pgf := range pkg.AsmFiles() { + for _, id := range pgf.Idents { + _, name, ok := morestrings.CutLast(id.Name, ".") + if !ok { + continue + } + if id.Kind != asm.Text { + continue + } + obj := pkg.Types().Scope().Lookup(name) + if obj == nil { + continue + } + if rng, err := pgf.NodeRange(id); err == nil && matches(obj) { + asmLocation := protocol.Location{ + URI: pgf.URI, + Range: rng, + } + report(asmLocation, false) + } + } + } return nil } From 556b74148ab804fa491bb11143cef15fb79ada80 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Sun, 25 May 2025 13:19:28 +0800 Subject: [PATCH 05/11] gopls: implement reference support for goasm files This commit adds support for reference relationships in Go assembly (.goasm) files within the references file. It implements the logic to identify and record references in goasm file types, enabling reference lookups and navigation for Go assembly code. Updates golang/go#71754 --- gopls/internal/cache/package.go | 14 ++++ gopls/internal/goasm/references.go | 117 ++++++++++++++++++++++++++++ gopls/internal/golang/references.go | 2 +- gopls/internal/server/references.go | 3 + 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 gopls/internal/goasm/references.go diff --git a/gopls/internal/cache/package.go b/gopls/internal/cache/package.go index 0c1c724308b..09e3698a318 100644 --- a/gopls/internal/cache/package.go +++ b/gopls/internal/cache/package.go @@ -206,3 +206,17 @@ func (p *Package) TypeErrors() []types.Error { func (p *Package) AsmFiles() []*asm.File { return p.pkg.asmFiles } + +func (p *Package) AsmFile(uri protocol.DocumentURI) (*asm.File, error) { + return p.pkg.AsmFile(uri) +} + +func (pkg *syntaxPackage) AsmFile(uri protocol.DocumentURI) (*asm.File, error) { + for _, af := range pkg.asmFiles { + if af.URI == uri { + return af, nil + } + } + + return nil, fmt.Errorf("no parsed file for %s in %v", uri, pkg.id) +} diff --git a/gopls/internal/goasm/references.go b/gopls/internal/goasm/references.go new file mode 100644 index 00000000000..db72ba6512f --- /dev/null +++ b/gopls/internal/goasm/references.go @@ -0,0 +1,117 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package goasm provides language-server features for files in Go +// assembly language (https://go.dev/doc/asm). +package goasm + +import ( + "context" + "fmt" + "go/ast" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/asm" + "golang.org/x/tools/gopls/internal/util/morestrings" + "golang.org/x/tools/internal/event" +) + +func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Location, error) { + ctx, done := event.Start(ctx, "goasm.References") + defer done() + + pkg, asmFile, err := GetPackageID(ctx, snapshot, fh.URI()) + if err != nil { + return nil, err + } + + // Read the file. + content, err := fh.Content() + if err != nil { + return nil, err + } + mapper := protocol.NewMapper(fh.URI(), content) + offset, err := mapper.PositionOffset(position) + if err != nil { + return nil, err + } + + // // Parse the assembly. + // file := asm.Parse(fh.URI(), content) + + // Figure out the selected symbol. + // For now, just find the identifier around the cursor. + var found *asm.Ident + for _, id := range asmFile.Idents { + if id.Offset <= offset && offset <= id.End() { + found = &id + break + } + } + if found == nil { + return nil, fmt.Errorf("not an identifier") + } + + sym := found.Name + var locations []protocol.Location + _, name, ok := morestrings.CutLast(sym, ".") + if !ok { + return nil, fmt.Errorf("not found") + } + // return localReferences(pkg, targets, true, report) + for _, pgf := range pkg.CompiledGoFiles() { + for curId := range pgf.Cursor.Preorder((*ast.Ident)(nil)) { + id := curId.Node().(*ast.Ident) + if id.Name == name { + loc, err := pgf.NodeLocation(id) + if err != nil { + return nil, err + } + locations = append(locations, loc) + } + } + } + + for _, asmFile := range pkg.AsmFiles() { + for _, id := range asmFile.Idents { + if id.Name != sym { + continue + } + if rng, err := asmFile.NodeRange(id); err == nil { + asmLocation := protocol.Location{ + URI: asmFile.URI, + Range: rng, + } + locations = append(locations, asmLocation) + } + } + } + + return locations, nil +} + +func GetPackageID(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) (*cache.Package, *asm.File, error) { + mps, err := snapshot.MetadataForFile(ctx, uri) + if err != nil { + return nil, nil, err + } + metadata.RemoveIntermediateTestVariants(&mps) + if len(mps) == 0 { + return nil, nil, fmt.Errorf("no package metadata for file %s", uri) + } + mp := mps[0] + pkgs, err := snapshot.TypeCheck(ctx, mp.ID) + if err != nil { + return nil, nil, err + } + pkg := pkgs[0] + asmFile, err := pkg.AsmFile(uri) + if err != nil { + return nil, nil, err // "can't happen" + } + return pkg, asmFile, nil +} diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go index 7b5afbfab1d..5688db4d317 100644 --- a/gopls/internal/golang/references.go +++ b/gopls/internal/golang/references.go @@ -631,7 +631,7 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo URI: pgf.URI, Range: rng, } - report(asmLocation, false) + report(asmLocation, true) } } } diff --git a/gopls/internal/server/references.go b/gopls/internal/server/references.go index 8a01e96498b..392abc5877a 100644 --- a/gopls/internal/server/references.go +++ b/gopls/internal/server/references.go @@ -8,6 +8,7 @@ import ( "context" "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/goasm" "golang.org/x/tools/gopls/internal/golang" "golang.org/x/tools/gopls/internal/label" "golang.org/x/tools/gopls/internal/protocol" @@ -35,6 +36,8 @@ func (s *server) References(ctx context.Context, params *protocol.ReferenceParam return template.References(ctx, snapshot, fh, params) case file.Go: return golang.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration) + case file.Asm: + return goasm.References(ctx, snapshot, fh, params.Position) } return nil, nil // empty result } From d376e740b75157d3298964b37ba3b3c5bee0f0ed Mon Sep 17 00:00:00 2001 From: groot-guo Date: Wed, 28 May 2025 23:00:48 +0800 Subject: [PATCH 06/11] gopls: Add test cases for references in assembly and Go files Improve code comments for better readability.Remove type checking logic from assembly file reference lookup.Update xrefs index retrieval for consistency Updates golang/go#71754 --- gopls/internal/cache/metadata/metadata.go | 3 +- gopls/internal/cache/package.go | 2 +- gopls/internal/cache/xrefs/xrefs.go | 25 +++----- gopls/internal/goasm/references.go | 59 ++++++++----------- gopls/internal/golang/references.go | 7 +-- gopls/internal/server/references.go | 2 +- .../marker/testdata/references/asm_ref.txt | 25 ++++++++ gopls/internal/util/asm/parse.go | 4 +- 8 files changed, 65 insertions(+), 62 deletions(-) create mode 100644 gopls/internal/test/marker/testdata/references/asm_ref.txt diff --git a/gopls/internal/cache/metadata/metadata.go b/gopls/internal/cache/metadata/metadata.go index 629d25852f6..2e0eb82b81a 100644 --- a/gopls/internal/cache/metadata/metadata.go +++ b/gopls/internal/cache/metadata/metadata.go @@ -51,8 +51,7 @@ type Package struct { IgnoredFiles []protocol.DocumentURI OtherFiles []protocol.DocumentURI - // These fields ares as defined by asm.File - AsmFiles []protocol.DocumentURI + AsmFiles []protocol.DocumentURI // *.s subset of OtherFiles ForTest PackagePath // q in a "p [q.test]" package, else "" TypesSizes types.Sizes diff --git a/gopls/internal/cache/package.go b/gopls/internal/cache/package.go index 09e3698a318..da12ec7989a 100644 --- a/gopls/internal/cache/package.go +++ b/gopls/internal/cache/package.go @@ -215,7 +215,7 @@ func (pkg *syntaxPackage) AsmFile(uri protocol.DocumentURI) (*asm.File, error) { for _, af := range pkg.asmFiles { if af.URI == uri { return af, nil - } + } } return nil, fmt.Errorf("no parsed file for %s in %v", uri, pkg.id) diff --git a/gopls/internal/cache/xrefs/xrefs.go b/gopls/internal/cache/xrefs/xrefs.go index 52728d69100..7760a5329d2 100644 --- a/gopls/internal/cache/xrefs/xrefs.go +++ b/gopls/internal/cache/xrefs/xrefs.go @@ -11,6 +11,7 @@ package xrefs import ( "go/ast" "go/types" + "slices" "sort" "golang.org/x/tools/go/types/objectpath" @@ -72,7 +73,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles // (e.g. local const/var/type). continue } - gobObj = &gobObject{Path: path, isAsm: false} + gobObj = &gobObject{Path: path} objects[obj] = gobObj } @@ -114,15 +115,13 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles } } + // For each asm file, record references to identifiers. for fileIndex, af := range asmFiles { for _, id := range af.Idents { _, name, ok := morestrings.CutLast(id.Name, ".") if !ok { continue } - if id.Kind != asm.Text { - continue - } obj := pkg.Scope().Lookup(name) if obj == nil { continue @@ -130,13 +129,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles objects := getObjects(pkg) gobObj, ok := objects[obj] if !ok { - path, err := objectpathFor(obj) - if err != nil { - // Capitalized but not exported - // (e.g. local const/var/type). - continue - } - gobObj = &gobObject{Path: path, isAsm: true} + gobObj = &gobObject{Path: objectpath.Path(obj.Name())} objects[obj] = gobObj } if rng, err := af.NodeRange(id); err == nil { @@ -183,10 +176,7 @@ func Lookup(mp *metadata.Package, data []byte, targets map[metadata.PackagePath] for _, gobObj := range gp.Objects { if _, ok := objectSet[gobObj.Path]; ok { for _, ref := range gobObj.Refs { - uri := mp.CompiledGoFiles[ref.FileIndex] - if gobObj.isAsm { - uri = mp.AsmFiles[ref.FileIndex] - } + uri := slices.Concat(mp.CompiledGoFiles, mp.AsmFiles)[ref.FileIndex] locs = append(locs, protocol.Location{ URI: uri, Range: ref.Range, @@ -223,9 +213,8 @@ type gobPackage struct { // A gobObject records all references to a particular symbol. type gobObject struct { - Path objectpath.Path // symbol name within package; "" => import of package itself - Refs []gobRef // locations of references within P, in lexical order - isAsm bool // true if this is an assembly object + Path objectpath.Path // symbol name within package; "" => import of package itself + Refs []gobRef // locations of references within P, in lexical order } type gobRef struct { diff --git a/gopls/internal/goasm/references.go b/gopls/internal/goasm/references.go index db72ba6512f..58f2bd8c269 100644 --- a/gopls/internal/goasm/references.go +++ b/gopls/internal/goasm/references.go @@ -20,29 +20,36 @@ import ( "golang.org/x/tools/internal/event" ) -func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Location, error) { +// References returns a list of locations (file and position) where the symbol under the cursor in an assembly file is referenced, +// including both Go source files and assembly files within the same package. +func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position, includeDeclaration bool) ([]protocol.Location, error) { ctx, done := event.Start(ctx, "goasm.References") defer done() - pkg, asmFile, err := GetPackageID(ctx, snapshot, fh.URI()) + mps, err := snapshot.MetadataForFile(ctx, fh.URI()) if err != nil { return nil, err } - - // Read the file. - content, err := fh.Content() + metadata.RemoveIntermediateTestVariants(&mps) + if len(mps) == 0 { + return nil, fmt.Errorf("no package metadata for file %s", fh.URI()) + } + mp := mps[0] + pkgs, err := snapshot.TypeCheck(ctx, mp.ID) if err != nil { return nil, err } - mapper := protocol.NewMapper(fh.URI(), content) - offset, err := mapper.PositionOffset(position) + pkg := pkgs[0] + asmFile, err := pkg.AsmFile(fh.URI()) + if err != nil { + return nil, err // "can't happen" + } + + offset, err := asmFile.Mapper.PositionOffset(position) if err != nil { return nil, err } - // // Parse the assembly. - // file := asm.Parse(fh.URI(), content) - // Figure out the selected symbol. // For now, just find the identifier around the cursor. var found *asm.Ident @@ -62,7 +69,10 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p if !ok { return nil, fmt.Errorf("not found") } - // return localReferences(pkg, targets, true, report) + + // TODO(grootguo): Currently, only references to the symbol within the package are found (i.e., only Idents in this package's Go files are searched). + // It is still necessary to implement cross-package reference lookup: that is, to find all references to this symbol in other packages that import the current package. + // Refer to the global search logic in golang.References, and add corresponding test cases for verification. for _, pgf := range pkg.CompiledGoFiles() { for curId := range pgf.Cursor.Preorder((*ast.Ident)(nil)) { id := curId.Node().(*ast.Ident) @@ -76,6 +86,11 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p } } + // If includeDeclaration is false, return only reference locations (exclude declarations). + if !includeDeclaration { + return locations, nil + } + for _, asmFile := range pkg.AsmFiles() { for _, id := range asmFile.Idents { if id.Name != sym { @@ -93,25 +108,3 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p return locations, nil } - -func GetPackageID(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) (*cache.Package, *asm.File, error) { - mps, err := snapshot.MetadataForFile(ctx, uri) - if err != nil { - return nil, nil, err - } - metadata.RemoveIntermediateTestVariants(&mps) - if len(mps) == 0 { - return nil, nil, fmt.Errorf("no package metadata for file %s", uri) - } - mp := mps[0] - pkgs, err := snapshot.TypeCheck(ctx, mp.ID) - if err != nil { - return nil, nil, err - } - pkg := pkgs[0] - asmFile, err := pkg.AsmFile(uri) - if err != nil { - return nil, nil, err // "can't happen" - } - return pkg, asmFile, nil -} diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go index 5688db4d317..7ce64e995dc 100644 --- a/gopls/internal/golang/references.go +++ b/gopls/internal/golang/references.go @@ -32,7 +32,6 @@ import ( "golang.org/x/tools/gopls/internal/cache/parsego" "golang.org/x/tools/gopls/internal/file" "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/asm" "golang.org/x/tools/gopls/internal/util/morestrings" "golang.org/x/tools/gopls/internal/util/safetoken" "golang.org/x/tools/internal/event" @@ -613,15 +612,13 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo } } + // Iterate over all assembly files and find all references to the target object. for _, pgf := range pkg.AsmFiles() { for _, id := range pgf.Idents { _, name, ok := morestrings.CutLast(id.Name, ".") if !ok { continue } - if id.Kind != asm.Text { - continue - } obj := pkg.Types().Scope().Lookup(name) if obj == nil { continue @@ -631,7 +628,7 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo URI: pgf.URI, Range: rng, } - report(asmLocation, true) + report(asmLocation, false) } } } diff --git a/gopls/internal/server/references.go b/gopls/internal/server/references.go index 392abc5877a..3a852ae7c05 100644 --- a/gopls/internal/server/references.go +++ b/gopls/internal/server/references.go @@ -37,7 +37,7 @@ func (s *server) References(ctx context.Context, params *protocol.ReferenceParam case file.Go: return golang.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration) case file.Asm: - return goasm.References(ctx, snapshot, fh, params.Position) + return goasm.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration) } return nil, nil // empty result } diff --git a/gopls/internal/test/marker/testdata/references/asm_ref.txt b/gopls/internal/test/marker/testdata/references/asm_ref.txt new file mode 100644 index 00000000000..564834a8d70 --- /dev/null +++ b/gopls/internal/test/marker/testdata/references/asm_ref.txt @@ -0,0 +1,25 @@ + +-- go.mod -- +module example.com +go 1.24 + +-- foo/foo.go -- +package foo + +func Add(a, b int) int //@ loc(use, "Add"), refs("Add", use, def) +func sub(a, b int) int //@ loc(useSub, "sub"), refs("sub", useSub, defSub, refSub) +var _ = sub //@loc(refSub, "sub"), refs("sub", useSub, defSub, refSub) + +-- foo/foo.s -- +// portable assembly +#include "textflag.h" + +TEXT ·Add(SB), NOSPLIT, $0-24 //@ loc(def, "Add"), refs("Add", def, use) + MOVQ a+0(FP), AX + ADDQ b+8(FP), AX + RET + +TEXT ·sub(SB), NOSPLIT, $0-24 //@ loc(defSub, "sub"), refs("sub", defSub, useSub, refSub) + MOVQ a+0(FP), AX + SUBQ b+8(FP), AX + RET diff --git a/gopls/internal/util/asm/parse.go b/gopls/internal/util/asm/parse.go index 448a26bc6d2..16e7549059a 100644 --- a/gopls/internal/util/asm/parse.go +++ b/gopls/internal/util/asm/parse.go @@ -48,14 +48,14 @@ type File struct { URI protocol.DocumentURI Idents []Ident - Mapper *protocol.Mapper // may map fixed Src, not file content + Mapper *protocol.Mapper // TODO(adonovan): use token.File? This may be important in a // future in which analyzers can report diagnostics in .s files. } func (f *File) NodeRange(ident Ident) (protocol.Range, error) { - return f.Mapper.OffsetRange(ident.Offset, ident.End()) + return f.Mapper.OffsetRange(ident.Offset+2, ident.End()+1) } // Ident represents an identifier in an assembly file. From 18ea5403bf32e8e4fdce89f86dddb527a33127c0 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Tue, 3 Jun 2025 22:00:07 +0800 Subject: [PATCH 07/11] gopls: Add TODO for handling cross-package references and refine local reference matching logic Updated the code in xrefs.go and references.go. Updates golang/go#71754 --- gopls/internal/cache/xrefs/xrefs.go | 2 ++ gopls/internal/golang/references.go | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gopls/internal/cache/xrefs/xrefs.go b/gopls/internal/cache/xrefs/xrefs.go index 7760a5329d2..6b1791b24a8 100644 --- a/gopls/internal/cache/xrefs/xrefs.go +++ b/gopls/internal/cache/xrefs/xrefs.go @@ -124,6 +124,8 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles } obj := pkg.Scope().Lookup(name) if obj == nil { + // TODO(grootguo): If the object is not found in the current package, + // consider handling cross-package references. continue } objects := getObjects(pkg) diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go index 7ce64e995dc..ae6747ed801 100644 --- a/gopls/internal/golang/references.go +++ b/gopls/internal/golang/references.go @@ -623,7 +623,10 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo if obj == nil { continue } - if rng, err := pgf.NodeRange(id); err == nil && matches(obj) { + if !matches(obj) { + continue + } + if rng, err := pgf.NodeRange(id); err == nil { asmLocation := protocol.Location{ URI: pgf.URI, Range: rng, From 85033594399438ec98e7185392c3deff6369aa28 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Wed, 4 Jun 2025 20:39:30 +0800 Subject: [PATCH 08/11] gopls: Refactor goasm.References function. Leveraged Defs and Uses mappings from types.Info to efficiently find references in Go files. Updates golang/go#71754 --- gopls/internal/goasm/references.go | 37 +++++++++++++------ .../test/marker/testdata/references/asm.txt | 36 ++++++++++++++++++ .../marker/testdata/references/asm_ref.txt | 25 ------------- gopls/internal/util/asm/parse.go | 5 +++ 4 files changed, 67 insertions(+), 36 deletions(-) create mode 100644 gopls/internal/test/marker/testdata/references/asm.txt delete mode 100644 gopls/internal/test/marker/testdata/references/asm_ref.txt diff --git a/gopls/internal/goasm/references.go b/gopls/internal/goasm/references.go index 58f2bd8c269..f7a09ca0fd3 100644 --- a/gopls/internal/goasm/references.go +++ b/gopls/internal/goasm/references.go @@ -10,6 +10,7 @@ import ( "context" "fmt" "go/ast" + "go/types" "golang.org/x/tools/gopls/internal/cache" "golang.org/x/tools/gopls/internal/cache/metadata" @@ -73,16 +74,34 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p // TODO(grootguo): Currently, only references to the symbol within the package are found (i.e., only Idents in this package's Go files are searched). // It is still necessary to implement cross-package reference lookup: that is, to find all references to this symbol in other packages that import the current package. // Refer to the global search logic in golang.References, and add corresponding test cases for verification. + obj := pkg.Types().Scope().Lookup(name) + matches := func(curObj types.Object) bool { + if curObj == nil { + return false + } + if curObj.Name() != obj.Name() { + return false + } + return true + } for _, pgf := range pkg.CompiledGoFiles() { for curId := range pgf.Cursor.Preorder((*ast.Ident)(nil)) { id := curId.Node().(*ast.Ident) - if id.Name == name { - loc, err := pgf.NodeLocation(id) - if err != nil { - return nil, err + curObj, ok := pkg.TypesInfo().Defs[id] + if !ok { + curObj, ok = pkg.TypesInfo().Uses[id] + if !ok { + continue } - locations = append(locations, loc) } + if !matches(curObj) { + continue + } + loc, err := pgf.NodeLocation(id) + if err != nil { + return nil, err + } + locations = append(locations, loc) } } @@ -96,12 +115,8 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p if id.Name != sym { continue } - if rng, err := asmFile.NodeRange(id); err == nil { - asmLocation := protocol.Location{ - URI: asmFile.URI, - Range: rng, - } - locations = append(locations, asmLocation) + if loc, err := asmFile.NodeLocation(id); err == nil { + locations = append(locations, loc) } } } diff --git a/gopls/internal/test/marker/testdata/references/asm.txt b/gopls/internal/test/marker/testdata/references/asm.txt new file mode 100644 index 00000000000..1bdfad56cb8 --- /dev/null +++ b/gopls/internal/test/marker/testdata/references/asm.txt @@ -0,0 +1,36 @@ +This test validates the References request functionality in Go assembly files. + +It ensures that references to both exported (`Add`) and unexported (`sub`) functions are correctly identified across Go and assembly files. The test covers: +- Locating the definition of functions in both Go and assembly files. +- Identifying all references to the functions (`Add` and `sub`) within the Go and assembly files. + +The test includes: +- `Add`: An exported function with references in both Go and assembly files. +- `sub`: An unexported function with references in both Go and assembly files, including a usage in Go code (`var _ = sub`). + +The assembly file demonstrates portable assembly syntax and verifies cross-file reference handling. + +-- go.mod -- +module example.com +go 1.24 + +-- foo/foo.go -- +package foo + +func Add(a, b int) int //@ loc(use, "Add"), refs("Add", use, def) +func sub(a, b int) int //@ loc(useSub, "sub"), refs("sub", useSub, defSub, refSub) +var _ = sub //@loc(refSub, "sub"), refs("sub", useSub, defSub, refSub) + +-- foo/foo.s -- +// portable assembly +#include "textflag.h" + +TEXT ·Add(SB), NOSPLIT, $0-24 //@ loc(def, "Add"), refs("Add", def, use) + MOVQ a+0(FP), AX + ADDQ b+8(FP), AX + RET + +TEXT ·sub(SB), NOSPLIT, $0-24 //@ loc(defSub, "sub"), refs("sub", defSub, useSub, refSub) + MOVQ a+0(FP), AX + SUBQ b+8(FP), AX + RET diff --git a/gopls/internal/test/marker/testdata/references/asm_ref.txt b/gopls/internal/test/marker/testdata/references/asm_ref.txt deleted file mode 100644 index 564834a8d70..00000000000 --- a/gopls/internal/test/marker/testdata/references/asm_ref.txt +++ /dev/null @@ -1,25 +0,0 @@ - --- go.mod -- -module example.com -go 1.24 - --- foo/foo.go -- -package foo - -func Add(a, b int) int //@ loc(use, "Add"), refs("Add", use, def) -func sub(a, b int) int //@ loc(useSub, "sub"), refs("sub", useSub, defSub, refSub) -var _ = sub //@loc(refSub, "sub"), refs("sub", useSub, defSub, refSub) - --- foo/foo.s -- -// portable assembly -#include "textflag.h" - -TEXT ·Add(SB), NOSPLIT, $0-24 //@ loc(def, "Add"), refs("Add", def, use) - MOVQ a+0(FP), AX - ADDQ b+8(FP), AX - RET - -TEXT ·sub(SB), NOSPLIT, $0-24 //@ loc(defSub, "sub"), refs("sub", defSub, useSub, refSub) - MOVQ a+0(FP), AX - SUBQ b+8(FP), AX - RET diff --git a/gopls/internal/util/asm/parse.go b/gopls/internal/util/asm/parse.go index 16e7549059a..dc43c5f783a 100644 --- a/gopls/internal/util/asm/parse.go +++ b/gopls/internal/util/asm/parse.go @@ -58,6 +58,11 @@ func (f *File) NodeRange(ident Ident) (protocol.Range, error) { return f.Mapper.OffsetRange(ident.Offset+2, ident.End()+1) } +// NodeLocation returns a protocol Location for the ast.Node interval in this file. +func (f *File) NodeLocation(ident Ident) (protocol.Location, error) { + return f.Mapper.OffsetLocation(ident.Offset+2, ident.End()+1) +} + // Ident represents an identifier in an assembly file. type Ident struct { Name string // symbol name (after correcting [·∕]); Name[0]='.' => current package From f47a913459eb84887b7f76c398fc45f1b668b5aa Mon Sep 17 00:00:00 2001 From: groot-guo Date: Wed, 25 Jun 2025 23:16:51 +0800 Subject: [PATCH 09/11] gopls: Refactor goasm.References function. Modify the file index logic for asmfile in xrefs.go. Adjust the overall code logic flow in references. When parsing asm files, add the original symbol name length, return the original position, and update the test files. Updates golang/go#71754 --- gopls/internal/cache/xrefs/xrefs.go | 20 +++++++-- gopls/internal/goasm/references.go | 42 ++++++------------- gopls/internal/golang/references.go | 4 ++ .../test/marker/testdata/references/asm.txt | 4 +- gopls/internal/util/asm/parse.go | 25 ++++++----- 5 files changed, 49 insertions(+), 46 deletions(-) diff --git a/gopls/internal/cache/xrefs/xrefs.go b/gopls/internal/cache/xrefs/xrefs.go index 6b1791b24a8..84d84b864d3 100644 --- a/gopls/internal/cache/xrefs/xrefs.go +++ b/gopls/internal/cache/xrefs/xrefs.go @@ -11,7 +11,6 @@ package xrefs import ( "go/ast" "go/types" - "slices" "sort" "golang.org/x/tools/go/types/objectpath" @@ -118,6 +117,9 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles // For each asm file, record references to identifiers. for fileIndex, af := range asmFiles { for _, id := range af.Idents { + if id.Kind != asm.Data && id.Kind != asm.Text { + continue + } _, name, ok := morestrings.CutLast(id.Name, ".") if !ok { continue @@ -131,6 +133,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles objects := getObjects(pkg) gobObj, ok := objects[obj] if !ok { + // obj is a package-level symbol, so its objectpath is just its name. gobObj = &gobObject{Path: objectpath.Path(obj.Name())} objects[obj] = gobObj } @@ -171,14 +174,25 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles // to any object in the target set. Each object is denoted by a pair // of (package path, object path). func Lookup(mp *metadata.Package, data []byte, targets map[metadata.PackagePath]map[objectpath.Path]struct{}) (locs []protocol.Location) { - var packages []*gobPackage + var ( + packages []*gobPackage + goFilesLen = len(mp.CompiledGoFiles) + goAsmFilesLen = len(mp.AsmFiles) + ) packageCodec.Decode(data, &packages) for _, gp := range packages { if objectSet, ok := targets[gp.PkgPath]; ok { for _, gobObj := range gp.Objects { if _, ok := objectSet[gobObj.Path]; ok { for _, ref := range gobObj.Refs { - uri := slices.Concat(mp.CompiledGoFiles, mp.AsmFiles)[ref.FileIndex] + var uri protocol.DocumentURI + if ref.FileIndex < goFilesLen { + uri = mp.CompiledGoFiles[ref.FileIndex] + } else if ref.FileIndex < goFilesLen+goAsmFilesLen { + uri = mp.AsmFiles[ref.FileIndex] + } else { + continue // out of bounds + } locs = append(locs, protocol.Location{ URI: uri, Range: ref.Range, diff --git a/gopls/internal/goasm/references.go b/gopls/internal/goasm/references.go index f7a09ca0fd3..22bd718fd2c 100644 --- a/gopls/internal/goasm/references.go +++ b/gopls/internal/goasm/references.go @@ -64,9 +64,8 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p return nil, fmt.Errorf("not an identifier") } - sym := found.Name var locations []protocol.Location - _, name, ok := morestrings.CutLast(sym, ".") + _, name, ok := morestrings.CutLast(found.Name, ".") if !ok { return nil, fmt.Errorf("not found") } @@ -76,25 +75,12 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p // Refer to the global search logic in golang.References, and add corresponding test cases for verification. obj := pkg.Types().Scope().Lookup(name) matches := func(curObj types.Object) bool { - if curObj == nil { - return false - } - if curObj.Name() != obj.Name() { - return false - } - return true + return curObj != nil && curObj.Name() == obj.Name() } for _, pgf := range pkg.CompiledGoFiles() { for curId := range pgf.Cursor.Preorder((*ast.Ident)(nil)) { id := curId.Node().(*ast.Ident) - curObj, ok := pkg.TypesInfo().Defs[id] - if !ok { - curObj, ok = pkg.TypesInfo().Uses[id] - if !ok { - continue - } - } - if !matches(curObj) { + if !matches(pkg.TypesInfo().ObjectOf(id)) { continue } loc, err := pgf.NodeLocation(id) @@ -105,21 +91,17 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p } } - // If includeDeclaration is false, return only reference locations (exclude declarations). - if !includeDeclaration { - return locations, nil - } - - for _, asmFile := range pkg.AsmFiles() { - for _, id := range asmFile.Idents { - if id.Name != sym { - continue - } - if loc, err := asmFile.NodeLocation(id); err == nil { - locations = append(locations, loc) + if includeDeclaration { + for _, asmFile := range pkg.AsmFiles() { + for _, id := range asmFile.Idents { + if id.Name == found.Name && + (id.Kind == asm.Text || id.Kind == asm.Global || id.Kind == asm.Label) { + if loc, err := asmFile.NodeLocation(id); err == nil { + locations = append(locations, loc) + } + } } } } - return locations, nil } diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go index ae6747ed801..9b4ae045991 100644 --- a/gopls/internal/golang/references.go +++ b/gopls/internal/golang/references.go @@ -32,6 +32,7 @@ import ( "golang.org/x/tools/gopls/internal/cache/parsego" "golang.org/x/tools/gopls/internal/file" "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/asm" "golang.org/x/tools/gopls/internal/util/morestrings" "golang.org/x/tools/gopls/internal/util/safetoken" "golang.org/x/tools/internal/event" @@ -615,6 +616,9 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo // Iterate over all assembly files and find all references to the target object. for _, pgf := range pkg.AsmFiles() { for _, id := range pgf.Idents { + if id.Kind != asm.Data && id.Kind != asm.Text { + continue + } _, name, ok := morestrings.CutLast(id.Name, ".") if !ok { continue diff --git a/gopls/internal/test/marker/testdata/references/asm.txt b/gopls/internal/test/marker/testdata/references/asm.txt index 1bdfad56cb8..07cc4a46034 100644 --- a/gopls/internal/test/marker/testdata/references/asm.txt +++ b/gopls/internal/test/marker/testdata/references/asm.txt @@ -25,12 +25,12 @@ var _ = sub //@loc(refSub, "sub"), refs("sub", useSub, defSub, refSub) // portable assembly #include "textflag.h" -TEXT ·Add(SB), NOSPLIT, $0-24 //@ loc(def, "Add"), refs("Add", def, use) +TEXT ·Add(SB), NOSPLIT, $0-24 //@ loc(def, "·Add"), refs("Add", def, use) MOVQ a+0(FP), AX ADDQ b+8(FP), AX RET -TEXT ·sub(SB), NOSPLIT, $0-24 //@ loc(defSub, "sub"), refs("sub", defSub, useSub, refSub) +TEXT ·sub(SB), NOSPLIT, $0-24 //@ loc(defSub, "·sub"), refs("sub", defSub, useSub, refSub) MOVQ a+0(FP), AX SUBQ b+8(FP), AX RET diff --git a/gopls/internal/util/asm/parse.go b/gopls/internal/util/asm/parse.go index dc43c5f783a..5437c95ff0f 100644 --- a/gopls/internal/util/asm/parse.go +++ b/gopls/internal/util/asm/parse.go @@ -55,19 +55,20 @@ type File struct { } func (f *File) NodeRange(ident Ident) (protocol.Range, error) { - return f.Mapper.OffsetRange(ident.Offset+2, ident.End()+1) + return f.Mapper.OffsetRange(ident.Offset, ident.Offset+ident.OrigLen) } // NodeLocation returns a protocol Location for the ast.Node interval in this file. func (f *File) NodeLocation(ident Ident) (protocol.Location, error) { - return f.Mapper.OffsetLocation(ident.Offset+2, ident.End()+1) + return f.Mapper.OffsetLocation(ident.Offset, ident.Offset+ident.OrigLen) } // Ident represents an identifier in an assembly file. type Ident struct { - Name string // symbol name (after correcting [·∕]); Name[0]='.' => current package - Offset int // zero-based byte offset - Kind Kind + Name string // symbol name (after correcting [·∕]); Name[0]='.' => current package + Offset int // zero-based byte offset + OrigLen int // original length of the symbol name (before cleanup) + Kind Kind } // End returns the identifier's end offset. @@ -136,9 +137,10 @@ func Parse(uri protocol.DocumentURI, content []byte) *File { if isIdent(sym) { // (The Index call assumes sym is not itself "TEXT" etc.) idents = append(idents, Ident{ - Name: cleanup(sym), - Kind: kind, - Offset: offset + strings.Index(line, sym), + Name: cleanup(sym), + Kind: kind, + Offset: offset + strings.Index(line, sym), + OrigLen: len(sym), }) } continue @@ -196,9 +198,10 @@ func Parse(uri protocol.DocumentURI, content []byte) *File { sym = cutBefore(sym, "<") // "sym" =>> "sym" if isIdent(sym) { idents = append(idents, Ident{ - Name: cleanup(sym), - Kind: Ref, - Offset: offset + tokenPos, + Name: cleanup(sym), + Kind: Ref, + Offset: offset + tokenPos, + OrigLen: len(sym), }) } } From 3ba13a60428020148fbca38d28f3acf719584a75 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Fri, 27 Jun 2025 20:15:32 +0800 Subject: [PATCH 10/11] gopls: Refactor xrefs.go Lookup function. Modify the logic for distinguishing between asm files and Go files. Add test examples. Updates golang/go#71754 --- gopls/internal/cache/xrefs/xrefs.go | 12 ++++-------- .../internal/test/marker/testdata/references/asm.txt | 5 +++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/gopls/internal/cache/xrefs/xrefs.go b/gopls/internal/cache/xrefs/xrefs.go index 84d84b864d3..1215ac3fc85 100644 --- a/gopls/internal/cache/xrefs/xrefs.go +++ b/gopls/internal/cache/xrefs/xrefs.go @@ -175,9 +175,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles // of (package path, object path). func Lookup(mp *metadata.Package, data []byte, targets map[metadata.PackagePath]map[objectpath.Path]struct{}) (locs []protocol.Location) { var ( - packages []*gobPackage - goFilesLen = len(mp.CompiledGoFiles) - goAsmFilesLen = len(mp.AsmFiles) + packages []*gobPackage ) packageCodec.Decode(data, &packages) for _, gp := range packages { @@ -186,12 +184,10 @@ func Lookup(mp *metadata.Package, data []byte, targets map[metadata.PackagePath] if _, ok := objectSet[gobObj.Path]; ok { for _, ref := range gobObj.Refs { var uri protocol.DocumentURI - if ref.FileIndex < goFilesLen { + if asmIndex := ref.FileIndex - len(mp.CompiledGoFiles); asmIndex < 0 { uri = mp.CompiledGoFiles[ref.FileIndex] - } else if ref.FileIndex < goFilesLen+goAsmFilesLen { - uri = mp.AsmFiles[ref.FileIndex] } else { - continue // out of bounds + uri = mp.AsmFiles[asmIndex] } locs = append(locs, protocol.Location{ URI: uri, @@ -234,6 +230,6 @@ type gobObject struct { } type gobRef struct { - FileIndex int // index of enclosing file within P's CompiledGoFiles + FileIndex int // index of enclosing file within P's CompiledGoFiles + AsmFiles Range protocol.Range // source range of reference } diff --git a/gopls/internal/test/marker/testdata/references/asm.txt b/gopls/internal/test/marker/testdata/references/asm.txt index 07cc4a46034..ef5e0216135 100644 --- a/gopls/internal/test/marker/testdata/references/asm.txt +++ b/gopls/internal/test/marker/testdata/references/asm.txt @@ -34,3 +34,8 @@ TEXT ·sub(SB), NOSPLIT, $0-24 //@ loc(defSub, "·sub"), refs("sub", defSub, use MOVQ a+0(FP), AX SUBQ b+8(FP), AX RET + +TEXT ·AddAndSub(SB), NOSPLIT, $0-24 + CALL ·Add(SB) + CALL ·sub(SB) + RET From 9ce01585ac9c632046ad8ebd67d75eb11ecbeb56 Mon Sep 17 00:00:00 2001 From: groot-guo Date: Thu, 3 Jul 2025 23:27:34 +0800 Subject: [PATCH 11/11] gopls: Update the condition for assembly reference checks and add test cases. Update the condition for identifying assembly files to allow only ref and data types. Remove Go definition references from the goasm reference process. Add test files for different types of reference relationships between asm and Go. Updates golang/go#71754 --- gopls/internal/cache/xrefs/xrefs.go | 2 +- gopls/internal/goasm/references.go | 21 +++++++++------ gopls/internal/golang/references.go | 2 +- .../test/marker/testdata/references/asm.txt | 18 ++++++------- .../marker/testdata/references/asm_data.txt | 17 ++++++++++++ .../marker/testdata/references/asm_label.txt | 10 +++++++ .../marker/testdata/references/asm_ref.txt | 26 +++++++++++++++++++ .../testdata/references/asm_ref_asm.txt | 12 +++++++++ .../marker/testdata/references/asm_ref_go.txt | 20 ++++++++++++++ .../marker/testdata/references/go_ref_asm.txt | 19 ++++++++++++++ 10 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 gopls/internal/test/marker/testdata/references/asm_data.txt create mode 100644 gopls/internal/test/marker/testdata/references/asm_label.txt create mode 100644 gopls/internal/test/marker/testdata/references/asm_ref.txt create mode 100644 gopls/internal/test/marker/testdata/references/asm_ref_asm.txt create mode 100644 gopls/internal/test/marker/testdata/references/asm_ref_go.txt create mode 100644 gopls/internal/test/marker/testdata/references/go_ref_asm.txt diff --git a/gopls/internal/cache/xrefs/xrefs.go b/gopls/internal/cache/xrefs/xrefs.go index 1215ac3fc85..ae5de00f7cc 100644 --- a/gopls/internal/cache/xrefs/xrefs.go +++ b/gopls/internal/cache/xrefs/xrefs.go @@ -117,7 +117,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info, asmFiles // For each asm file, record references to identifiers. for fileIndex, af := range asmFiles { for _, id := range af.Idents { - if id.Kind != asm.Data && id.Kind != asm.Text { + if id.Kind != asm.Data && id.Kind != asm.Ref { continue } _, name, ok := morestrings.CutLast(id.Name, ".") diff --git a/gopls/internal/goasm/references.go b/gopls/internal/goasm/references.go index 22bd718fd2c..9a4c92f36f0 100644 --- a/gopls/internal/goasm/references.go +++ b/gopls/internal/goasm/references.go @@ -80,6 +80,9 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p for _, pgf := range pkg.CompiledGoFiles() { for curId := range pgf.Cursor.Preorder((*ast.Ident)(nil)) { id := curId.Node().(*ast.Ident) + if !includeDeclaration && pkg.TypesInfo().Defs[id] != nil { + continue + } if !matches(pkg.TypesInfo().ObjectOf(id)) { continue } @@ -91,17 +94,19 @@ func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p } } - if includeDeclaration { - for _, asmFile := range pkg.AsmFiles() { - for _, id := range asmFile.Idents { - if id.Name == found.Name && - (id.Kind == asm.Text || id.Kind == asm.Global || id.Kind == asm.Label) { - if loc, err := asmFile.NodeLocation(id); err == nil { - locations = append(locations, loc) - } + for _, asmFile := range pkg.AsmFiles() { + for _, id := range asmFile.Idents { + if id.Name == found.Name && + (id.Kind == asm.Data || id.Kind == asm.Ref) { + if id.Kind == asm.Data && !includeDeclaration { + continue + } + if loc, err := asmFile.NodeLocation(id); err == nil { + locations = append(locations, loc) } } } } + return locations, nil } diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go index 9b4ae045991..5c81dc1fa7f 100644 --- a/gopls/internal/golang/references.go +++ b/gopls/internal/golang/references.go @@ -616,7 +616,7 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo // Iterate over all assembly files and find all references to the target object. for _, pgf := range pkg.AsmFiles() { for _, id := range pgf.Idents { - if id.Kind != asm.Data && id.Kind != asm.Text { + if id.Kind != asm.Data && id.Kind != asm.Ref { continue } _, name, ok := morestrings.CutLast(id.Name, ".") diff --git a/gopls/internal/test/marker/testdata/references/asm.txt b/gopls/internal/test/marker/testdata/references/asm.txt index ef5e0216135..4ab547c0a08 100644 --- a/gopls/internal/test/marker/testdata/references/asm.txt +++ b/gopls/internal/test/marker/testdata/references/asm.txt @@ -17,25 +17,23 @@ go 1.24 -- foo/foo.go -- package foo -func Add(a, b int) int //@ loc(use, "Add"), refs("Add", use, def) -func sub(a, b int) int //@ loc(useSub, "sub"), refs("sub", useSub, defSub, refSub) -var _ = sub //@loc(refSub, "sub"), refs("sub", useSub, defSub, refSub) +func Add(a, b int) int //@ loc(defAdd, "Add"), refs("Add", defAdd, callAdd) +func sub(a, b int) int //@ loc(defSub, "sub"), refs("sub", defSub, refSub, callSub) +var _ = sub //@loc(refSub, "sub") -- foo/foo.s -- -// portable assembly -#include "textflag.h" -TEXT ·Add(SB), NOSPLIT, $0-24 //@ loc(def, "·Add"), refs("Add", def, use) +TEXT ·Add(SB), $0-24 //@ loc(addAsm, "·Add"), refs("Add", defAdd, callAdd) MOVQ a+0(FP), AX ADDQ b+8(FP), AX RET -TEXT ·sub(SB), NOSPLIT, $0-24 //@ loc(defSub, "·sub"), refs("sub", defSub, useSub, refSub) +TEXT ·sub(SB), $0-24 //@ loc(subAsm, "·sub"), refs("sub", defSub, refSub, callSub) MOVQ a+0(FP), AX SUBQ b+8(FP), AX RET -TEXT ·AddAndSub(SB), NOSPLIT, $0-24 - CALL ·Add(SB) - CALL ·sub(SB) +TEXT ·AddAndSub(SB), $0-24 + CALL ·Add(SB) //@ loc(callAdd, "·Add") + CALL ·sub(SB) //@ loc(callSub, "·sub") RET diff --git a/gopls/internal/test/marker/testdata/references/asm_data.txt b/gopls/internal/test/marker/testdata/references/asm_data.txt new file mode 100644 index 00000000000..07dd510a797 --- /dev/null +++ b/gopls/internal/test/marker/testdata/references/asm_data.txt @@ -0,0 +1,17 @@ + +-- go.mod -- +module example.com +go 1.24 + +-- foo/foo.go -- +package foo + +var myGlobal int64 //@loc(defMyGlobalGo, "myGlobal") + +-- foo/foo.s -- +DATA ·myGlobal+0(SB)/8, $42 +GLOBL ·myGlobal(SB), NOPTR, 8 + +TEXT ·UseGlobal(SB), $0-0 + MOVQ ·myGlobal(SB), AX //@loc(refMyGlobal, "·myGlobal") + RET diff --git a/gopls/internal/test/marker/testdata/references/asm_label.txt b/gopls/internal/test/marker/testdata/references/asm_label.txt new file mode 100644 index 00000000000..ade11102283 --- /dev/null +++ b/gopls/internal/test/marker/testdata/references/asm_label.txt @@ -0,0 +1,10 @@ + + +-- go.mod -- +module example.com +go 1.24 + +-- foo/foo.s -- +TEXT ·JumpDemo(SB), $0-0 +label1: //@loc(defLabel, "label1") + JMP label1 //@loc(refLabel, "label1") diff --git a/gopls/internal/test/marker/testdata/references/asm_ref.txt b/gopls/internal/test/marker/testdata/references/asm_ref.txt new file mode 100644 index 00000000000..293d1ef6a2b --- /dev/null +++ b/gopls/internal/test/marker/testdata/references/asm_ref.txt @@ -0,0 +1,26 @@ + + +-- go.mod -- +module example.com +go 1.24 + +-- foo/foo.go -- +package foo + +func Add(a, b int) int //@loc(defAddGo, "Add"), refs("Add", defAddGo, callAddGo) +func UseAdd() int { + return Add(1, 2) //@loc(callAddGo, "Add") +} +var myGlobal int64 //@loc(defMyGlobalGo, "myGlobal") + +-- foo/foo.s -- +TEXT ·Add(SB), $0-24 //@loc(defAddAsm, "·Add"), refs("Add", defAddGo, callAddGo) + MOVQ a+0(FP), AX + ADDQ b+8(FP), AX + RET + +DATA ·myGlobal+0(SB)/8, $42 +GLOBL ·myGlobal(SB), NOPTR, 8 +TEXT ·UseGlobal(SB), $0-0 + MOVQ ·myGlobal(SB), AX //@loc(refMyGlobal, "·myGlobal") + RET diff --git a/gopls/internal/test/marker/testdata/references/asm_ref_asm.txt b/gopls/internal/test/marker/testdata/references/asm_ref_asm.txt new file mode 100644 index 00000000000..af4e68a1b6d --- /dev/null +++ b/gopls/internal/test/marker/testdata/references/asm_ref_asm.txt @@ -0,0 +1,12 @@ + +-- go.mod -- +module example.com +go 1.24 + +-- foo/foo.s -- +TEXT ·Foo(SB), $0-8 //@loc(defFooAsm, "·Foo") + RET + +TEXT ·Bar(SB), $0-8 + CALL ·Foo(SB) //@loc(callFooAsm, "·Foo") + RET diff --git a/gopls/internal/test/marker/testdata/references/asm_ref_go.txt b/gopls/internal/test/marker/testdata/references/asm_ref_go.txt new file mode 100644 index 00000000000..cfd1da5097f --- /dev/null +++ b/gopls/internal/test/marker/testdata/references/asm_ref_go.txt @@ -0,0 +1,20 @@ + + +-- go.mod -- +module example.com +go 1.24 + +-- foo/foo.go -- +package foo + +func Add(a, b int) int //@loc(defAdd, "Add") + +func UseAdd() int { + return Add(1, 2) //@loc(callAddGo, "Add") +} + +-- foo/foo.s -- +TEXT ·Add(SB), $0-24 //@loc(defAddAsm, "·Add"), refs("Add", defAdd, callAddGo) + MOVQ a+0(FP), AX + ADDQ b+8(FP), AX + RET diff --git a/gopls/internal/test/marker/testdata/references/go_ref_asm.txt b/gopls/internal/test/marker/testdata/references/go_ref_asm.txt new file mode 100644 index 00000000000..73614cb4739 --- /dev/null +++ b/gopls/internal/test/marker/testdata/references/go_ref_asm.txt @@ -0,0 +1,19 @@ + + +-- go.mod -- +module example.com +go 1.24 + +-- foo/foo.go -- +package foo + +func Sub(a, b int) int { return a - b } //@loc(defSubGo, "Sub") + +-- foo/foo.s -- +TEXT ·CallSub(SB), $0-16 + MOVQ $10, AX + MOVQ $3, BX + MOVQ AX, a+0(FP) + MOVQ BX, b+8(FP) + CALL ·Sub(SB) //@loc(callSubAsm, "·Sub") + RET