From: Cherry Zhang Date: Thu, 2 Apr 2020 16:48:13 +0000 (-0400) Subject: [dev.link] cmd: support large function alignment X-Git-Tag: go1.15beta1~679^2~2 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=d92a5a80b5c06b5d9915c5f888ab5cac6a94b11e;p=gostls13.git [dev.link] cmd: support large function alignment This ports CL 226997 to the dev.link branch. - The assembler part and old object file writing are unchanged. - Changes to cmd/link are applied to cmd/oldlink. - Add alignment field to new object files for the new linker. Change-Id: Id00f323ae5bdd86b2709a702ee28bcaa9ba962f8 Reviewed-on: https://go-review.googlesource.com/c/go/+/227025 Reviewed-by: Than McIntosh --- diff --git a/src/cmd/internal/goobj/read.go b/src/cmd/internal/goobj/read.go index 027c77e725..44e619cabb 100644 --- a/src/cmd/internal/goobj/read.go +++ b/src/cmd/internal/goobj/read.go @@ -96,6 +96,7 @@ type Var struct { type Func struct { Args int64 // size in bytes of argument frame: inputs and outputs Frame int64 // size in bytes of local variable frame + Align uint32 // alignment requirement in bytes for the address of the function Leaf bool // function omits save of link register (ARM) NoSplit bool // function omits stack split prologue TopFrame bool // function is the top of the call stack @@ -591,6 +592,7 @@ func (r *objReader) parseObject(prefix []byte) error { s.Func = f f.Args = r.readInt() f.Frame = r.readInt() + f.Align = uint32(r.readInt()) flags := r.readInt() f.Leaf = flags&(1<<0) != 0 f.TopFrame = flags&(1<<4) != 0 diff --git a/src/cmd/internal/goobj2/objfile.go b/src/cmd/internal/goobj2/objfile.go index c7b508cc5e..52544bf773 100644 --- a/src/cmd/internal/goobj2/objfile.go +++ b/src/cmd/internal/goobj2/objfile.go @@ -186,11 +186,12 @@ func (h *Header) Size() int { // Symbol definition. type Sym struct { - Name string - ABI uint16 - Type uint8 - Flag uint8 - Siz uint32 + Name string + ABI uint16 + Type uint8 + Flag uint8 + Siz uint32 + Align uint32 } const SymABIstatic = ^uint16(0) @@ -216,9 +217,10 @@ func (s *Sym) Write(w *Writer) { w.Uint8(s.Type) w.Uint8(s.Flag) w.Uint32(s.Siz) + w.Uint32(s.Align) } -const SymSize = stringRefSize + 2 + 1 + 1 + 4 +const SymSize = stringRefSize + 2 + 1 + 1 + 4 + 4 type Sym2 [SymSize]byte @@ -228,10 +230,11 @@ func (s *Sym2) Name(r *Reader) string { return r.StringAt(off, len) } -func (s *Sym2) ABI() uint16 { return binary.LittleEndian.Uint16(s[8:]) } -func (s *Sym2) Type() uint8 { return s[10] } -func (s *Sym2) Flag() uint8 { return s[11] } -func (s *Sym2) Siz() uint32 { return binary.LittleEndian.Uint32(s[12:]) } +func (s *Sym2) ABI() uint16 { return binary.LittleEndian.Uint16(s[8:]) } +func (s *Sym2) Type() uint8 { return s[10] } +func (s *Sym2) Flag() uint8 { return s[11] } +func (s *Sym2) Siz() uint32 { return binary.LittleEndian.Uint32(s[12:]) } +func (s *Sym2) Align() uint32 { return binary.LittleEndian.Uint32(s[16:]) } func (s *Sym2) Dupok() bool { return s.Flag()&SymFlagDupok != 0 } func (s *Sym2) Local() bool { return s.Flag()&SymFlagLocal != 0 } diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go index e8b092a2a8..8e5b598084 100644 --- a/src/cmd/internal/obj/arm64/asm7.go +++ b/src/cmd/internal/obj/arm64/asm7.go @@ -886,25 +886,10 @@ const OP_NOOP = 0xd503201f // align code to a certain length by padding bytes. func pcAlignPadLength(pc int64, alignedValue int64, ctxt *obj.Link) int { - switch alignedValue { - case 8: - if pc%8 == 4 { - return 4 - } - case 16: - switch pc % 16 { - case 4: - return 12 - case 8: - return 8 - case 12: - return 4 - } - default: - ctxt.Diag("Unexpected alignment: %d for PCALIGN directive\n", alignedValue) + if !((alignedValue&(alignedValue-1) == 0) && 8 <= alignedValue && alignedValue <= 2048) { + ctxt.Diag("alignment value of an instruction must be a power of two and in the range [8, 2048], got %d\n", alignedValue) } - - return 0 + return int(-pc & (alignedValue - 1)) } func span7(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { @@ -940,8 +925,12 @@ func span7(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if m == 0 { switch p.As { case obj.APCALIGN: - a := p.From.Offset - m = pcAlignPadLength(pc, a, ctxt) + alignedValue := p.From.Offset + m = pcAlignPadLength(pc, alignedValue, ctxt) + // Update the current text symbol alignment value. + if int32(alignedValue) > cursym.Func.Align { + cursym.Func.Align = int32(alignedValue) + } break case obj.ANOP, obj.AFUNCDATA, obj.APCDATA: continue @@ -1017,8 +1006,8 @@ func span7(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if m == 0 { switch p.As { case obj.APCALIGN: - a := p.From.Offset - m = pcAlignPadLength(pc, a, ctxt) + alignedValue := p.From.Offset + m = pcAlignPadLength(pc, alignedValue, ctxt) break case obj.ANOP, obj.AFUNCDATA, obj.APCDATA: continue diff --git a/src/cmd/internal/obj/arm64/asm_test.go b/src/cmd/internal/obj/arm64/asm_test.go index 1691828739..9efdb0217f 100644 --- a/src/cmd/internal/obj/arm64/asm_test.go +++ b/src/cmd/internal/obj/arm64/asm_test.go @@ -18,7 +18,9 @@ import ( // TestLarge generates a very large file to verify that large // program builds successfully, in particular, too-far -// conditional branches are fixed. +// conditional branches are fixed, and also verify that the +// instruction's pc can be correctly aligned even when branches +// need to be fixed. func TestLarge(t *testing.T) { if testing.Short() { t.Skip("Skip in short mode") @@ -41,10 +43,27 @@ func TestLarge(t *testing.T) { t.Fatalf("can't write output: %v\n", err) } - // build generated file - cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile) + pattern := `0x0080\s00128\s\(.*\)\tMOVD\t\$3,\sR3` + + // assemble generated file + cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-S", "-o", filepath.Join(dir, "test.o"), tmpfile) cmd.Env = append(os.Environ(), "GOARCH=arm64", "GOOS=linux") out, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("Assemble failed: %v, output: %s", err, out) + } + matched, err := regexp.MatchString(pattern, string(out)) + if err != nil { + t.Fatal(err) + } + if !matched { + t.Errorf("The alignment is not correct: %t, output:%s\n", matched, out) + } + + // build generated file + cmd = exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile) + cmd.Env = append(os.Environ(), "GOARCH=arm64", "GOOS=linux") + out, err = cmd.CombinedOutput() if err != nil { t.Errorf("Build failed: %v, output: %s", err, out) } @@ -56,6 +75,8 @@ func gen(buf *bytes.Buffer) { fmt.Fprintln(buf, "TBZ $5, R0, label") fmt.Fprintln(buf, "CBZ R0, label") fmt.Fprintln(buf, "BEQ label") + fmt.Fprintln(buf, "PCALIGN $128") + fmt.Fprintln(buf, "MOVD $3, R3") for i := 0; i < 1<<19; i++ { fmt.Fprintln(buf, "MOVD R0, R1") } diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index 9dd821d532..ac3621bf74 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -398,6 +398,7 @@ type LSym struct { type FuncInfo struct { Args int32 Locals int32 + Align int32 Text *Prog Autot map[*LSym]struct{} Pcln Pcln diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index 3e97c614b8..cb6b709066 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -350,6 +350,7 @@ func (w *objWriter) writeSym(s *LSym) { w.writeInt(int64(s.Func.Args)) w.writeInt(int64(s.Func.Locals)) + w.writeInt(int64(s.Func.Align)) w.writeBool(s.NoSplit()) flags = int64(0) if s.Leaf() { diff --git a/src/cmd/internal/obj/objfile2.go b/src/cmd/internal/obj/objfile2.go index 95f920eef5..6261924d0d 100644 --- a/src/cmd/internal/obj/objfile2.go +++ b/src/cmd/internal/obj/objfile2.go @@ -244,12 +244,17 @@ func (w *writer) Sym(s *LSym) { if strings.HasPrefix(name, "gofile..") { name = filepath.ToSlash(name) } + var align uint32 + if s.Func != nil { + align = uint32(s.Func.Align) + } o := goobj2.Sym{ - Name: name, - ABI: abi, - Type: uint8(s.Type), - Flag: flag, - Siz: uint32(s.Size), + Name: name, + ABI: abi, + Type: uint8(s.Type), + Flag: flag, + Siz: uint32(s.Size), + Align: align, } o.Write(w.Writer) } diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index bff29fb568..7c4b08a805 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -2256,6 +2256,10 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s *sym.Symbol, va uint6 funcsize = uint64(s.Size) } + if sect.Align < s.Align { + sect.Align = s.Align + } + // On ppc64x a text section should not be larger than 2^26 bytes due to the size of // call target offset field in the bl instruction. Splitting into smaller text // sections smaller than this limit allows the GNU linker to modify the long calls diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 5f128749ab..7cc846a19e 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -1616,6 +1616,9 @@ func (l *Loader) preloadSyms(r *oReader, kind int) { strings.HasPrefix(name, "runtime.gcbits.") { l.SetAttrNotInSymbolTable(gi, true) } + if a := osym.Align(); a != 0 { + l.SetSymAlign(gi, int32(a)) + } } } diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 5e19cb5de1..ed2d3f4495 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -471,3 +471,73 @@ func TestOldLink(t *testing.T) { t.Errorf("%v: %v:\n%s", cmd.Args, err, out) } } + +const testFuncAlignSrc = ` +package main +import ( + "fmt" + "reflect" +) +func alignPc() + +func main() { + addr := reflect.ValueOf(alignPc).Pointer() + if (addr % 512) != 0 { + fmt.Printf("expected 512 bytes alignment, got %v\n", addr) + } else { + fmt.Printf("PASS") + } +} +` + +const testFuncAlignAsmSrc = ` +#include "textflag.h" + +TEXT ·alignPc(SB),NOSPLIT, $0-0 + MOVD $2, R0 + PCALIGN $512 + MOVD $3, R1 + RET +` + +// TestFuncAlign verifies that the address of a function can be aligned +// with a specfic value on arm64. +func TestFuncAlign(t *testing.T) { + if runtime.GOARCH != "arm64" || runtime.GOOS != "linux" { + t.Skip("skipping on non-linux/arm64 platform") + } + testenv.MustHaveGoBuild(t) + + tmpdir, err := ioutil.TempDir("", "TestFuncAlign") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + src := filepath.Join(tmpdir, "falign.go") + err = ioutil.WriteFile(src, []byte(testFuncAlignSrc), 0666) + if err != nil { + t.Fatal(err) + } + src = filepath.Join(tmpdir, "falign.s") + err = ioutil.WriteFile(src, []byte(testFuncAlignAsmSrc), 0666) + if err != nil { + t.Fatal(err) + } + + // Build and run with old object file format. + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "falign") + cmd.Dir = tmpdir + out, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("build failed: %v", err) + } + cmd = exec.Command(tmpdir + "/falign") + out, err = cmd.CombinedOutput() + if err != nil { + t.Errorf("failed to run with err %v, output: %s", err, out) + } + if string(out) != "PASS" { + t.Errorf("unexpected output: %s\n", out) + } +} diff --git a/src/cmd/oldlink/internal/ld/data.go b/src/cmd/oldlink/internal/ld/data.go index 3c78896e45..13f412ccd8 100644 --- a/src/cmd/oldlink/internal/ld/data.go +++ b/src/cmd/oldlink/internal/ld/data.go @@ -2119,6 +2119,10 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s *sym.Symbol, va uint6 funcsize = uint64(s.Size) } + if sect.Align < s.Align { + sect.Align = s.Align + } + // On ppc64x a text section should not be larger than 2^26 bytes due to the size of // call target offset field in the bl instruction. Splitting into smaller text // sections smaller than this limit allows the GNU linker to modify the long calls diff --git a/src/cmd/oldlink/internal/objfile/objfile.go b/src/cmd/oldlink/internal/objfile/objfile.go index 7be433ad40..3a59f6a624 100644 --- a/src/cmd/oldlink/internal/objfile/objfile.go +++ b/src/cmd/oldlink/internal/objfile/objfile.go @@ -316,6 +316,7 @@ overwrite: pc.Args = r.readInt32() pc.Locals = r.readInt32() + s.Align = r.readInt32() if r.readUint8() != 0 { s.Attr |= sym.AttrNoSplit }