Skip to content

gopls: add CompiledAsmFiles in cache.Package #572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
25 changes: 19 additions & 6 deletions gopls/internal/cache/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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,
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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" {
Expand Down
7 changes: 7 additions & 0 deletions gopls/internal/cache/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,13 @@ func buildMetadata(updates map[PackageID]*metadata.Package, loadDir string, stan
*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))
}
copyURIs(&mp.CompiledGoFiles, pkg.CompiledGoFiles)
copyURIs(&mp.GoFiles, pkg.GoFiles)
copyURIs(&mp.IgnoredFiles, pkg.IgnoredFiles)
Expand Down
2 changes: 2 additions & 0 deletions gopls/internal/cache/metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type Package struct {
IgnoredFiles []protocol.DocumentURI
OtherFiles []protocol.DocumentURI

AsmFiles []protocol.DocumentURI // *.s subset of OtherFiles

ForTest PackagePath // q in a "p [q.test]" package, else ""
TypesSizes types.Sizes
Errors []packages.Error // must be set for packages in import cycles
Expand Down
22 changes: 21 additions & 1 deletion gopls/internal/cache/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -69,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
}
Expand Down Expand Up @@ -200,3 +202,21 @@ 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
}

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)
}
19 changes: 19 additions & 0 deletions gopls/internal/cache/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -43,3 +44,21 @@ 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 parses the assembly files whose contents are provided by fhs.
func parseAsmFiles(ctx context.Context, fhs ...file.Handle) ([]*asm.File, error) {
pafs := 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()
}
pafs[i] = asm.Parse(fh.URI(), content)
}
return pafs, nil
}
35 changes: 33 additions & 2 deletions gopls/internal/cache/xrefs/xrefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ package xrefs
import (
"go/ast"
"go/types"
"slices"
"sort"

"golang.org/x/tools/go/types/objectpath"
"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.
Expand Down Expand Up @@ -112,6 +115,34 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte {
}
}

// 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
}
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)
gobObj, ok := objects[obj]
if !ok {
gobObj = &gobObject{Path: objectpath.Path(obj.Name())}
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 {
Expand Down Expand Up @@ -147,7 +178,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]
uri := slices.Concat(mp.CompiledGoFiles, mp.AsmFiles)[ref.FileIndex]
locs = append(locs, protocol.Location{
URI: uri,
Range: ref.Range,
Expand Down
2 changes: 1 addition & 1 deletion gopls/internal/goasm/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
125 changes: 125 additions & 0 deletions gopls/internal/goasm/references.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// 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"
"go/types"

"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"
)

// 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()

mps, err := snapshot.MetadataForFile(ctx, fh.URI())
if err != nil {
return nil, err
}
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
}
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
}

// 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")
}

// 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)
curObj, ok := pkg.TypesInfo().Defs[id]
if !ok {
curObj, ok = pkg.TypesInfo().Uses[id]
if !ok {
continue
}
}
if !matches(curObj) {
continue
}
loc, err := pgf.NodeLocation(id)
if err != nil {
return nil, err
}
locations = append(locations, loc)
}
}

// 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)
}
}
}

return locations, nil
}
Loading