diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go
index f514ce104a0bef9e320fd17809cd3b1d8ebaa9fa..fe515aafbf30b6f3ccd1903718a137d0255703bd 100644
--- a/src/cmd/compile/internal/base/flag.go
+++ b/src/cmd/compile/internal/base/flag.go
@@ -213,6 +213,8 @@ func ParseFlags() {
 		Flag.CompilingRuntime = true
 	}
 
+	Ctxt.Std = Flag.Std
+
 	// Three inputs govern loop iteration variable rewriting, hash, experiment, flag.
 	// The loop variable rewriting is:
 	// IF non-empty hash, then hash determines behavior (function+line match) (*)
diff --git a/src/cmd/compile/internal/liveness/plive.go b/src/cmd/compile/internal/liveness/plive.go
index dd48d10bc5755ed5d40cc8161f5e937d680c4348..1a36035f46d68850490efdfb13b60b3e1e1d14a1 100644
--- a/src/cmd/compile/internal/liveness/plive.go
+++ b/src/cmd/compile/internal/liveness/plive.go
@@ -1551,6 +1551,7 @@ func WriteFuncMap(fn *ir.Func, abiInfo *abi.ABIParamResultInfo) {
 		nbitmap = 2
 	}
 	lsym := base.Ctxt.Lookup(fn.LSym.Name + ".args_stackmap")
+	lsym.Set(obj.AttrLinkname, true) // allow args_stackmap referenced from assembly
 	off := objw.Uint32(lsym, 0, uint32(nbitmap))
 	off = objw.Uint32(lsym, off, uint32(bv.N))
 	off = objw.BitVec(lsym, off, bv)
diff --git a/src/cmd/compile/internal/ssagen/abi.go b/src/cmd/compile/internal/ssagen/abi.go
index 5c4a8aff69c45d3373cdec56d9509bfc7edd8f9a..d5ae3b179316bba942378cf4154ed2da40e445a2 100644
--- a/src/cmd/compile/internal/ssagen/abi.go
+++ b/src/cmd/compile/internal/ssagen/abi.go
@@ -148,6 +148,11 @@ func (s *SymABIs) GenABIWrappers() {
 			// offsets to dispatch arguments, which currently using ABI0
 			// frame layout. Pin it to ABI0.
 			fn.ABI = obj.ABI0
+			// Propagate linkname attribute, which was set on the ABIInternal
+			// symbol.
+			if sym.Linksym().IsLinkname() {
+				sym.LinksymABI(fn.ABI).Set(obj.AttrLinkname, true)
+			}
 		}
 
 		// If cgo-exported, add the definition ABI to the cgo
diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go
index 5e5d53903395cf4ed9c374d7529532c73372dcbc..a5ce22c0c363aebe11cd508f4079bb197f16e051 100644
--- a/src/cmd/go/go_test.go
+++ b/src/cmd/go/go_test.go
@@ -1058,7 +1058,7 @@ func TestGoListDeps(t *testing.T) {
 	if runtime.Compiler != "gccgo" {
 		// Check the list is in dependency order.
 		tg.run("list", "-deps", "math")
-		want := "internal/cpu\nunsafe\nmath/bits\nmath\n"
+		want := "unsafe\ninternal/cpu\nmath/bits\nmath\n"
 		out := tg.stdout.String()
 		if !strings.Contains(out, "internal/cpu") {
 			// Some systems don't use internal/cpu.
diff --git a/src/cmd/internal/goobj/objfile.go b/src/cmd/internal/goobj/objfile.go
index fb87b044122848946b3d82154f0d04fb6e43dba3..56ce76ad09fc8a98d5208878e0dd8060905426e1 100644
--- a/src/cmd/internal/goobj/objfile.go
+++ b/src/cmd/internal/goobj/objfile.go
@@ -284,6 +284,7 @@ const (
 	_                               // was ObjFlagNeedNameExpansion
 	ObjFlagFromAssembly             // object is from asm src, not go
 	ObjFlagUnlinkable               // unlinkable package (linker will emit an error)
+	ObjFlagStd                      // standard library package
 )
 
 // Sym.Flag
@@ -304,6 +305,7 @@ const (
 	SymFlagDict
 	SymFlagPkgInit
 	SymFlagLinkname
+	SymFlagABIWrapper
 )
 
 // Returns the length of the name of the symbol.
@@ -336,6 +338,7 @@ func (s *Sym) IsItab() bool        { return s.Flag2()&SymFlagItab != 0 }
 func (s *Sym) IsDict() bool        { return s.Flag2()&SymFlagDict != 0 }
 func (s *Sym) IsPkgInit() bool     { return s.Flag2()&SymFlagPkgInit != 0 }
 func (s *Sym) IsLinkname() bool    { return s.Flag2()&SymFlagLinkname != 0 }
+func (s *Sym) ABIWrapper() bool    { return s.Flag2()&SymFlagABIWrapper != 0 }
 
 func (s *Sym) SetName(x string, w *Writer) {
 	binary.LittleEndian.PutUint32(s[:], uint32(len(x)))
@@ -882,3 +885,4 @@ func (r *Reader) Flags() uint32 {
 func (r *Reader) Shared() bool       { return r.Flags()&ObjFlagShared != 0 }
 func (r *Reader) FromAssembly() bool { return r.Flags()&ObjFlagFromAssembly != 0 }
 func (r *Reader) Unlinkable() bool   { return r.Flags()&ObjFlagUnlinkable != 0 }
+func (r *Reader) Std() bool          { return r.Flags()&ObjFlagStd != 0 }
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 38869f0f478cd66a4fb064286a3c4af1d09c7e5c..647a459d595573acf424c6dd6c291a0994b35761 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -1048,6 +1048,7 @@ type Link struct {
 	InParallel    bool // parallel backend phase in effect
 	UseBASEntries bool // use Base Address Selection Entries in location lists and PC ranges
 	IsAsm         bool // is the source assembly language, which may contain surprising idioms (e.g., call tables)
+	Std           bool // is standard library package
 
 	// state for writing objects
 	Text []*LSym
diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go
index 648aae4fa20ff0a8a2557dfae86952b519fbf18d..2ed98cb5778a72aab2ba19c9655bb9fa5d7ee778 100644
--- a/src/cmd/internal/obj/objfile.go
+++ b/src/cmd/internal/obj/objfile.go
@@ -57,6 +57,9 @@ func WriteObjFile(ctxt *Link, b *bio.Writer) {
 	if ctxt.IsAsm {
 		flags |= goobj.ObjFlagFromAssembly
 	}
+	if ctxt.Std {
+		flags |= goobj.ObjFlagStd
+	}
 	h := goobj.Header{
 		Magic:       goobj.Magic,
 		Fingerprint: ctxt.Fingerprint,
@@ -309,6 +312,7 @@ func (w *writer) StringTable() {
 const cutoff = int64(2e9) // 2 GB (or so; looks better in errors than 2^31)
 
 func (w *writer) Sym(s *LSym) {
+	name := s.Name
 	abi := uint16(s.ABI())
 	if s.Static() {
 		abi = goobj.SymABIstatic
@@ -348,10 +352,15 @@ func (w *writer) Sym(s *LSym) {
 	if s.IsPkgInit() {
 		flag2 |= goobj.SymFlagPkgInit
 	}
-	if s.IsLinkname() || w.ctxt.IsAsm { // assembly reference is treated the same as linkname
+	if s.IsLinkname() || (w.ctxt.IsAsm && name != "") || name == "main.main" {
+		// Assembly reference is treated the same as linkname,
+		// but not for unnamed (aux) symbols.
+		// The runtime linknames main.main.
 		flag2 |= goobj.SymFlagLinkname
 	}
-	name := s.Name
+	if s.ABIWrapper() {
+		flag2 |= goobj.SymFlagABIWrapper
+	}
 	if strings.HasPrefix(name, "gofile..") {
 		name = filepath.ToSlash(name)
 	}
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index cb0961eaefb2c89256d824b6170c8401c93301ea..11df3a466d43d86a7380cc0d2adac6ed6988e535 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -518,6 +518,9 @@ func (ctxt *Link) findLibPath(libname string) string {
 
 func (ctxt *Link) loadlib() {
 	var flags uint32
+	if *flagCheckLinkname {
+		flags |= loader.FlagCheckLinkname
+	}
 	switch *FlagStrictDups {
 	case 0:
 		// nothing to do
diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go
index 8a67ccfb32afffee063eb8d0117950fc5f3d7a16..e6608fd791aab710045e36c5e689c8341e4aa98b 100644
--- a/src/cmd/link/internal/ld/main.go
+++ b/src/cmd/link/internal/ld/main.go
@@ -96,6 +96,7 @@ var (
 	FlagS             = flag.Bool("s", false, "disable symbol table")
 	flag8             bool // use 64-bit addresses in symbol table
 	flagInterpreter   = flag.String("I", "", "use `linker` as ELF dynamic linker")
+	flagCheckLinkname = flag.Bool("checklinkname", false, "check linkname symbol references")
 	FlagDebugTramp    = flag.Int("debugtramp", 0, "debug trampolines")
 	FlagDebugTextSize = flag.Int("debugtextsize", 0, "debug text section max size")
 	flagDebugNosplit  = flag.Bool("debugnosplit", false, "dump nosplit call graph")
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index 53ebb53a75943ed3969961267ade4ff506319766..0a76c1fb0c6cd2416e75ed1620bc26e00db2c487 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -292,6 +292,7 @@ type extSymPayload struct {
 const (
 	// Loader.flags
 	FlagStrictDups = 1 << iota
+	FlagCheckLinkname
 )
 
 func NewLoader(flags uint32, reporter *ErrorReporter) *Loader {
@@ -421,14 +422,6 @@ func (st *loadState) addSym(name string, ver int, r *oReader, li uint32, kind in
 	}
 
 	// Non-package (named) symbol.
-	if osym.IsLinkname() && r.DataSize(li) == 0 {
-		// This is a linknamed "var" "reference" (var x T with no data and //go:linkname x).
-		// Check if a linkname reference is allowed.
-		// Only check references (pull), not definitions (push, with non-zero size),
-		// so push is always allowed.
-		// Linkname is always a non-package reference.
-		checkLinkname(r.unit.Lib.Pkg, name)
-	}
 	// Check if it already exists.
 	oldi, existed := l.symsByName[ver][name]
 	if !existed {
@@ -2154,6 +2147,14 @@ type loadState struct {
 	l            *Loader
 	hashed64Syms map[uint64]symAndSize         // short hashed (content-addressable) symbols, keyed by content hash
 	hashedSyms   map[goobj.HashType]symAndSize // hashed (content-addressable) symbols, keyed by content hash
+
+	linknameVarRefs []linknameVarRef // linknamed var refererces
+}
+
+type linknameVarRef struct {
+	pkg  string // package of reference (not definition)
+	name string
+	sym  Sym
 }
 
 // Preload symbols of given kind from an object.
@@ -2188,6 +2189,19 @@ func (st *loadState) preloadSyms(r *oReader, kind int) {
 		}
 		gi := st.addSym(name, v, r, i, kind, osym)
 		r.syms[i] = gi
+		if kind == nonPkgDef && osym.IsLinkname() && r.DataSize(i) == 0 && strings.Contains(name, ".") {
+			// This is a linknamed "var" "reference" (var x T with no data and //go:linkname x).
+			// We want to check if a linkname reference is allowed. Here we haven't loaded all
+			// symbol definitions, so we don't yet know all the push linknames. So we add to a
+			// list and check later after all symbol defs are loaded. Linknamed vars are rare,
+			// so this list won't be long.
+			// Only check references (pull), not definitions (push, with non-zero size),
+			// so push is always allowed.
+			// This use of linkname is usually for referencing C symbols, so allow symbols
+			// with no "." in its name (not a regular Go symbol).
+			// Linkname is always a non-package reference.
+			st.linknameVarRefs = append(st.linknameVarRefs, linknameVarRef{r.unit.Lib.Pkg, name, gi})
+		}
 		if osym.Local() {
 			l.SetAttrLocal(gi, true)
 		}
@@ -2237,6 +2251,9 @@ func (l *Loader) LoadSyms(arch *sys.Arch) {
 		st.preloadSyms(r, hashedDef)
 		st.preloadSyms(r, nonPkgDef)
 	}
+	for _, vr := range st.linknameVarRefs {
+		l.checkLinkname(vr.pkg, vr.name, vr.sym)
+	}
 	l.nhashedsyms = len(st.hashed64Syms) + len(st.hashedSyms)
 	for _, r := range l.objs[goObjStart:] {
 		loadObjRefs(l, r, arch)
@@ -2252,15 +2269,15 @@ func loadObjRefs(l *Loader, r *oReader, arch *sys.Arch) {
 		osym := r.Sym(ndef + i)
 		name := osym.Name(r.Reader)
 		v := abiToVer(osym.ABI(), r.version)
+		gi := l.LookupOrCreateSym(name, v)
+		r.syms[ndef+i] = gi
 		if osym.IsLinkname() {
 			// Check if a linkname reference is allowed.
 			// Only check references (pull), not definitions (push),
 			// so push is always allowed.
 			// Linkname is always a non-package reference.
-			checkLinkname(r.unit.Lib.Pkg, name)
+			l.checkLinkname(r.unit.Lib.Pkg, name, gi)
 		}
-		r.syms[ndef+i] = l.LookupOrCreateSym(name, v)
-		gi := r.syms[ndef+i]
 		if osym.Local() {
 			l.SetAttrLocal(gi, true)
 		}
@@ -2307,30 +2324,27 @@ func abiToVer(abi uint16, localSymVersion int) int {
 
 // A list of blocked linknames. Some linknames are allowed only
 // in specific packages. This maps symbol names to allowed packages.
-// If a name is not in this map, and not with a blocked prefix (see
-// blockedLinknamePrefixes), it is allowed everywhere.
-// If a name is in this map, it is allowed only in listed packages.
+// If a name is not in this map, it is allowed iff the definition
+// has a linkname (push).
+// If a name is in this map, it is allowed only in listed packages,
+// even if it has a linknamed definition.
 var blockedLinknames = map[string][]string{
 	// coroutines
-	"runtime.coroexit":   nil,
-	"runtime.corostart":  nil,
 	"runtime.coroswitch": {"iter"},
 	"runtime.newcoro":    {"iter"},
 	// weak references
 	"internal/weak.runtime_registerWeakPointer": {"internal/weak"},
 	"internal/weak.runtime_makeStrongFromWeak":  {"internal/weak"},
-	"runtime.getOrAddWeakHandle":                nil,
 }
 
-// A list of blocked linkname prefixes (packages).
-var blockedLinknamePrefixes = []string{
-	"internal/weak.",
-	"internal/concurrent.",
-}
+// check if a linkname reference to symbol s from pkg is allowed
+func (l *Loader) checkLinkname(pkg, name string, s Sym) {
+	if l.flags&FlagCheckLinkname == 0 {
+		return
+	}
 
-func checkLinkname(pkg, name string) {
 	error := func() {
-		log.Fatalf("linkname or assembly reference of %s is not allowed in package %s", name, pkg)
+		log.Fatalf("%s: invalid reference to %s", pkg, name)
 	}
 	pkgs, ok := blockedLinknames[name]
 	if ok {
@@ -2341,11 +2355,26 @@ func checkLinkname(pkg, name string) {
 		}
 		error()
 	}
-	for _, p := range blockedLinknamePrefixes {
-		if strings.HasPrefix(name, p) {
-			error()
-		}
+	r, li := l.toLocal(s)
+	if r == l.extReader { // referencing external symbol is okay
+		return
+	}
+	if !r.Std() { // For now, only check for symbols defined in std
+		return
+	}
+	if r.unit.Lib.Pkg == pkg { // assembly reference from same package
+		return
+	}
+	osym := r.Sym(li)
+	if osym.IsLinkname() || osym.ABIWrapper() {
+		// Allow if the def has a linkname (push).
+		// ABI wrapper usually wraps an assembly symbol, a linknamed symbol,
+		// or an external symbol, or provide access of a Go symbol to assembly.
+		// For now, allow ABI wrappers.
+		// TODO: check the wrapped symbol?
+		return
 	}
+	error()
 }
 
 // TopLevelSym tests a symbol (by name and kind) to determine whether
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
index 3abec64c5d20e4c3c1b1b922bba45875fa73c22a..1ce484fe612e974a0fce8bfb4d22b2d4a121fb71 100644
--- a/src/cmd/link/link_test.go
+++ b/src/cmd/link/link_test.go
@@ -1416,7 +1416,7 @@ func TestRandLayout(t *testing.T) {
 	}
 }
 
-func TestBlockedLinkname(t *testing.T) {
+func TestCheckLinkname(t *testing.T) {
 	// Test that code containing blocked linknames does not build.
 	testenv.MustHaveGoBuild(t)
 	t.Parallel()
@@ -1433,10 +1433,13 @@ func TestBlockedLinkname(t *testing.T) {
 		{"push.go", true},
 		// pull linkname of blocked symbol is not ok
 		{"coro.go", false},
-		{"weak.go", false},
 		{"coro_var.go", false},
 		// assembly reference is not ok
 		{"coro_asm", false},
+		// pull-only linkname is not ok
+		{"coro2.go", false},
+		// legacy bad linkname is ok, for now
+		{"fastrand.go", true},
 	}
 	for _, test := range tests {
 		test := test
@@ -1444,7 +1447,7 @@ func TestBlockedLinkname(t *testing.T) {
 			t.Parallel()
 			src := filepath.Join("testdata", "linkname", test.src)
 			exe := filepath.Join(tmpdir, test.src+".exe")
-			cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src)
+			cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-checklinkname=1", "-o", exe, src)
 			out, err := cmd.CombinedOutput()
 			if test.ok && err != nil {
 				t.Errorf("build failed unexpectedly: %v:\n%s", err, out)
diff --git a/src/cmd/link/testdata/linkname/coro2.go b/src/cmd/link/testdata/linkname/coro2.go
new file mode 100644
index 0000000000000000000000000000000000000000..ae47147670cfe74d2e6a8403c341019f00764426
--- /dev/null
+++ b/src/cmd/link/testdata/linkname/coro2.go
@@ -0,0 +1,17 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Linkname corostart is not allowed, as it doesn't have
+// a linknamed definition.
+
+package main
+
+import _ "unsafe"
+
+//go:linkname corostart runtime.corostart
+func corostart()
+
+func main() {
+	corostart()
+}
diff --git a/src/cmd/link/testdata/linkname/fastrand.go b/src/cmd/link/testdata/linkname/fastrand.go
new file mode 100644
index 0000000000000000000000000000000000000000..ce51e2a7f399b2c21add1f6f33436e7678fbc2a8
--- /dev/null
+++ b/src/cmd/link/testdata/linkname/fastrand.go
@@ -0,0 +1,18 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Linkname fastrand is allowed _for now_, as it has a
+// linknamed definition, for legacy reason.
+// NOTE: this may not be allowed in the future. Don't do this!
+
+package main
+
+import _ "unsafe"
+
+//go:linkname fastrand runtime.fastrand
+func fastrand() uint32
+
+func main() {
+	println(fastrand())
+}
diff --git a/src/cmd/link/testdata/linkname/weak.go b/src/cmd/link/testdata/linkname/weak.go
deleted file mode 100644
index 2bf0fbcbab1d0f891d727b24a65b50987c3fa7df..0000000000000000000000000000000000000000
--- a/src/cmd/link/testdata/linkname/weak.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2024 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Linkname generic functions in internal/weak is not
-// allowed; legitimate instantiation is ok.
-
-package main
-
-import (
-	"unique"
-	"unsafe"
-)
-
-//go:linkname weakMake internal/weak.Make[string]
-func weakMake(string) unsafe.Pointer
-
-func main() {
-	h := unique.Make("xxx")
-	println(h.Value())
-	weakMake("xxx")
-}
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 4e8f1c9109b7d298b95071b4865236f62bad68be..ee53b3114031f602929798a2b6bc2ea364acc0ce 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -42,7 +42,7 @@ var depsRules = `
 	< cmp, container/list, container/ring,
 	  internal/cfg, internal/coverage, internal/coverage/rtcov,
 	  internal/coverage/uleb128, internal/coverage/calloc,
-	  internal/cpu, internal/goarch, internal/godebugs,
+	  internal/goarch, internal/godebugs,
 	  internal/goexperiment, internal/goos, internal/byteorder,
 	  internal/goversion, internal/nettrace, internal/platform,
 	  internal/trace/traceviewer/format,
@@ -55,7 +55,7 @@ var depsRules = `
 
 	internal/byteorder, internal/goarch, unsafe < internal/chacha8rand;
 
-	unsafe < maps;
+	unsafe < internal/cpu, maps;
 
 	# RUNTIME is the core runtime group of packages, all of them very light-weight.
 	internal/abi,
diff --git a/src/go/types/api.go b/src/go/types/api.go
index 2db67e5329cf1589f014901898cfdf410de8e7a8..dea974bec80918ed9816e9e4c2690cde1d545868 100644
--- a/src/go/types/api.go
+++ b/src/go/types/api.go
@@ -36,6 +36,7 @@ import (
 	"go/constant"
 	"go/token"
 	. "internal/types/errors"
+	_ "unsafe" // for linkname
 )
 
 // An Error describes a type-checking error; it implements the error interface.
@@ -192,6 +193,9 @@ type Config struct {
 	_EnableAlias bool
 }
 
+// Linkname for use from srcimporter.
+//go:linkname srcimporter_setUsesCgo
+
 func srcimporter_setUsesCgo(conf *Config) {
 	conf.go115UsesCgo = true
 }
diff --git a/src/internal/cpu/cpu.go b/src/internal/cpu/cpu.go
index d794e53ceeef3e3ea25b2251abade08e3f7ef1e2..9be280c6baf0fb209eac040318f3c102d24b3b6e 100644
--- a/src/internal/cpu/cpu.go
+++ b/src/internal/cpu/cpu.go
@@ -6,6 +6,8 @@
 // used by the Go standard library.
 package cpu
 
+import _ "unsafe" // for linkname
+
 // DebugOptions is set to true by the runtime if the OS supports reading
 // GODEBUG early in runtime startup.
 // This should not be changed after it is initialized.
@@ -121,6 +123,14 @@ var S390X struct {
 	_         CacheLinePad
 }
 
+// CPU feature variables are accessed by assembly code in various packages.
+//go:linkname X86
+//go:linkname ARM
+//go:linkname ARM64
+//go:linkname MIPS64X
+//go:linkname PPC64
+//go:linkname S390X
+
 // Initialize examines the processor and sets the relevant variables above.
 // This is called by the runtime package early in program initialization,
 // before normal init functions are run. env is set by runtime if the OS supports
diff --git a/src/internal/runtime/atomic/atomic_386.go b/src/internal/runtime/atomic/atomic_386.go
index e74dcaa92dd3f06364b72628f65d92bb7e3a2bf7..a023baddb764fe90e38a0e4e00ec332c16eb0615 100644
--- a/src/internal/runtime/atomic/atomic_386.go
+++ b/src/internal/runtime/atomic/atomic_386.go
@@ -12,6 +12,7 @@ import "unsafe"
 //
 //go:linkname Load
 //go:linkname Loadp
+//go:linkname LoadAcquintptr
 
 //go:nosplit
 //go:noinline
diff --git a/src/internal/runtime/atomic/atomic_arm.go b/src/internal/runtime/atomic/atomic_arm.go
index 567e95124480b4d99d28d17e53e1938df4fc42a4..b58f643ca34c1ce34b81a89a8718211c39bf0aa0 100644
--- a/src/internal/runtime/atomic/atomic_arm.go
+++ b/src/internal/runtime/atomic/atomic_arm.go
@@ -19,6 +19,7 @@ const (
 //
 //go:linkname Xchg
 //go:linkname Xchguintptr
+//go:linkname Xadd
 
 type spinlock struct {
 	v uint32
diff --git a/src/internal/runtime/atomic/atomic_wasm.go b/src/internal/runtime/atomic/atomic_wasm.go
index 835fc43ccf9f68787c414faaa33cbaca52fb0575..d1dcfec7adde9f75fcf068c19de7fc013ccc8b47 100644
--- a/src/internal/runtime/atomic/atomic_wasm.go
+++ b/src/internal/runtime/atomic/atomic_wasm.go
@@ -13,6 +13,7 @@
 //go:linkname Loadint32
 //go:linkname Loadint64
 //go:linkname Loaduintptr
+//go:linkname LoadAcquintptr
 //go:linkname Xadd
 //go:linkname Xaddint32
 //go:linkname Xaddint64
@@ -33,6 +34,7 @@
 //go:linkname Storeint32
 //go:linkname Storeint64
 //go:linkname Storeuintptr
+//go:linkname StoreReluintptr
 
 package atomic
 
diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go
index 1a8145355955efc2a503167bb2de8f5ba8a3670b..f98e05bd1d86af4d64949f8e014792428db83dce 100644
--- a/src/net/textproto/reader.go
+++ b/src/net/textproto/reader.go
@@ -14,6 +14,7 @@ import (
 	"strconv"
 	"strings"
 	"sync"
+	_ "unsafe" // for linkname
 )
 
 // TODO: This should be a distinguishable error (ErrMessageTooLarge)
@@ -501,6 +502,9 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) {
 	return readMIMEHeader(r, math.MaxInt64, math.MaxInt64)
 }
 
+// readMIMEHeader is accessed from mime/multipart.
+//go:linkname readMIMEHeader
+
 // readMIMEHeader is a version of ReadMIMEHeader which takes a limit on the header size.
 // It is called by the mime/multipart package.
 func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error) {
diff --git a/src/os/executable_darwin.go b/src/os/executable_darwin.go
index dae9f4ee18ecb96aa32712d1375a8dd2cc8fb6d0..2bb50ab3fef4bb1939e8e86b78f623647480ca31 100644
--- a/src/os/executable_darwin.go
+++ b/src/os/executable_darwin.go
@@ -4,8 +4,12 @@
 
 package os
 
-import "errors"
+import (
+	"errors"
+	_ "unsafe" // for linkname
+)
 
+//go:linkname executablePath
 var executablePath string // set by ../runtime/os_darwin.go
 
 var initCwd, initCwdErr = Getwd()
diff --git a/src/os/executable_solaris.go b/src/os/executable_solaris.go
index b145980c5656b625cd47388eaa4ff0e32d5dedf0..8ee897f4b0e5698d79e6f5baa990674827157a01 100644
--- a/src/os/executable_solaris.go
+++ b/src/os/executable_solaris.go
@@ -4,8 +4,12 @@
 
 package os
 
-import "syscall"
+import (
+	"syscall"
+	_ "unsafe" // for linkname
+)
 
+//go:linkname executablePath
 var executablePath string // set by sysauxv in ../runtime/os3_solaris.go
 
 var initCwd, initCwdErr = Getwd()
diff --git a/src/runtime/coro.go b/src/runtime/coro.go
index 98e789f133a45c305f6f7899cbc0d89a76c7a63c..b2bc8019409095930be8b3f507d20f47ec1d0618 100644
--- a/src/runtime/coro.go
+++ b/src/runtime/coro.go
@@ -46,8 +46,6 @@ func newcoro(f func(*coro)) *coro {
 	return c
 }
 
-//go:linkname corostart
-
 // corostart is the entry func for a new coroutine.
 // It runs the coroutine user function f passed to corostart
 // and then calls coroexit to remove the extra concurrency.
diff --git a/src/runtime/coverage/emit.go b/src/runtime/coverage/emit.go
index 6fe04daea8d0a427c4d01e888974fa2e4e1f4b88..6510c889eaa5b55cc1dfcfc152258e010818d7e2 100644
--- a/src/runtime/coverage/emit.go
+++ b/src/runtime/coverage/emit.go
@@ -574,6 +574,9 @@ func (s *emitState) emitCounterDataFile(finalHash [16]byte, w io.Writer) error {
 	return nil
 }
 
+// markProfileEmitted is injected to testmain via linkname.
+//go:linkname markProfileEmitted
+
 // markProfileEmitted signals the runtime/coverage machinery that
 // coverage data output files have already been written out, and there
 // is no need to take any additional action at exit time. This
diff --git a/src/runtime/coverage/testsupport.go b/src/runtime/coverage/testsupport.go
index 4b00f3a0f7138cc12a0750fb82ce95916fb9e2f0..b673d3cd2c27b93ef0114b7b096ad22e4c0771ac 100644
--- a/src/runtime/coverage/testsupport.go
+++ b/src/runtime/coverage/testsupport.go
@@ -22,6 +22,9 @@ import (
 	"unsafe"
 )
 
+// processCoverTestDir is injected in testmain.
+//go:linkname processCoverTestDir
+
 // processCoverTestDir is called (via a linknamed reference) from
 // testmain code when "go test -cover" is in effect. It is not
 // intended to be used other than internally by the Go command's
@@ -277,6 +280,9 @@ func (ts *tstate) readAuxMetaFiles(metafiles string, importpaths map[string]stru
 	return nil
 }
 
+// snapshot is injected in testmain.
+//go:linkname snapshot
+
 // snapshot returns a snapshot of coverage percentage at a moment of
 // time within a running test, so as to support the testing.Coverage()
 // function. This version doesn't examine coverage meta-data, so the
diff --git a/src/runtime/linkname.go b/src/runtime/linkname.go
new file mode 100644
index 0000000000000000000000000000000000000000..0f02c6b4e358a115636a1930db9476de084d7bf2
--- /dev/null
+++ b/src/runtime/linkname.go
@@ -0,0 +1,49 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime
+
+import _ "unsafe"
+
+// used in time and internal/poll
+//go:linkname nanotime
+
+// used in internal/godebug and syscall
+//go:linkname write
+
+// used in internal/runtime/atomic
+//go:linkname goarm
+
+// used by cgo
+//go:linkname cgocall
+//go:linkname _cgo_panic_internal
+//go:linkname cgoAlwaysFalse
+//go:linkname cgoUse
+//go:linkname cgoCheckPointer
+//go:linkname cgoCheckResult
+//go:linkname cgoNoCallback
+//go:linkname gobytes
+//go:linkname gostringn
+//go:linkname throw
+
+// used in plugin
+//go:linkname doInit
+
+// used in math/bits
+//go:linkname overflowError
+//go:linkname divideError
+
+// used in runtime/coverage and in tests
+//go:linkname addExitHook
+
+// used in x/sys/cpu
+//go:linkname getAuxv
+
+// used in tests
+//go:linkname extraMInUse
+//go:linkname getm
+//go:linkname blockevent
+//go:linkname haveHighResSleep
+//go:linkname blockUntilEmptyFinalizerQueue
+//go:linkname lockedOSThread
diff --git a/src/runtime/linkname_unix.go b/src/runtime/linkname_unix.go
new file mode 100644
index 0000000000000000000000000000000000000000..65f876fa4bafde0d8ea950a9d9c3b26ade7c46c8
--- /dev/null
+++ b/src/runtime/linkname_unix.go
@@ -0,0 +1,12 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package runtime
+
+import _ "unsafe"
+
+// used in internal/syscall/unix
+//go:linkname fcntl
diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go
index bbfef80aec88695018953b5ba98ea7a497ef1151..7b37d91b24a4d46445dadbb6da257d84bcd8c5d1 100644
--- a/src/runtime/netpoll.go
+++ b/src/runtime/netpoll.go
@@ -207,6 +207,9 @@ var (
 	netpollWaiters atomic.Uint32
 )
 
+// netpollWaiters is accessed in tests
+//go:linkname netpollWaiters
+
 //go:linkname poll_runtime_pollServerInit internal/poll.runtime_pollServerInit
 func poll_runtime_pollServerInit() {
 	netpollGenericInit()
diff --git a/src/runtime/string.go b/src/runtime/string.go
index e01b7fc74448e5c41ed9c146f70e1d86e42cef26..81d1b80e56030bdb101cafbac847238a776f7dc1 100644
--- a/src/runtime/string.go
+++ b/src/runtime/string.go
@@ -312,7 +312,7 @@ func gobytes(p *byte, n int) (b []byte) {
 	return
 }
 
-// This is exported via linkname to assembly in syscall (for Plan9).
+// This is exported via linkname to assembly in syscall (for Plan9) and cgo.
 //
 //go:linkname gostring
 func gostring(p *byte) string {
diff --git a/src/runtime/vdso_linux_amd64.go b/src/runtime/vdso_linux_amd64.go
index 4e9f748f4a1a71c66b84455d740a1c7177b986c8..9c564091377e016cfbc429d183b0303501c2a12a 100644
--- a/src/runtime/vdso_linux_amd64.go
+++ b/src/runtime/vdso_linux_amd64.go
@@ -4,6 +4,8 @@
 
 package runtime
 
+import _ "unsafe" // for linkname
+
 const (
 	// vdsoArrayMax is the byte-size of a maximally sized array on this architecture.
 	// See cmd/compile/internal/amd64/galign.go arch.MAXWIDTH initialization.
@@ -21,3 +23,6 @@ var (
 	vdsoGettimeofdaySym uintptr
 	vdsoClockgettimeSym uintptr
 )
+
+// vdsoGettimeofdaySym is accessed from the syscall package.
+//go:linkname vdsoGettimeofdaySym
diff --git a/src/syscall/fs_wasip1.go b/src/syscall/fs_wasip1.go
index f19e8f3b3cca7bdf024293277d347c0f427b4118..fc361ee898978f0165aa79ceabc2949700e3a067 100644
--- a/src/syscall/fs_wasip1.go
+++ b/src/syscall/fs_wasip1.go
@@ -288,12 +288,18 @@ func fd_fdstat_get(fd int32, buf unsafe.Pointer) Errno
 //go:noescape
 func fd_fdstat_set_flags(fd int32, flags fdflags) Errno
 
+// fd_fdstat_get_flags is accessed from internal/syscall/unix
+//go:linkname fd_fdstat_get_flags
+
 func fd_fdstat_get_flags(fd int) (uint32, error) {
 	var stat fdstat
 	errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat))
 	return uint32(stat.fdflags), errnoErr(errno)
 }
 
+// fd_fdstat_get_type is accessed from net
+//go:linkname fd_fdstat_get_type
+
 func fd_fdstat_get_type(fd int) (uint8, error) {
 	var stat fdstat
 	errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat))
diff --git a/src/syscall/linkname_bsd.go b/src/syscall/linkname_bsd.go
new file mode 100644
index 0000000000000000000000000000000000000000..65ef900241ce8e8e9d315b0c4ec5d4a79ab49628
--- /dev/null
+++ b/src/syscall/linkname_bsd.go
@@ -0,0 +1,15 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin || dragonfly || freebsd || netbsd || openbsd
+
+package syscall
+
+import _ "unsafe"
+
+// used by internal/syscall/unix
+//go:linkname ioctlPtr
+
+// used by x/net/route
+//go:linkname sysctl
diff --git a/src/syscall/linkname_darwin.go b/src/syscall/linkname_darwin.go
new file mode 100644
index 0000000000000000000000000000000000000000..2ed83a4fad2a5a2e66d950d4ce06175b006c4b1d
--- /dev/null
+++ b/src/syscall/linkname_darwin.go
@@ -0,0 +1,23 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package syscall
+
+import _ "unsafe"
+
+// used by os
+//go:linkname closedir
+//go:linkname readdir_r
+
+// used by internal/poll
+//go:linkname fdopendir
+
+// used by internal/syscall/unix
+//go:linkname unlinkat
+//go:linkname openat
+//go:linkname fstatat
+
+// used by cmd/link
+//go:linkname msync
+//go:linkname fcntl
diff --git a/src/syscall/linkname_libc.go b/src/syscall/linkname_libc.go
new file mode 100644
index 0000000000000000000000000000000000000000..1e7b4880d6ed3200eb582ea031cbb4ee31bd39c5
--- /dev/null
+++ b/src/syscall/linkname_libc.go
@@ -0,0 +1,12 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build aix || darwin || (openbsd && !mips64) || solaris
+
+package syscall
+
+import _ "unsafe"
+
+// used by internal/poll
+//go:linkname writev
diff --git a/src/syscall/linkname_openbsd.go b/src/syscall/linkname_openbsd.go
new file mode 100644
index 0000000000000000000000000000000000000000..5f5c517ab585992dfe1a7aaae8ac60a3c08f2db3
--- /dev/null
+++ b/src/syscall/linkname_openbsd.go
@@ -0,0 +1,15 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build openbsd && !mips64
+
+package syscall
+
+import _ "unsafe"
+
+// used by internal/syscall/unix
+//go:linkname unlinkat
+//go:linkname openat
+//go:linkname fstatat
+//go:linkname getentropy
diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go
index f35e78c26af33a617ffdb6816579af85c9232589..28727dc98a88be27e4f10fb534a5d8d984cbc625 100644
--- a/src/syscall/syscall_linux.go
+++ b/src/syscall/syscall_linux.go
@@ -1284,6 +1284,9 @@ func Munmap(b []byte) (err error) {
 //sys	Mlockall(flags int) (err error)
 //sys	Munlockall() (err error)
 
+// prlimit is accessed from x/sys/unix.
+//go:linkname prlimit
+
 // prlimit changes a resource limit. We use a single definition so that
 // we can tell StartProcess to not restore the original NOFILE limit.
 // This is unexported but can be called from x/sys/unix.
diff --git a/src/testing/newcover.go b/src/testing/newcover.go
index 6199f3bd7ba680ed5247d14ba205aa69bb1e6f7f..7a70dcfffa5e8f7c843303fecbe033f8a4bbb31f 100644
--- a/src/testing/newcover.go
+++ b/src/testing/newcover.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"internal/goexperiment"
 	"os"
+	_ "unsafe" // for linkname
 )
 
 // cover2 variable stores the current coverage mode and a
@@ -20,6 +21,9 @@ var cover2 struct {
 	snapshotcov func() float64
 }
 
+// registerCover2 is injected in testmain.
+//go:linkname registerCover2
+
 // registerCover2 is invoked during "go test -cover" runs by the test harness
 // code in _testmain.go; it is used to record a 'tear down' function
 // (to be called when the test is complete) and the coverage mode.
@@ -42,6 +46,9 @@ func coverReport2() {
 	}
 }
 
+// testGoCoverDir is used in runtime/coverage tests.
+//go:linkname testGoCoverDir
+
 // testGoCoverDir returns the value passed to the -test.gocoverdir
 // flag by the Go command, if goexperiment.CoverageRedesign is
 // in effect.
diff --git a/src/time/zoneinfo_read.go b/src/time/zoneinfo_read.go
index 9ce735d279ca0ecf0e37e68ba9e52654ec66cbb1..5314b6ff9a1623b8f95b08a99d1179dc5745aba9 100644
--- a/src/time/zoneinfo_read.go
+++ b/src/time/zoneinfo_read.go
@@ -14,10 +14,13 @@ import (
 	"internal/bytealg"
 	"runtime"
 	"syscall"
+	_ "unsafe" // for linkname
 )
 
 // registerLoadFromEmbeddedTZData is called by the time/tzdata package,
 // if it is imported.
+//
+//go:linkname registerLoadFromEmbeddedTZData
 func registerLoadFromEmbeddedTZData(f func(string) (string, error)) {
 	loadFromEmbeddedTZData = f
 }