mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-17 03:03:27 +00:00
compiler: support file-level //go:linkname directives
Modern golang.org/x/sys/unix (v0.36+) declares its linknames
detached from function declarations, e.g.:
func syscall_syscall(...)
//go:linkname syscall_syscall syscall.syscall
TinyGo's pragma parser only inspected function doc comments and
therefore missed these, producing link errors like:
undefined symbol: _golang.org/x/sys/unix.syscall_syscall
Extend parsePragmas to also walk the enclosing *ast.File's
free-standing comments for //go:linkname directives matching the
function's name. Function-attached directives still take
precedence. The existing 'unsafe' import gate is preserved.
Fixes #4395, #5365
This commit is contained in:
@@ -90,7 +90,8 @@ type compilerContext struct {
|
|||||||
astComments map[string]*ast.CommentGroup
|
astComments map[string]*ast.CommentGroup
|
||||||
embedGlobals map[string][]*loader.EmbedFile
|
embedGlobals map[string][]*loader.EmbedFile
|
||||||
pkg *types.Package
|
pkg *types.Package
|
||||||
packageDir string // directory for this package
|
loaderPkg *loader.Package // current package being compiled (for AST access)
|
||||||
|
packageDir string // directory for this package
|
||||||
runtimePkg *types.Package
|
runtimePkg *types.Package
|
||||||
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
|
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
|
||||||
}
|
}
|
||||||
@@ -298,6 +299,7 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
|
|||||||
c.packageDir = pkg.OriginalDir()
|
c.packageDir = pkg.OriginalDir()
|
||||||
c.embedGlobals = pkg.EmbedGlobals
|
c.embedGlobals = pkg.EmbedGlobals
|
||||||
c.pkg = pkg.Pkg
|
c.pkg = pkg.Pkg
|
||||||
|
c.loaderPkg = pkg
|
||||||
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
|
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
|
||||||
c.program = ssaPkg.Prog
|
c.program = ssaPkg.Prog
|
||||||
|
|
||||||
|
|||||||
@@ -399,6 +399,51 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also scan file-level //go:linkname directives. These appear as
|
||||||
|
// free-standing comments in *ast.File.Comments (not attached to any
|
||||||
|
// declaration), and are used by modern golang.org/x/sys/unix and others.
|
||||||
|
// Function-attached directives (above) take precedence — we only add
|
||||||
|
// file-level ones if no doc-comment linkname was found for this function.
|
||||||
|
//
|
||||||
|
// TODO: the hasUnsafeImport gate enforced downstream (see the
|
||||||
|
// //go:linkname case below) is package-level. gc enforces it per
|
||||||
|
// file, on the file containing the directive. For file-level
|
||||||
|
// linknames this is more important than for function-attached ones,
|
||||||
|
// because the directive can live in a file separate from the
|
||||||
|
// function. A stricter implementation would check whether the file
|
||||||
|
// returned by fileForFunc imports "unsafe", not whether any file in
|
||||||
|
// the package does.
|
||||||
|
hasFunctionLinkname := false
|
||||||
|
for _, comment := range pragmas {
|
||||||
|
if strings.HasPrefix(comment.Text, "//go:linkname ") {
|
||||||
|
parts := strings.Fields(comment.Text)
|
||||||
|
if len(parts) == 3 && parts[1] == f.Name() {
|
||||||
|
hasFunctionLinkname = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasFunctionLinkname {
|
||||||
|
if file := c.fileForFunc(f); file != nil {
|
||||||
|
for _, group := range file.Comments {
|
||||||
|
// Skip the function's own doc comment — already handled above.
|
||||||
|
if decl, ok := syntax.(*ast.FuncDecl); ok && group == decl.Doc {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, comment := range group.List {
|
||||||
|
if !strings.HasPrefix(comment.Text, "//go:linkname ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Fields(comment.Text)
|
||||||
|
if len(parts) != 3 || parts[1] != f.Name() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pragmas = append(pragmas, comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse each pragma.
|
// Parse each pragma.
|
||||||
for _, comment := range pragmas {
|
for _, comment := range pragmas {
|
||||||
parts := strings.Fields(comment.Text)
|
parts := strings.Fields(comment.Text)
|
||||||
@@ -693,6 +738,34 @@ type globalInfo struct {
|
|||||||
section string // go:section
|
section string // go:section
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fileForFunc returns the *ast.File that contains the declaration of f, or
|
||||||
|
// nil if it cannot be determined. File-level pragmas are only consulted for
|
||||||
|
// functions in the package currently being compiled — functions imported from
|
||||||
|
// other packages have their file-level pragmas processed when those packages
|
||||||
|
// are compiled.
|
||||||
|
func (c *compilerContext) fileForFunc(f *ssa.Function) *ast.File {
|
||||||
|
if c.loaderPkg == nil || f.Pkg == nil || f.Pkg.Pkg != c.loaderPkg.Pkg {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
syntax := f.Syntax()
|
||||||
|
if f.Origin() != nil {
|
||||||
|
syntax = f.Origin().Syntax()
|
||||||
|
}
|
||||||
|
if syntax == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pos := syntax.Pos()
|
||||||
|
if !pos.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, file := range c.loaderPkg.Files {
|
||||||
|
if file.FileStart <= pos && pos < file.FileEnd {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// loadASTComments loads comments on globals from the AST, for use later in the
|
// loadASTComments loads comments on globals from the AST, for use later in the
|
||||||
// program. In particular, they are required for //go:extern pragmas on globals.
|
// program. In particular, they are required for //go:extern pragmas on globals.
|
||||||
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
||||||
|
|||||||
Vendored
+28
@@ -120,3 +120,31 @@ func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
|
|||||||
func doesHeapAlloc() *int {
|
func doesHeapAlloc() *int {
|
||||||
return new(int)
|
return new(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define a function in a different package using a file-level go:linkname.
|
||||||
|
// (Same as withLinkageName1, but with the //go:linkname directive detached
|
||||||
|
// from the function declaration — see https://github.com/tinygo-org/tinygo/issues/4395)
|
||||||
|
func withFileLevelLinkageName1() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import a function from a different package using a file-level go:linkname.
|
||||||
|
// (Same as withLinkageName2, but with the //go:linkname directive detached
|
||||||
|
// from the function declaration.)
|
||||||
|
func withFileLevelLinkageName2()
|
||||||
|
|
||||||
|
//go:linkname withFileLevelLinkageName1 somepkg.someFileLevelFunction1
|
||||||
|
//go:linkname withFileLevelLinkageName2 somepkg.someFileLevelFunction2
|
||||||
|
|
||||||
|
// File-level linkname directives can also appear between two function
|
||||||
|
// declarations, in which case Go's AST attaches them as the doc comment
|
||||||
|
// of the following function — even when the directive's localname refers
|
||||||
|
// to a different function. Exercise that case: the directive below names
|
||||||
|
// withAdjacentLinkageName, but Go will attach it to
|
||||||
|
// sentinelAfterAdjacentLinkname's Doc. The file-level scan must find it
|
||||||
|
// by walking comment groups regardless of which decl they're attached to.
|
||||||
|
func withAdjacentLinkageName() {
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname withAdjacentLinkageName somepkg.someAdjacentFunction
|
||||||
|
func sentinelAfterAdjacentLinkname() {
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+20
@@ -102,6 +102,26 @@ entry:
|
|||||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||||
declare noalias nonnull ptr @runtime.alloc_noheap(i32, ptr, ptr) #9
|
declare noalias nonnull ptr @runtime.alloc_noheap(i32, ptr, ptr) #9
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define hidden void @somepkg.someFileLevelFunction1(ptr %context) unnamed_addr #1 {
|
||||||
|
entry:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
declare void @somepkg.someFileLevelFunction2(ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define hidden void @somepkg.someAdjacentFunction(ptr %context) unnamed_addr #1 {
|
||||||
|
entry:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define hidden void @main.sentinelAfterAdjacentLinkname(ptr %context) unnamed_addr #1 {
|
||||||
|
entry:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||||
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||||
attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" }
|
attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" }
|
||||||
|
|||||||
Reference in New Issue
Block a user