Skip to content

cmd/compile: "missing function body" error when using the //go:linkname compiler directive #15006

@tsuna

Description

@tsuna
What version of Go are you using (go version)?

go version go1.6 darwin/amd64
go version devel +aa3650f Wed Mar 9 09:13:43 2016 +0000 darwin/amd64

What operating system and processor architecture are you using (go env)?

Mac OS X 10.9.5 on Intel Core i7-3615QM

GOARCH="amd64"
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/tsuna/go"
GOROOT="/usr/local/Cellar/go/1.6/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.6/libexec/pkg/tool/darwin_amd64"
GO15VENDOREXPERIMENT="1"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
CXX="clang++"
CGO_ENABLED="1"
What did you do?

The Go manual on Compiler Directives says:

The //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code.

For example let's say (hypothetically) that I wanted to call the strhash function defined in the runtime package, I could do:

package key

import "unsafe"

//go:linkname strhash runtime.strhash
func strhash(a unsafe.Pointer, h uintptr) uintptr

func hash(s string) uintptr {
    return strhash(unsafe.Pointer(&s), 0)
}
What did you expect to see?

The code above should build with no special ceremony.

What did you see instead?

The code doesn't build for a silly reason:

strhash.go:6: missing function body for "strhash"

This comes from the fact that in cmd/compile/internal/gc/pgen.go we do:

    if fn.Nbody == nil {
        if pure_go != 0 || strings.HasPrefix(fn.Func.Nname.Sym.Name, "init.") {
            Yyerror("missing function body for %q", fn.Func.Nname.Sym.Name)
            goto ret
        }

So in order to pass this check, we need to not have pure_go, which comes from the -complete flag in cmd/compile/internal/gc/lex.go:

    obj.Flagcount("complete", "compiling complete package (no C or assembly)", &pure_go)

which is passed from cmd/go/build.go under the following circumstances:

    // If we're giving the compiler the entire package (no C etc files), tell it that,
    // so that it can give good error messages about forward declarations.
    // Exceptions: a few standard packages have forward declarations for
    // pieces supplied behind-the-scenes by package runtime.
    extFiles := len(p.CgoFiles) + len(p.CFiles) + len(p.CXXFiles) + len(p.MFiles) + len(p.SFiles) + len(p.SysoFiles) + len(p.SwigFiles) + len(p.SwigCXXFiles)
    if p.Standard {
        switch p.ImportPath {
        case "bytes", "net", "os", "runtime/pprof", "sync", "time":
            extFiles++
        }
    }
    if extFiles == 0 {
        gcargs = append(gcargs, "-complete")
    }

So one workaround to not be -complete is to include an empty .s file in the package using the //go:linkname directive, as this will make len(p.SFiles) be 1, which in turn will make extFiles non-zero, so that the -complete flag won't get passed and we can pass the check above, and then the code builds and runs as expected. Phew!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions