cmd/link/internal/ld: introduce -funcalign=N option

This patch adds linker option -funcalign=N that allows to set alignment
for function entries.

This CL is based on vasiliy.leonenko@gmail.com's cl/615736.

For #72130

Change-Id: I57e5c9c4c71a989533643fda63a9a79c5c897dea
Reviewed-on: https://go-review.googlesource.com/c/go/+/660996
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
Aleksey Markin 2025-03-26 18:47:15 +03:00 committed by Cherry Mui
parent 5b36f61356
commit 9302a57134
5 changed files with 94 additions and 5 deletions

View File

@ -83,6 +83,8 @@ Flags:
Set space-separated flags to pass to the external linker.
-f
Ignore version mismatch in the linked archives.
-funcalign N
Set function alignment to N bytes
-g
Disable Go package data checks.
-importcfg file

View File

@ -2658,9 +2658,7 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
}
align := ldr.SymAlign(s)
if align == 0 {
align = int32(Funcalign)
}
align = max(align, int32(Funcalign))
va = uint64(Rnd(int64(va), int64(align)))
if sect.Align < align {
sect.Align = align

View File

@ -377,7 +377,11 @@ func mayberemoveoutfile() {
}
func libinit(ctxt *Link) {
Funcalign = thearch.Funcalign
if *FlagFuncAlign != 0 {
Funcalign = *FlagFuncAlign
} else {
Funcalign = thearch.Funcalign
}
// add goroot to the end of the libdir list.
suffix := ""

View File

@ -105,6 +105,7 @@ var (
FlagStrictDups = flag.Int("strictdups", 0, "sanity check duplicate symbol contents during object file reading (1=warn 2=err).")
FlagRound = flag.Int64("R", -1, "set address rounding `quantum`")
FlagTextAddr = flag.Int64("T", -1, "set the start address of text symbols")
FlagFuncAlign = flag.Int("funcalign", 0, "set function align to `N` bytes")
flagEntrySymbol = flag.String("E", "", "set `entry` symbol name")
flagPruneWeakMap = flag.Bool("pruneweakmap", true, "prune weak mapinit refs")
flagRandLayout = flag.Int64("randlayout", 0, "randomize function layout")
@ -251,6 +252,9 @@ func Main(arch *sys.Arch, theArch Arch) {
if *FlagRound != -1 && (*FlagRound < 4096 || !isPowerOfTwo(*FlagRound)) {
Exitf("invalid -R value 0x%x", *FlagRound)
}
if *FlagFuncAlign != 0 && !isPowerOfTwo(int64(*FlagFuncAlign)) {
Exitf("invalid -funcalign value %d", *FlagFuncAlign)
}
checkStrictDups = *FlagStrictDups

View File

@ -16,6 +16,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"testing"
@ -701,7 +702,6 @@ func TestFuncAlign(t *testing.T) {
t.Fatal(err)
}
// Build and run with old object file format.
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "falign")
cmd.Dir = tmpdir
out, err := cmd.CombinedOutput()
@ -718,6 +718,87 @@ func TestFuncAlign(t *testing.T) {
}
}
const testFuncAlignOptionSrc = `
package main
//go:noinline
func foo() {
}
//go:noinline
func bar() {
}
//go:noinline
func baz() {
}
func main() {
foo()
bar()
baz()
}
`
// TestFuncAlignOption verifies that the -funcalign option changes the function alignment
func TestFuncAlignOption(t *testing.T) {
testenv.MustHaveGoBuild(t)
t.Parallel()
tmpdir := t.TempDir()
src := filepath.Join(tmpdir, "falign.go")
err := os.WriteFile(src, []byte(testFuncAlignOptionSrc), 0666)
if err != nil {
t.Fatal(err)
}
alignTest := func(align uint64) {
exeName := "falign.exe"
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-funcalign="+strconv.FormatUint(align, 10), "-o", exeName, "falign.go")
cmd.Dir = tmpdir
out, err := cmd.CombinedOutput()
if err != nil {
t.Errorf("build failed: %v \n%s", err, out)
}
exe := filepath.Join(tmpdir, exeName)
cmd = testenv.Command(t, exe)
out, err = cmd.CombinedOutput()
if err != nil {
t.Errorf("failed to run with err %v, output: %s", err, out)
}
// Check function alignment
f, err := objfile.Open(exe)
if err != nil {
t.Fatalf("failed to open file:%v\n", err)
}
defer f.Close()
fname := map[string]bool{"_main.foo": false,
"_main.bar": false,
"_main.baz": false}
syms, err := f.Symbols()
for _, s := range syms {
fn := s.Name
if _, ok := fname[fn]; !ok {
fn = "_" + s.Name
if _, ok := fname[fn]; !ok {
continue
}
}
if s.Addr%align != 0 {
t.Fatalf("unaligned function: %s %x. Expected alignment: %d\n", fn, s.Addr, align)
}
fname[fn] = true
}
for k, v := range fname {
if !v {
t.Fatalf("function %s not found\n", k)
}
}
}
alignTest(16)
alignTest(32)
}
const testTrampSrc = `
package main
import "fmt"