From 9302a57134a698b62576cd3b849017a02c731c98 Mon Sep 17 00:00:00 2001 From: Aleksey Markin Date: Wed, 26 Mar 2025 18:47:15 +0300 Subject: [PATCH] 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 Reviewed-by: Dmitri Shuralyov Reviewed-by: Cherry Mui --- src/cmd/link/doc.go | 2 + src/cmd/link/internal/ld/data.go | 4 +- src/cmd/link/internal/ld/lib.go | 6 ++- src/cmd/link/internal/ld/main.go | 4 ++ src/cmd/link/link_test.go | 83 +++++++++++++++++++++++++++++++- 5 files changed, 94 insertions(+), 5 deletions(-) diff --git a/src/cmd/link/doc.go b/src/cmd/link/doc.go index 840f4b04ed..b620219e96 100644 --- a/src/cmd/link/doc.go +++ b/src/cmd/link/doc.go @@ -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 diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index ca394700cf..b3e1ac457d 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -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 diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index b114ca2a3d..7f22b6ba1c 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -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 := "" diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index 377dcd6c85..6a684890be 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -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 diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index cd2f9e3953..53c4ee77fe 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -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" -- 2.51.0