From 24e9707cbfa6b1ed6abdd4b11f9ddaf3aac5ad88 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 25 May 2021 16:31:41 -0700 Subject: [PATCH] cmd/link, cmd/cgo: support -flto in CFLAGS The linker now accepts unrecognized object files in external linking mode. These objects will simply be passed to the external linker. This permits using -flto which can generate pure byte code objects, whose symbol table the linker does not know how to read. The cgo tool now passes -fno-lto when generating objects whose symbols it needs to read. The cgo tool now emits matching types in different objects, so that the lto linker does not report a mismatch. This is based on https://golang.org/cl/293290 by Derek Parker. For #43505 Fixes #43830 Fixes #46295 Change-Id: I6787de213417466784ddef5af8899e453b4ae1ad Reviewed-on: https://go-review.googlesource.com/c/go/+/322614 Trust: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Go Bot Reviewed-by: Michael Hudson-Doyle --- diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index ae61725..a73e998 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -1638,6 +1638,8 @@ c = append(c, "-maix64") c = append(c, "-mcmodel=large") } + // disable LTO so we get an object whose symbols we can read + c = append(c, "-fno-lto") c = append(c, "-") //read input from standard input return c } diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 8c31d5b..94152f4 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -168,8 +168,18 @@ if *gccgo { fmt.Fprintf(fc, "extern byte *%s;\n", n.C) } else { - fmt.Fprintf(fm, "extern char %s[];\n", n.C) - fmt.Fprintf(fm, "void *_cgohack_%s = %s;\n\n", n.C, n.C) + // Force a reference to all symbols so that + // the external linker will add DT_NEEDED + // entries as needed on ELF systems. + // Treat function variables differently + // to avoid type confict errors from LTO + // (Link Time Optimization). + if n.Kind == "fpvar" { + fmt.Fprintf(fm, "extern void %s();\n", n.C) + } else { + fmt.Fprintf(fm, "extern char %s[];\n", n.C) + fmt.Fprintf(fm, "void *_cgohack_%s = %s;\n\n", n.C, n.C) + } fmt.Fprintf(fgo2, "//go:linkname __cgo_%s %s\n", n.C, n.C) fmt.Fprintf(fgo2, "//go:cgo_import_static %s\n", n.C) fmt.Fprintf(fgo2, "var __cgo_%s byte\n", n.C) @@ -1042,7 +1052,7 @@ fmt.Fprintf(fgo2, "//go:cgo_export_static _cgoexp%s_%s\n", cPrefix, exp.ExpName) fmt.Fprintf(fgo2, "func _cgoexp%s_%s(a *%s) {\n", cPrefix, exp.ExpName, gotype) - fmt.Fprintf(fm, "int _cgoexp%s_%s;\n", cPrefix, exp.ExpName) + fmt.Fprintf(fm, "void _cgoexp%s_%s(void* p){}\n", cPrefix, exp.ExpName) if gccResult != "void" { // Write results back to frame. diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 50bf80b..bc49c6d 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -722,14 +722,29 @@ }, }) if t.hasCxx() { - t.tests = append(t.tests, distTest{ - name: "swig_callback", - heading: "../misc/swig/callback", - fn: func(dt *distTest) error { - t.addCmd(dt, "misc/swig/callback", t.goTest()) - return nil + t.tests = append(t.tests, + distTest{ + name: "swig_callback", + heading: "../misc/swig/callback", + fn: func(dt *distTest) error { + t.addCmd(dt, "misc/swig/callback", t.goTest()) + return nil + }, }, - }) + distTest{ + name: "swig_callback_lto", + heading: "../misc/swig/callback", + fn: func(dt *distTest) error { + cmd := t.addCmd(dt, "misc/swig/callback", t.goTest()) + cmd.Env = append(os.Environ(), + "CGO_CFLAGS=-flto", + "CGO_CXXFLAGS=-flto", + "CGO_LDFLAGS=-flto", + ) + return nil + }, + }, + ) } } } diff --git a/src/cmd/go/testdata/script/cgo_lto2_issue43830.txt b/src/cmd/go/testdata/script/cgo_lto2_issue43830.txt new file mode 100644 index 0000000..e2483ba --- /dev/null +++ b/src/cmd/go/testdata/script/cgo_lto2_issue43830.txt @@ -0,0 +1,33 @@ +# tests golang.org/issue/43830 + +[!cgo] skip 'skipping test without cgo' +[openbsd] env CC='clang' +[openbsd] [!exec:clang] skip 'skipping test without clang present' +[!openbsd] env CC='gcc' +[!openbsd] [!exec:gcc] skip 'skipping test without gcc present' + +env CGO_CFLAGS='-Wno-ignored-optimization-argument -flto -ffat-lto-objects' + +go build main.go + +-- main.go -- + +package main + +import "fmt" + +// #include "hello.h" +import "C" + +func main() { + hello := C.hello + fmt.Printf("%v\n", hello) +} + +-- hello.h -- + +#include + +void hello(void) { + printf("hello\n"); +} diff --git a/src/cmd/go/testdata/script/cgo_lto_issue43830.txt b/src/cmd/go/testdata/script/cgo_lto_issue43830.txt new file mode 100644 index 0000000..06ab2f3 --- /dev/null +++ b/src/cmd/go/testdata/script/cgo_lto_issue43830.txt @@ -0,0 +1,39 @@ +# tests golang.org/issue/43830 + +[!cgo] skip 'skipping test without cgo' +[openbsd] env CC='clang' +[openbsd] [!exec:clang] skip 'skipping test without clang present' +[!openbsd] env CC='gcc' +[!openbsd] [!exec:gcc] skip 'skipping test without gcc present' + +env CGO_CFLAGS='-Wno-ignored-optimization-argument -flto -ffat-lto-objects' + +go build main.go add.go + +-- main.go -- + +package main + +/* +int c_add(int a, int b) { + return myadd(a, b); +} +*/ +import "C" + +func main() { + println(C.c_add(1, 2)) +} + +-- add.go -- + +package main + +import "C" + +/* test */ + +//export myadd +func myadd(a C.int, b C.int) C.int { + return a + b +} diff --git a/src/cmd/link/internal/ld/ar.go b/src/cmd/link/internal/ld/ar.go index 22f53a4..23915f9 100644 --- a/src/cmd/link/internal/ld/ar.go +++ b/src/cmd/link/internal/ld/ar.go @@ -124,6 +124,10 @@ libgcc := sym.Library{Pkg: "libgcc"} h := ldobj(ctxt, f, &libgcc, l, pname, name) + if h.ld == nil { + Errorf(nil, "%s unrecognized object file at offset %d", name, off) + continue + } f.MustSeek(h.off, 0) h.ld(ctxt, f, h.pkg, h.length, h.pn) } diff --git a/src/cmd/link/internal/ld/config.go b/src/cmd/link/internal/ld/config.go index ae0d752..20f1d0b 100644 --- a/src/cmd/link/internal/ld/config.go +++ b/src/cmd/link/internal/ld/config.go @@ -241,6 +241,10 @@ return true, "dynamically linking with a shared library" } + if unknownObjFormat { + return true, "some input objects have an unrecognized file format" + } + return false, "" } @@ -248,7 +252,7 @@ // // It is called after flags are processed and inputs are processed, // so the ctxt.LinkMode variable has an initial value from the -linkmode -// flag and the iscgo externalobj variables are set. +// flag and the iscgo, externalobj, and unknownObjFormat variables are set. func determineLinkMode(ctxt *Link) { extNeeded, extReason := mustLinkExternal(ctxt) via := "" diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index e8f001b..644faeb 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -343,10 +343,16 @@ const pkgdef = "__.PKGDEF" var ( - // Set if we see an object compiled by the host compiler that is not - // from a package that is known to support internal linking mode. + // externalobj is set to true if we see an object compiled by + // the host compiler that is not from a package that is known + // to support internal linking mode. externalobj = false - theline string + + // unknownObjFormat is set to true if we see an object whose + // format we don't recognize. + unknownObjFormat = false + + theline string ) func Lflag(ctxt *Link, arg string) { @@ -1065,6 +1071,10 @@ } f.MustSeek(h.off, 0) + if h.ld == nil { + Errorf(nil, "%s: unrecognized object file format", h.pn) + continue + } h.ld(ctxt, f, h.pkg, h.length, h.pn) f.Close() } @@ -1855,6 +1865,14 @@ return ldhostobj(ldxcoff, ctxt.HeadType, f, pkg, length, pn, file) } + if c1 != 'g' || c2 != 'o' || c3 != ' ' || c4 != 'o' { + // An unrecognized object is just passed to the external linker. + // If we try to read symbols from this object, we will + // report an error at that time. + unknownObjFormat = true + return ldhostobj(nil, ctxt.HeadType, f, pkg, length, pn, file) + } + /* check the header */ line, err := f.ReadString('\n') if err != nil {