github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/linkname.go (about) 1 package compiler 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "strings" 8 9 "github.com/gopherjs/gopherjs/compiler/astutil" 10 "github.com/gopherjs/gopherjs/compiler/internal/symbol" 11 ) 12 13 // GoLinkname describes a go:linkname compiler directive found in the source code. 14 // 15 // GopherJS treats these directives in a way that resembles a symbolic link, 16 // where for a single given symbol implementation there may be zero or more 17 // symbols referencing it. This is subtly different from the upstream Go 18 // implementation, which simply overrides symbol name the linker will use. 19 type GoLinkname struct { 20 Implementation symbol.Name 21 Reference symbol.Name 22 } 23 24 // parseGoLinknames processed comments in a source file and extracts //go:linkname 25 // compiler directive from the comments. 26 // 27 // The following directive format is supported: 28 // //go:linkname <localname> <importpath>.<name> 29 // //go:linkname <localname> <importpath>.<type>.<name> 30 // //go:linkname <localname> <importpath>.<(*type)>.<name> 31 // 32 // GopherJS directive support has the following limitations: 33 // 34 // - External linkname must be specified. 35 // - The directive must be applied to a package-level function or method (variables 36 // are not supported). 37 // - The local function referenced by the directive must have no body (in other 38 // words, it can only "import" an external function implementation into the 39 // local scope). 40 func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]GoLinkname, error) { 41 var errs ErrorList = nil 42 var directives []GoLinkname 43 44 isUnsafe := astutil.ImportsUnsafe(file) 45 46 processComment := func(comment *ast.Comment) error { 47 if !strings.HasPrefix(comment.Text, "//go:linkname ") { 48 return nil // Not a linkname compiler directive. 49 } 50 51 // TODO(nevkontakte): Ideally we should check that the directive comment 52 // is on a line by itself, line Go compiler does, but ast.Comment doesn't 53 // provide an easy way to find that out. 54 55 if !isUnsafe { 56 return fmt.Errorf(`//go:linkname is only allowed in Go files that import "unsafe"`) 57 } 58 59 fields := strings.Fields(comment.Text) 60 if len(fields) != 3 { 61 return fmt.Errorf(`usage (all fields required): //go:linkname localname importpath.extname`) 62 } 63 64 localPkg, localName := pkgPath, fields[1] 65 extPkg, extName := "", fields[2] 66 if pos := strings.LastIndexByte(extName, '/'); pos != -1 { 67 if idx := strings.IndexByte(extName[pos+1:], '.'); idx != -1 { 68 extPkg, extName = extName[0:pos+idx+1], extName[pos+idx+2:] 69 } 70 } else if idx := strings.IndexByte(extName, '.'); idx != -1 { 71 extPkg, extName = extName[0:idx], extName[idx+1:] 72 } 73 74 obj := file.Scope.Lookup(localName) 75 if obj == nil { 76 return fmt.Errorf("//go:linkname local symbol %q is not found in the current source file", localName) 77 } 78 79 if obj.Kind != ast.Fun { 80 if pkgPath == "math/bits" || pkgPath == "reflect" { 81 // These standard library packages are known to use go:linkname with 82 // variables, which GopherJS doesn't support. We silently ignore such 83 // directives, since it doesn't seem to cause any problems. 84 return nil 85 } 86 return fmt.Errorf("gopherjs: //go:linkname is only supported for functions, got %q", obj.Kind) 87 } 88 89 decl := obj.Decl.(*ast.FuncDecl) 90 if decl.Body != nil { 91 if pkgPath == "runtime" || pkgPath == "internal/bytealg" || pkgPath == "internal/fuzz" { 92 // These standard library packages are known to use unsupported 93 // "insert"-style go:linkname directives, which we ignore here and handle 94 // case-by-case in native overrides. 95 return nil 96 } 97 return fmt.Errorf("gopherjs: //go:linkname can not insert local implementation into an external package %q", extPkg) 98 } 99 // Local function has no body, treat it as a reference to an external implementation. 100 directives = append(directives, GoLinkname{ 101 Reference: symbol.Name{PkgPath: localPkg, Name: localName}, 102 Implementation: symbol.Name{PkgPath: extPkg, Name: extName}, 103 }) 104 return nil 105 } 106 107 for _, cg := range file.Comments { 108 for _, c := range cg.List { 109 if err := processComment(c); err != nil { 110 errs = append(errs, ErrorAt(err, fset, c.Pos())) 111 } 112 } 113 } 114 115 return directives, errs.ErrOrNil() 116 } 117 118 // goLinknameSet is a utility that enables quick lookup of whether a decl is 119 // affected by any go:linkname directive in the program. 120 type goLinknameSet struct { 121 byImplementation map[symbol.Name][]GoLinkname 122 byReference map[symbol.Name]GoLinkname 123 } 124 125 // Add more GoLinkname directives into the set. 126 func (gls *goLinknameSet) Add(entries []GoLinkname) error { 127 if gls.byImplementation == nil { 128 gls.byImplementation = map[symbol.Name][]GoLinkname{} 129 } 130 if gls.byReference == nil { 131 gls.byReference = map[symbol.Name]GoLinkname{} 132 } 133 for _, e := range entries { 134 gls.byImplementation[e.Implementation] = append(gls.byImplementation[e.Implementation], e) 135 if prev, found := gls.byReference[e.Reference]; found { 136 return fmt.Errorf("conflicting go:linkname directives: two implementations for %q: %q and %q", 137 e.Reference, prev.Implementation, e.Implementation) 138 } 139 gls.byReference[e.Reference] = e 140 } 141 return nil 142 } 143 144 // IsImplementation returns true if there is a directive referencing this symbol 145 // as an implementation. 146 func (gls *goLinknameSet) IsImplementation(sym symbol.Name) bool { 147 _, found := gls.byImplementation[sym] 148 return found 149 } 150 151 // FindImplementation returns a symbol name, which provides the implementation 152 // for the given symbol. The second value indicates whether the implementation 153 // was found. 154 func (gls *goLinknameSet) FindImplementation(sym symbol.Name) (symbol.Name, bool) { 155 directive, found := gls.byReference[sym] 156 return directive.Implementation, found 157 }