[dev.link] cmd/link: stream external relocations on ARM64 and on Darwin

Support streaming external relocations on ARM64. Support
architecture-specific relocations.

Also support streaming external relocations on Darwin. Do it in
the same CL so ARM64's archreloc doesn't need to support both
streaming and non-streaming.

Change-Id: Ia7fee9957892f98c065022c69a51f47402f4d6e2
Reviewed-on: https://go-review.googlesource.com/c/go/+/243644
Reviewed-by: Jeremy Faller <jeremy@golang.org>
diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go
index 2d964e9..093aadb 100644
--- a/src/cmd/link/internal/arm64/asm.go
+++ b/src/cmd/link/internal/arm64/asm.go
@@ -456,15 +456,14 @@
 
 			// set up addend for eventual relocation via outer symbol.
 			rs, off := ld.FoldSubSymbolOffset(ldr, rs)
-			rr.Xadd = r.Add() + off
+			xadd := r.Add() + off
 			rst := ldr.SymType(rs)
 			if rst != sym.SHOSTOBJ && rst != sym.SDYNIMPORT && ldr.SymSect(rs) == nil {
 				ldr.Errorf(s, "missing section for %s", ldr.SymName(rs))
 			}
-			rr.Xsym = rs
 
 			nExtReloc = 2 // need two ELF/Mach-O relocations. see elfreloc1/machoreloc1
-			if target.IsDarwin() && rt == objabi.R_ADDRARM64 && rr.Xadd != 0 {
+			if target.IsDarwin() && rt == objabi.R_ADDRARM64 && xadd != 0 {
 				nExtReloc = 4 // need another two relocations for non-zero addend
 			}
 
@@ -488,9 +487,8 @@
 				// can only encode 24-bit of signed addend, but the instructions
 				// supports 33-bit of signed addend, so we always encode the
 				// addend in place.
-				o0 |= (uint32((rr.Xadd>>12)&3) << 29) | (uint32((rr.Xadd>>12>>2)&0x7ffff) << 5)
-				o1 |= uint32(rr.Xadd&0xfff) << 10
-				rr.Xadd = 0
+				o0 |= (uint32((xadd>>12)&3) << 29) | (uint32((xadd>>12>>2)&0x7ffff) << 5)
+				o1 |= uint32(xadd&0xfff) << 10
 
 				// when laid out, the instruction order must always be o1, o2.
 				if target.IsBigEndian() {
@@ -508,8 +506,6 @@
 			if rt == objabi.R_ARM64_TLS_IE {
 				nExtReloc = 2 // need two ELF relocations. see elfreloc1
 			}
-			rr.Xsym = rs
-			rr.Xadd = r.Add()
 			return val, nExtReloc, isOk
 		}
 	}
@@ -693,6 +689,42 @@
 	return -1
 }
 
+func extreloc(target *ld.Target, ldr *loader.Loader, r loader.Reloc2, s loader.Sym) (loader.ExtReloc, bool) {
+	rs := ldr.ResolveABIAlias(r.Sym())
+	var rr loader.ExtReloc
+	switch rt := r.Type(); rt {
+	case objabi.R_ARM64_GOTPCREL,
+		objabi.R_ADDRARM64:
+
+		// set up addend for eventual relocation via outer symbol.
+		rs, off := ld.FoldSubSymbolOffset(ldr, rs)
+		rr.Xadd = r.Add() + off
+		rr.Xsym = rs
+
+		// Note: ld64 currently has a bug that any non-zero addend for BR26 relocation
+		// will make the linking fail because it thinks the code is not PIC even though
+		// the BR26 relocation should be fully resolved at link time.
+		// That is the reason why the next if block is disabled. When the bug in ld64
+		// is fixed, we can enable this block and also enable duff's device in cmd/7g.
+		if false && target.IsDarwin() {
+			// Mach-O wants the addend to be encoded in the instruction
+			// Note that although Mach-O supports ARM64_RELOC_ADDEND, it
+			// can only encode 24-bit of signed addend, but the instructions
+			// supports 33-bit of signed addend, so we always encode the
+			// addend in place.
+			rr.Xadd = 0
+		}
+		return rr, true
+	case objabi.R_CALLARM64,
+		objabi.R_ARM64_TLS_LE,
+		objabi.R_ARM64_TLS_IE:
+		rr.Xsym = rs
+		rr.Xadd = r.Add()
+		return rr, true
+	}
+	return rr, false
+}
+
 func elfsetupplt(ctxt *ld.Link, plt, gotplt *loader.SymbolBuilder, dynamic loader.Sym) {
 	if plt.Size() == 0 {
 		// stp     x16, x30, [sp, #-16]!
diff --git a/src/cmd/link/internal/arm64/obj.go b/src/cmd/link/internal/arm64/obj.go
index e7c2397..37b72b6 100644
--- a/src/cmd/link/internal/arm64/obj.go
+++ b/src/cmd/link/internal/arm64/obj.go
@@ -50,6 +50,7 @@
 		Archinit:         archinit,
 		Archreloc:        archreloc,
 		Archrelocvariant: archrelocvariant,
+		Extreloc:         extreloc,
 		Elfreloc1:        elfreloc1,
 		ElfrelocSize:     24,
 		Elfsetupplt:      elfsetupplt,
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index d5034ae..26bad1b 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -264,7 +264,7 @@
 				o = int64(target.Arch.ByteOrder.Uint64(P[off:]))
 			}
 			var rp *loader.ExtReloc
-			if target.IsExternal() {
+			if target.IsExternal() && !target.StreamExtRelocs() {
 				// Don't pass &rr directly to Archreloc, which will escape rr
 				// even if this case is not taken. Instead, as Archreloc will
 				// likely return true, we speculatively add rr to extRelocs
@@ -274,12 +274,16 @@
 			}
 			out, nExtReloc, ok := thearch.Archreloc(target, ldr, syms, r, rp, s, o)
 			if target.IsExternal() {
-				if nExtReloc == 0 {
-					// No external relocation needed. Speculation failed. Undo the append.
-					extRelocs = extRelocs[:len(extRelocs)-1]
+				if target.StreamExtRelocs() {
+					extraExtReloc += nExtReloc
 				} else {
-					// Account for the difference between host relocations and Go relocations.
-					extraExtReloc += nExtReloc - 1
+					if nExtReloc == 0 {
+						// No external relocation needed. Speculation failed. Undo the append.
+						extRelocs = extRelocs[:len(extRelocs)-1]
+					} else {
+						// Account for the difference between host relocations and Go relocations.
+						extraExtReloc += nExtReloc - 1
+					}
 				}
 			}
 			needExtReloc = false // already appended
@@ -638,8 +642,7 @@
 
 	switch rt {
 	default:
-		// TODO: handle arch-specific relocations
-		panic("unsupported")
+		return thearch.Extreloc(&target, ldr, r, s)
 
 	case objabi.R_TLS_LE, objabi.R_TLS_IE:
 		if target.IsElf() {
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index a489da0..e45458d 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -239,6 +239,11 @@
 	Asmb  func(*Link, *loader.Loader)
 	Asmb2 func(*Link, *loader.Loader)
 
+	// Extreloc is an arch-specific hook that converts a Go relocation to an
+	// external relocation. Return the external relocation and whether it is
+	// needed.
+	Extreloc func(*Target, *loader.Loader, loader.Reloc2, loader.Sym) (loader.ExtReloc, bool)
+
 	Elfreloc1      func(*Link, *OutBuf, *loader.Loader, loader.Sym, loader.ExtRelocView, int64) bool
 	ElfrelocSize   uint32 // size of an ELF relocation record, must match Elfreloc1.
 	Elfsetupplt    func(ctxt *Link, plt, gotplt *loader.SymbolBuilder, dynamic loader.Sym)
diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
index 4a0bb5d..1089d30 100644
--- a/src/cmd/link/internal/ld/macho.go
+++ b/src/cmd/link/internal/ld/macho.go
@@ -1045,17 +1045,25 @@
 		if ldr.SymValue(s) >= int64(eaddr) {
 			break
 		}
-		relocs := ldr.ExtRelocs(s)
+
+		// Compute external relocations on the go, and pass to Machoreloc1
+		// to stream out.
+		relocs := ldr.Relocs(s)
 		for ri := 0; ri < relocs.Count(); ri++ {
-			r := relocs.At(ri)
-			if r.Xsym == 0 {
+			r := relocs.At2(ri)
+			rr, ok := extreloc(ctxt, ldr, s, r, ri)
+			if !ok {
+				continue
+			}
+			if rr.Xsym == 0 {
 				ldr.Errorf(s, "missing xsym in relocation")
 				continue
 			}
-			if !ldr.AttrReachable(r.Xsym) {
-				ldr.Errorf(s, "unreachable reloc %d (%s) target %v", r.Type(), sym.RelocName(ctxt.Arch, r.Type()), ldr.SymName(r.Xsym))
+			if !ldr.AttrReachable(rr.Xsym) {
+				ldr.Errorf(s, "unreachable reloc %d (%s) target %v", r.Type(), sym.RelocName(ctxt.Arch, r.Type()), ldr.SymName(rr.Xsym))
 			}
-			if !thearch.Machoreloc1(ctxt.Arch, out, ldr, s, r, int64(uint64(ldr.SymValue(s)+int64(r.Off()))-sect.Vaddr)) {
+			rv := loader.ExtRelocView{Reloc2: r, ExtReloc: rr}
+			if !thearch.Machoreloc1(ctxt.Arch, out, ldr, s, rv, int64(uint64(ldr.SymValue(s)+int64(r.Off()))-sect.Vaddr)) {
 				ldr.Errorf(s, "unsupported obj reloc %d (%s)/%d to %s", r.Type(), sym.RelocName(ctxt.Arch, r.Type()), r.Siz(), ldr.SymName(r.Sym()))
 			}
 		}
diff --git a/src/cmd/link/internal/ld/target.go b/src/cmd/link/internal/ld/target.go
index 8702db1..d075bce 100644
--- a/src/cmd/link/internal/ld/target.go
+++ b/src/cmd/link/internal/ld/target.go
@@ -184,5 +184,5 @@
 
 // Temporary helper.
 func (t *Target) StreamExtRelocs() bool {
-	return t.IsELF && (t.IsAMD64() || t.Is386())
+	return (t.IsELF || t.IsDarwin()) && (t.IsAMD64() || t.Is386() || t.IsARM64())
 }