diff --git a/api/go1.26.txt b/api/go1.26.txt new file mode 100644 index 00000000000..87bcebd1af5 --- /dev/null +++ b/api/go1.26.txt @@ -0,0 +1,185 @@ +pkg bytes, method (*Buffer) Peek(int) ([]uint8, error) #73794 +pkg crypto, type Decapsulator interface { Decapsulate, Encapsulator } #75300 +pkg crypto, type Decapsulator interface, Decapsulate([]uint8) ([]uint8, error) #75300 +pkg crypto, type Decapsulator interface, Encapsulator() Encapsulator #75300 +pkg crypto, type Encapsulator interface { Bytes, Encapsulate } #75300 +pkg crypto, type Encapsulator interface, Bytes() []uint8 #75300 +pkg crypto, type Encapsulator interface, Encapsulate() ([]uint8, []uint8) #75300 +pkg crypto/ecdh, type KeyExchanger interface { Curve, ECDH, PublicKey } #75300 +pkg crypto/ecdh, type KeyExchanger interface, Curve() Curve #75300 +pkg crypto/ecdh, type KeyExchanger interface, ECDH(*PublicKey) ([]uint8, error) #75300 +pkg crypto/ecdh, type KeyExchanger interface, PublicKey() *PublicKey #75300 +pkg crypto/ecdsa, type PrivateKey struct, D //deprecated #63963 +pkg crypto/ecdsa, type PublicKey struct, X //deprecated #63963 +pkg crypto/ecdsa, type PublicKey struct, Y //deprecated #63963 +pkg crypto/fips140, func Enforced() bool #74630 +pkg crypto/fips140, func Version() string #75301 +pkg crypto/fips140, func WithoutEnforcement(func()) #74630 +pkg crypto/hpke, func AES128GCM() AEAD #75300 +pkg crypto/hpke, func AES256GCM() AEAD #75300 +pkg crypto/hpke, func ChaCha20Poly1305() AEAD #75300 +pkg crypto/hpke, func DHKEM(ecdh.Curve) KEM #75300 +pkg crypto/hpke, func ExportOnly() AEAD #75300 +pkg crypto/hpke, func HKDFSHA256() KDF #75300 +pkg crypto/hpke, func HKDFSHA384() KDF #75300 +pkg crypto/hpke, func HKDFSHA512() KDF #75300 +pkg crypto/hpke, func MLKEM1024() KEM #75300 +pkg crypto/hpke, func MLKEM1024P384() KEM #75300 +pkg crypto/hpke, func MLKEM768() KEM #75300 +pkg crypto/hpke, func MLKEM768P256() KEM #75300 +pkg crypto/hpke, func MLKEM768X25519() KEM #75300 +pkg crypto/hpke, func NewAEAD(uint16) (AEAD, error) #75300 +pkg crypto/hpke, func NewDHKEMPrivateKey(ecdh.KeyExchanger) (PrivateKey, error) #75300 +pkg crypto/hpke, func NewDHKEMPublicKey(*ecdh.PublicKey) (PublicKey, error) #75300 +pkg crypto/hpke, func NewHybridPrivateKey(crypto.Decapsulator, ecdh.KeyExchanger) (PrivateKey, error) #75300 +pkg crypto/hpke, func NewHybridPublicKey(crypto.Encapsulator, *ecdh.PublicKey) (PublicKey, error) #75300 +pkg crypto/hpke, func NewKDF(uint16) (KDF, error) #75300 +pkg crypto/hpke, func NewKEM(uint16) (KEM, error) #75300 +pkg crypto/hpke, func NewMLKEMPrivateKey(crypto.Decapsulator) (PrivateKey, error) #75300 +pkg crypto/hpke, func NewMLKEMPublicKey(crypto.Encapsulator) (PublicKey, error) #75300 +pkg crypto/hpke, func NewRecipient([]uint8, PrivateKey, KDF, AEAD, []uint8) (*Recipient, error) #75300 +pkg crypto/hpke, func NewSender(PublicKey, KDF, AEAD, []uint8) ([]uint8, *Sender, error) #75300 +pkg crypto/hpke, func Open(PrivateKey, KDF, AEAD, []uint8, []uint8) ([]uint8, error) #75300 +pkg crypto/hpke, func SHAKE128() KDF #75300 +pkg crypto/hpke, func SHAKE256() KDF #75300 +pkg crypto/hpke, func Seal(PublicKey, KDF, AEAD, []uint8, []uint8) ([]uint8, error) #75300 +pkg crypto/hpke, method (*Recipient) Export(string, int) ([]uint8, error) #75300 +pkg crypto/hpke, method (*Recipient) Open([]uint8, []uint8) ([]uint8, error) #75300 +pkg crypto/hpke, method (*Sender) Export(string, int) ([]uint8, error) #75300 +pkg crypto/hpke, method (*Sender) Seal([]uint8, []uint8) ([]uint8, error) #75300 +pkg crypto/hpke, type AEAD interface, ID() uint16 #75300 +pkg crypto/hpke, type AEAD interface, unexported methods #75300 +pkg crypto/hpke, type KDF interface, ID() uint16 #75300 +pkg crypto/hpke, type KDF interface, unexported methods #75300 +pkg crypto/hpke, type KEM interface, DeriveKeyPair([]uint8) (PrivateKey, error) #75300 +pkg crypto/hpke, type KEM interface, GenerateKey() (PrivateKey, error) #75300 +pkg crypto/hpke, type KEM interface, ID() uint16 #75300 +pkg crypto/hpke, type KEM interface, NewPrivateKey([]uint8) (PrivateKey, error) #75300 +pkg crypto/hpke, type KEM interface, NewPublicKey([]uint8) (PublicKey, error) #75300 +pkg crypto/hpke, type KEM interface, unexported methods #75300 +pkg crypto/hpke, type PrivateKey interface, Bytes() ([]uint8, error) #75300 +pkg crypto/hpke, type PrivateKey interface, KEM() KEM #75300 +pkg crypto/hpke, type PrivateKey interface, PublicKey() PublicKey #75300 +pkg crypto/hpke, type PrivateKey interface, unexported methods #75300 +pkg crypto/hpke, type PublicKey interface, Bytes() []uint8 #75300 +pkg crypto/hpke, type PublicKey interface, KEM() KEM #75300 +pkg crypto/hpke, type PublicKey interface, unexported methods #75300 +pkg crypto/hpke, type Recipient struct #75300 +pkg crypto/hpke, type Sender struct #75300 +pkg crypto/mlkem, method (*DecapsulationKey1024) Encapsulator() crypto.Encapsulator #75300 +pkg crypto/mlkem, method (*DecapsulationKey768) Encapsulator() crypto.Encapsulator #75300 +pkg crypto/mlkem/mlkemtest, func Encapsulate1024(*mlkem.EncapsulationKey1024, []uint8) ([]uint8, []uint8, error) #73627 +pkg crypto/mlkem/mlkemtest, func Encapsulate768(*mlkem.EncapsulationKey768, []uint8) ([]uint8, []uint8, error) #73627 +pkg crypto/rsa, func DecryptPKCS1v15 //deprecated #75302 +pkg crypto/rsa, func DecryptPKCS1v15SessionKey //deprecated #75302 +pkg crypto/rsa, func EncryptOAEPWithOptions(io.Reader, *PublicKey, []uint8, *OAEPOptions) ([]uint8, error) #65716 +pkg crypto/rsa, func EncryptPKCS1v15 //deprecated #75302 +pkg crypto/rsa, type PKCS1v15DecryptOptions //deprecated #75302 +pkg crypto/tls, const QUICErrorEvent = 10 #75108 +pkg crypto/tls, const QUICErrorEvent QUICEventKind #75108 +pkg crypto/tls, const SecP256r1MLKEM768 = 4587 #71206 +pkg crypto/tls, const SecP256r1MLKEM768 CurveID #71206 +pkg crypto/tls, const SecP384r1MLKEM1024 = 4589 #71206 +pkg crypto/tls, const SecP384r1MLKEM1024 CurveID #71206 +pkg crypto/tls, type ClientHelloInfo struct, HelloRetryRequest bool #74425 +pkg crypto/tls, type ConnectionState struct, HelloRetryRequest bool #74425 +pkg crypto/tls, type QUICEvent struct, Err error #75108 +pkg crypto/x509, func OIDFromASN1OID(asn1.ObjectIdentifier) (OID, error) #75325 +pkg crypto/x509, method (ExtKeyUsage) OID() OID #75325 +pkg crypto/x509, method (ExtKeyUsage) String() string #56866 +pkg crypto/x509, method (KeyUsage) String() string #56866 +pkg database/sql/driver, type RowsColumnScanner interface { Close, Columns, Next, ScanColumn } #67546 +pkg database/sql/driver, type RowsColumnScanner interface, Close() error #67546 +pkg database/sql/driver, type RowsColumnScanner interface, Columns() []string #67546 +pkg database/sql/driver, type RowsColumnScanner interface, Next([]Value) error #67546 +pkg database/sql/driver, type RowsColumnScanner interface, ScanColumn(interface{}, int) error #67546 +pkg debug/elf, const R_LARCH_CALL36 = 110 #75562 +pkg debug/elf, const R_LARCH_CALL36 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC32 = 13 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC32 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64 = 14 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64_HI12 = 118 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64_HI12 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64_LO20 = 117 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64_LO20 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64_PC_HI12 = 114 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64_PC_HI12 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64_PC_LO20 = 113 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC64_PC_LO20 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_CALL = 120 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_CALL R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_HI20 = 115 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_HI20 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_LD = 119 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_LD R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_LO12 = 116 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_LO12 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_PCREL20_S2 = 126 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_PCREL20_S2 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_PC_HI20 = 111 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_PC_HI20 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_PC_LO12 = 112 #75562 +pkg debug/elf, const R_LARCH_TLS_DESC_PC_LO12 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_GD_PCREL20_S2 = 125 #75562 +pkg debug/elf, const R_LARCH_TLS_GD_PCREL20_S2 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_LD_PCREL20_S2 = 124 #75562 +pkg debug/elf, const R_LARCH_TLS_LD_PCREL20_S2 R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_LE_ADD_R = 122 #75562 +pkg debug/elf, const R_LARCH_TLS_LE_ADD_R R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_LE_HI20_R = 121 #75562 +pkg debug/elf, const R_LARCH_TLS_LE_HI20_R R_LARCH #75562 +pkg debug/elf, const R_LARCH_TLS_LE_LO12_R = 123 #75562 +pkg debug/elf, const R_LARCH_TLS_LE_LO12_R R_LARCH #75562 +pkg errors, func AsType[$0 error](error) ($0, bool) #51945 +pkg go/ast, func ParseDirective(token.Pos, string) (Directive, bool) #68021 +pkg go/ast, method (*Directive) End() token.Pos #68021 +pkg go/ast, method (*Directive) ParseArgs() ([]DirectiveArg, error) #68021 +pkg go/ast, method (*Directive) Pos() token.Pos #68021 +pkg go/ast, type BasicLit struct, ValueEnd token.Pos #76031 +pkg go/ast, type Directive struct #68021 +pkg go/ast, type Directive struct, Args string #68021 +pkg go/ast, type Directive struct, ArgsPos token.Pos #68021 +pkg go/ast, type Directive struct, Name string #68021 +pkg go/ast, type Directive struct, Slash token.Pos #68021 +pkg go/ast, type Directive struct, Tool string #68021 +pkg go/ast, type DirectiveArg struct #68021 +pkg go/ast, type DirectiveArg struct, Arg string #68021 +pkg go/ast, type DirectiveArg struct, Pos token.Pos #68021 +pkg go/token, method (*File) End() Pos #75849 +pkg log/slog, func NewMultiHandler(...Handler) *MultiHandler #65954 +pkg log/slog, method (*MultiHandler) Enabled(context.Context, Level) bool #65954 +pkg log/slog, method (*MultiHandler) Handle(context.Context, Record) error #65954 +pkg log/slog, method (*MultiHandler) WithAttrs([]Attr) Handler #65954 +pkg log/slog, method (*MultiHandler) WithGroup(string) Handler #65954 +pkg log/slog, type MultiHandler struct #65954 +pkg net, method (*Dialer) DialIP(context.Context, string, netip.Addr, netip.Addr) (*IPConn, error) #49097 +pkg net, method (*Dialer) DialTCP(context.Context, string, netip.AddrPort, netip.AddrPort) (*TCPConn, error) #49097 +pkg net, method (*Dialer) DialUDP(context.Context, string, netip.AddrPort, netip.AddrPort) (*UDPConn, error) #49097 +pkg net, method (*Dialer) DialUnix(context.Context, string, *UnixAddr, *UnixAddr) (*UnixConn, error) #49097 +pkg net/http, method (*ClientConn) Available() int #75772 +pkg net/http, method (*ClientConn) Close() error #75772 +pkg net/http, method (*ClientConn) Err() error #75772 +pkg net/http, method (*ClientConn) InFlight() int #75772 +pkg net/http, method (*ClientConn) Release() #75772 +pkg net/http, method (*ClientConn) Reserve() error #75772 +pkg net/http, method (*ClientConn) RoundTrip(*Request) (*Response, error) #75772 +pkg net/http, method (*ClientConn) SetStateHook(func(*ClientConn)) #75772 +pkg net/http, method (*Transport) NewClientConn(context.Context, string, string) (*ClientConn, error) #75772 +pkg net/http, type ClientConn struct #75772 +pkg net/http, type HTTP2Config struct, StrictMaxConcurrentRequests bool #67813 +pkg net/http/httputil, type ReverseProxy struct, Director //deprecated #73161 +pkg net/netip, method (Prefix) Compare(Prefix) int #61642 +pkg os, method (*Process) WithHandle(func(uintptr)) error #70352 +pkg os, var ErrNoHandle error #70352 +pkg reflect, method (Value) Fields() iter.Seq2[StructField, Value] #66631 +pkg reflect, method (Value) Methods() iter.Seq2[Method, Value] #66631 +pkg reflect, type Type interface, Fields() iter.Seq[StructField] #66631 +pkg reflect, type Type interface, Ins() iter.Seq[Type] #66631 +pkg reflect, type Type interface, Methods() iter.Seq[Method] #66631 +pkg reflect, type Type interface, Outs() iter.Seq[Type] #66631 +pkg testing, method (*B) ArtifactDir() string #71287 +pkg testing, method (*F) ArtifactDir() string #71287 +pkg testing, method (*T) ArtifactDir() string #71287 +pkg testing, type TB interface, ArtifactDir() string #71287 +pkg testing/cryptotest, func SetGlobalRandom(*testing.T, uint64) #70942 diff --git a/api/next/49097.txt b/api/next/49097.txt deleted file mode 100644 index f7240954c66..00000000000 --- a/api/next/49097.txt +++ /dev/null @@ -1,4 +0,0 @@ -pkg net, method (*Dialer) DialIP(context.Context, string, netip.Addr, netip.Addr) (*IPConn, error) #49097 -pkg net, method (*Dialer) DialTCP(context.Context, string, netip.AddrPort, netip.AddrPort) (*TCPConn, error) #49097 -pkg net, method (*Dialer) DialUDP(context.Context, string, netip.AddrPort, netip.AddrPort) (*UDPConn, error) #49097 -pkg net, method (*Dialer) DialUnix(context.Context, string, *UnixAddr, *UnixAddr) (*UnixConn, error) #49097 diff --git a/api/next/51945.txt b/api/next/51945.txt deleted file mode 100644 index 7db1f093e5a..00000000000 --- a/api/next/51945.txt +++ /dev/null @@ -1 +0,0 @@ -pkg errors, func AsType[$0 error](error) ($0, bool) #51945 diff --git a/api/next/61642.txt b/api/next/61642.txt deleted file mode 100644 index dd67874ab9f..00000000000 --- a/api/next/61642.txt +++ /dev/null @@ -1 +0,0 @@ -pkg net/netip, method (Prefix) Compare(Prefix) int #61642 diff --git a/api/next/63963.txt b/api/next/63963.txt deleted file mode 100644 index c1aaae5044c..00000000000 --- a/api/next/63963.txt +++ /dev/null @@ -1,3 +0,0 @@ -pkg crypto/ecdsa, type PrivateKey struct, D //deprecated #63963 -pkg crypto/ecdsa, type PublicKey struct, X //deprecated #63963 -pkg crypto/ecdsa, type PublicKey struct, Y //deprecated #63963 diff --git a/api/next/65954.txt b/api/next/65954.txt deleted file mode 100644 index 88d7c2668fe..00000000000 --- a/api/next/65954.txt +++ /dev/null @@ -1,6 +0,0 @@ -pkg log/slog, func NewMultiHandler(...Handler) *MultiHandler #65954 -pkg log/slog, method (*MultiHandler) Enabled(context.Context, Level) bool #65954 -pkg log/slog, method (*MultiHandler) Handle(context.Context, Record) error #65954 -pkg log/slog, method (*MultiHandler) WithAttrs([]Attr) Handler #65954 -pkg log/slog, method (*MultiHandler) WithGroup(string) Handler #65954 -pkg log/slog, type MultiHandler struct #65954 diff --git a/api/next/67546.txt b/api/next/67546.txt deleted file mode 100644 index 0b5b4b981c1..00000000000 --- a/api/next/67546.txt +++ /dev/null @@ -1,5 +0,0 @@ -pkg database/sql/driver, type RowsColumnScanner interface { Close, Columns, Next, ScanColumn } #67546 -pkg database/sql/driver, type RowsColumnScanner interface, Close() error #67546 -pkg database/sql/driver, type RowsColumnScanner interface, Columns() []string #67546 -pkg database/sql/driver, type RowsColumnScanner interface, Next([]Value) error #67546 -pkg database/sql/driver, type RowsColumnScanner interface, ScanColumn(interface{}, int) error #67546 diff --git a/api/next/67813.txt b/api/next/67813.txt deleted file mode 100644 index b420a141e1f..00000000000 --- a/api/next/67813.txt +++ /dev/null @@ -1 +0,0 @@ -pkg net/http, type HTTP2Config struct, StrictMaxConcurrentRequests bool #67813 diff --git a/api/next/68021.txt b/api/next/68021.txt deleted file mode 100644 index 46156e06654..00000000000 --- a/api/next/68021.txt +++ /dev/null @@ -1,13 +0,0 @@ -pkg go/ast, func ParseDirective(token.Pos, string) (Directive, bool) #68021 -pkg go/ast, method (*Directive) End() token.Pos #68021 -pkg go/ast, method (*Directive) ParseArgs() ([]DirectiveArg, error) #68021 -pkg go/ast, method (*Directive) Pos() token.Pos #68021 -pkg go/ast, type Directive struct #68021 -pkg go/ast, type Directive struct, Args string #68021 -pkg go/ast, type Directive struct, ArgsPos token.Pos #68021 -pkg go/ast, type Directive struct, Name string #68021 -pkg go/ast, type Directive struct, Slash token.Pos #68021 -pkg go/ast, type Directive struct, Tool string #68021 -pkg go/ast, type DirectiveArg struct #68021 -pkg go/ast, type DirectiveArg struct, Arg string #68021 -pkg go/ast, type DirectiveArg struct, Pos token.Pos #68021 diff --git a/api/next/70352.txt b/api/next/70352.txt deleted file mode 100644 index cd264046c36..00000000000 --- a/api/next/70352.txt +++ /dev/null @@ -1,2 +0,0 @@ -pkg os, method (*Process) WithHandle(func(uintptr)) error #70352 -pkg os, var ErrNoHandle error #70352 diff --git a/api/next/71287.txt b/api/next/71287.txt deleted file mode 100644 index c1e09a1f523..00000000000 --- a/api/next/71287.txt +++ /dev/null @@ -1,4 +0,0 @@ -pkg testing, method (*B) ArtifactDir() string #71287 -pkg testing, method (*F) ArtifactDir() string #71287 -pkg testing, method (*T) ArtifactDir() string #71287 -pkg testing, type TB interface, ArtifactDir() string #71287 diff --git a/api/next/73161.txt b/api/next/73161.txt deleted file mode 100644 index 86526b597a9..00000000000 --- a/api/next/73161.txt +++ /dev/null @@ -1 +0,0 @@ -pkg net/http/httputil, type ReverseProxy struct, Director //deprecated #73161 diff --git a/api/next/73627.txt b/api/next/73627.txt deleted file mode 100644 index b13d705a61d..00000000000 --- a/api/next/73627.txt +++ /dev/null @@ -1,2 +0,0 @@ -pkg crypto/mlkem/mlkemtest, func Encapsulate1024(*mlkem.EncapsulationKey1024, []uint8) ([]uint8, []uint8, error) #73627 -pkg crypto/mlkem/mlkemtest, func Encapsulate768(*mlkem.EncapsulationKey768, []uint8) ([]uint8, []uint8, error) #73627 diff --git a/api/next/73794.txt b/api/next/73794.txt deleted file mode 100644 index 4018c149ecb..00000000000 --- a/api/next/73794.txt +++ /dev/null @@ -1 +0,0 @@ -pkg bytes, method (*Buffer) Peek(int) ([]uint8, error) #73794 diff --git a/api/next/75108.txt b/api/next/75108.txt deleted file mode 100644 index f1784d2c5a8..00000000000 --- a/api/next/75108.txt +++ /dev/null @@ -1,3 +0,0 @@ -pkg crypto/tls, const QUICErrorEvent = 10 #75108 -pkg crypto/tls, const QUICErrorEvent QUICEventKind #75108 -pkg crypto/tls, type QUICEvent struct, Err error #75108 diff --git a/api/next/75300.txt b/api/next/75300.txt deleted file mode 100644 index da24eb4aa34..00000000000 --- a/api/next/75300.txt +++ /dev/null @@ -1,12 +0,0 @@ -pkg crypto, type Decapsulator interface { Decapsulate, Encapsulator } #75300 -pkg crypto, type Decapsulator interface, Decapsulate([]uint8) ([]uint8, error) #75300 -pkg crypto, type Decapsulator interface, Encapsulator() Encapsulator #75300 -pkg crypto, type Encapsulator interface { Bytes, Encapsulate } #75300 -pkg crypto, type Encapsulator interface, Bytes() []uint8 #75300 -pkg crypto, type Encapsulator interface, Encapsulate() ([]uint8, []uint8) #75300 -pkg crypto/ecdh, type KeyExchanger interface { Curve, ECDH, PublicKey } #75300 -pkg crypto/ecdh, type KeyExchanger interface, Curve() Curve #75300 -pkg crypto/ecdh, type KeyExchanger interface, ECDH(*PublicKey) ([]uint8, error) #75300 -pkg crypto/ecdh, type KeyExchanger interface, PublicKey() *PublicKey #75300 -pkg crypto/mlkem, method (*DecapsulationKey1024) Encapsulator() crypto.Encapsulator #75300 -pkg crypto/mlkem, method (*DecapsulationKey768) Encapsulator() crypto.Encapsulator #75300 diff --git a/api/next/75302.txt b/api/next/75302.txt deleted file mode 100644 index 31474644b1f..00000000000 --- a/api/next/75302.txt +++ /dev/null @@ -1,4 +0,0 @@ -pkg crypto/rsa, func DecryptPKCS1v15 //deprecated #75302 -pkg crypto/rsa, func DecryptPKCS1v15SessionKey //deprecated #75302 -pkg crypto/rsa, func EncryptPKCS1v15 //deprecated #75302 -pkg crypto/rsa, type PKCS1v15DecryptOptions //deprecated #75302 diff --git a/api/next/75562.txt b/api/next/75562.txt deleted file mode 100644 index 5e3fe6c6a58..00000000000 --- a/api/next/75562.txt +++ /dev/null @@ -1,38 +0,0 @@ -pkg debug/elf, const R_LARCH_TLS_DESC32 = 13 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC32 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64 = 14 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64 R_LARCH #75562 -pkg debug/elf, const R_LARCH_CALL36 = 110 #75562 -pkg debug/elf, const R_LARCH_CALL36 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_PC_HI20 = 111 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_PC_HI20 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_PC_LO12 = 112 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_PC_LO12 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64_PC_LO20 = 113 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64_PC_LO20 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64_PC_HI12 = 114 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64_PC_HI12 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_HI20 = 115 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_HI20 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_LO12 = 116 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_LO12 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64_LO20 = 117 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64_LO20 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64_HI12 = 118 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC64_HI12 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_LD = 119 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_LD R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_CALL = 120 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_CALL R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_LE_HI20_R = 121 #75562 -pkg debug/elf, const R_LARCH_TLS_LE_HI20_R R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_LE_ADD_R = 122 #75562 -pkg debug/elf, const R_LARCH_TLS_LE_ADD_R R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_LE_LO12_R = 123 #75562 -pkg debug/elf, const R_LARCH_TLS_LE_LO12_R R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_LD_PCREL20_S2 = 124 #75562 -pkg debug/elf, const R_LARCH_TLS_LD_PCREL20_S2 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_GD_PCREL20_S2 = 125 #75562 -pkg debug/elf, const R_LARCH_TLS_GD_PCREL20_S2 R_LARCH #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_PCREL20_S2 = 126 #75562 -pkg debug/elf, const R_LARCH_TLS_DESC_PCREL20_S2 R_LARCH #75562 diff --git a/api/next/75849.txt b/api/next/75849.txt deleted file mode 100644 index 615d6158f14..00000000000 --- a/api/next/75849.txt +++ /dev/null @@ -1 +0,0 @@ -pkg go/token, method (*File) End() Pos #75849 diff --git a/api/next/76031.txt b/api/next/76031.txt deleted file mode 100644 index 049edc7a569..00000000000 --- a/api/next/76031.txt +++ /dev/null @@ -1 +0,0 @@ -pkg go/ast, type BasicLit struct, ValueEnd token.Pos #76031 diff --git a/doc/go_spec.html b/doc/go_spec.html index b75845372da..f47f5a6e3e2 100644 --- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1,6 +1,6 @@ @@ -7496,7 +7496,7 @@ returns a received value along with an indication of whether the channel is clos

If the type of the argument to close is a type parameter, -all types in its type set must be channels with the same element type. +all types in its type set must be channels. It is an error if any of those channels is a receive-only channel.

@@ -7794,40 +7794,32 @@ min(x, y, z) == min(min(x, y), z)

Allocation

- The built-in function new creates a new, initialized - variable and returns - a pointer to it. - - It accepts a single argument, which may be either an expression or a type. -

-

- If the argument expr is an expression of - type T, or an untyped constant expression - whose default type is T, - then new(expr) allocates a variable of - type T, initializes it to the value - of expr, and returns its address, a value of - type *T. -

-

- If the argument is a type T, then new(T) - allocates a variable initialized to - the zero value of type T. -

-

- For example, new(123) and new(int) each - return a pointer to a new variable of type int. - - The value of the first variable is 123, and the value - of the second is 0. +The built-in function new creates a new, initialized +variable and returns +a pointer to it. +It accepts a single argument, which may be either a type or an expression.

-
-new(T)
-
+

+If the argument is a type T, then new(T) +allocates a variable of type T initialized to its +zero value. +

-For instance +If the argument is an expression x, then new(x) +allocates a variable of the type of x initialized to the value of x. +If that value is an untyped constant, it is first implicitly converted +to its default type; +if it is an untyped boolean value, it is first implicitly converted to type bool. +The predeclared identifier nil cannot be used as an argument to new. +

+ +

+For example, new(int) and new(123) each +return a pointer to a new variable of type int. +The value of the first variable is 0, and the value +of the second is 123. Similarly

@@ -7836,13 +7828,12 @@ new(S)
 

-allocates storage for a variable of type S, +allocates a variable of type S, initializes it (a=0, b=0.0), and returns a value of type *S containing the address -of the location. +of the variable.

-

Handling panics

Two built-in functions, panic and recover, diff --git a/doc/godebug.md b/doc/godebug.md index d9ae462b980..28a2dc506ef 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -168,6 +168,21 @@ allows malformed hostnames containing colons outside of a bracketed IPv6 address The default `urlstrictcolons=1` rejects URLs such as `http://localhost:1:2` or `http://::1/`. Colons are permitted as part of a bracketed IPv6 address, such as `http://[::1]/`. +Go 1.26 enabled two additional post-quantum key exchange mechanisms: +SecP256r1MLKEM768 and SecP384r1MLKEM1024. The default can be reverted using the +[`tlssecpmlkem` setting](/pkg/crypto/tls/#Config.CurvePreferences). + +Go 1.26 added a new `tracebacklabels` setting that controls the inclusion of +goroutine labels set through the the `runtime/pprof` package. Setting `tracebacklabels=1` +includes these key/value pairs in the goroutine status header of runtime +tracebacks and debug=2 runtime/pprof stack dumps. This format may change in the future. +(see go.dev/issue/76349) + +Go 1.26 added a new `cryptocustomrand` setting that controls whether most crypto/... +APIs ignore the random `io.Reader` parameter. For Go 1.26, it defaults +to `cryptocustomrand=0`, ignoring the random parameters. Using `cryptocustomrand=1` +reverts to the pre-Go 1.26 behavior. + ### Go 1.25 Go 1.25 added a new `decoratemappings` setting that controls whether the Go @@ -291,7 +306,7 @@ Go 1.23 changed the channels created by package time to be unbuffered and [`Timer.Reset`](/pkg/time/#Timer.Reset) method results much easier. The [`asynctimerchan` setting](/pkg/time/#NewTimer) disables this change. There are no runtime metrics for this change, -This setting may be removed in a future release, Go 1.27 at the earliest. +This setting will be removed in Go 1.27. Go 1.23 changed the mode bits reported by [`os.Lstat`](/pkg/os#Lstat) and [`os.Stat`](/pkg/os#Stat) for reparse points, which can be controlled with the `winsymlink` setting. @@ -328,6 +343,7 @@ any effect. Go 1.23 changed the default TLS cipher suites used by clients and servers when not explicitly configured, removing 3DES cipher suites. The default can be reverted using the [`tls3des` setting](/pkg/crypto/tls/#Config.CipherSuites). +This setting will be removed in Go 1.27. Go 1.23 changed the behavior of [`tls.X509KeyPair`](/pkg/crypto/tls#X509KeyPair) and [`tls.LoadX509KeyPair`](/pkg/crypto/tls#LoadX509KeyPair) to populate the @@ -335,6 +351,7 @@ Leaf field of the returned [`tls.Certificate`](/pkg/crypto/tls#Certificate). This behavior is controlled by the `x509keypairleaf` setting. For Go 1.23, it defaults to `x509keypairleaf=1`. Previous versions default to `x509keypairleaf=0`. +This setting will be removed in Go 1.27. Go 1.23 changed [`net/http.ServeContent`](/pkg/net/http#ServeContent), @@ -368,21 +385,24 @@ Whether the type checker produces `Alias` types or not is controlled by the [`gotypesalias` setting](/pkg/go/types#Alias). For Go 1.22 it defaults to `gotypesalias=0`. For Go 1.23, `gotypesalias=1` will become the default. -This setting will be removed in a future release, Go 1.27 at the earliest. +This setting will be removed in Go 1.27. Go 1.22 changed the default minimum TLS version supported by both servers and clients to TLS 1.2. The default can be reverted to TLS 1.0 using the [`tls10server` setting](/pkg/crypto/tls/#Config). +This setting will be removed in Go 1.27. Go 1.22 changed the default TLS cipher suites used by clients and servers when not explicitly configured, removing the cipher suites which used RSA based key exchange. The default can be reverted using the [`tlsrsakex` setting](/pkg/crypto/tls/#Config). +This setting will be removed in Go 1.27. Go 1.22 disabled [`ConnectionState.ExportKeyingMaterial`](/pkg/crypto/tls/#ConnectionState.ExportKeyingMaterial) when the connection supports neither TLS 1.3 nor Extended Master Secret (implemented in Go 1.21). It can be reenabled with the [`tlsunsafeekm` setting](/pkg/crypto/tls/#ConnectionState.ExportKeyingMaterial). +This setting will be removed in Go 1.27. Go 1.22 changed how the runtime interacts with transparent huge pages on Linux. In particular, a common default Linux kernel configuration can result in diff --git a/doc/initial/6-stdlib/99-minor/0-heading.md b/doc/initial/6-stdlib/99-minor/0-heading.md index e80ebc64c38..266d98f496a 100644 --- a/doc/initial/6-stdlib/99-minor/0-heading.md +++ b/doc/initial/6-stdlib/99-minor/0-heading.md @@ -1,10 +1 @@ ### Minor changes to the library {#minor_library_changes} - -#### go/types - -The `Var.Kind` method returns an enumeration of type `VarKind` that -classifies the variable (package-level, local, receiver, parameter, -result, or struct field). See issue #70250. - -Callers of `NewVar` or `NewParam` are encouraged to call `Var.SetKind` -to ensure that this attribute is set correctly in all cases. diff --git a/doc/next/1-intro.md b/doc/next/1-intro.md deleted file mode 100644 index 3cd0d66b5ad..00000000000 --- a/doc/next/1-intro.md +++ /dev/null @@ -1,8 +0,0 @@ - - -## DRAFT RELEASE NOTES — Introduction to Go 1.26 {#introduction} - -**Go 1.26 is not yet released. These are work-in-progress release notes. -Go 1.26 is expected to be released in February 2026.** diff --git a/doc/next/2-language.md b/doc/next/2-language.md deleted file mode 100644 index 71da62f59e5..00000000000 --- a/doc/next/2-language.md +++ /dev/null @@ -1,32 +0,0 @@ -## Changes to the language {#language} - - - -The built-in `new` function, which creates a new variable, now allows -its operand to be an expression, specifying the initial value of the -variable. - -This feature is particularly useful when working with serialization -packages such as `encoding/json` or protocol buffers that use a -pointer to represent an optional value, as it enables an optional -field to be populated in a simple expression, for example: - -```go -import "encoding/json" - -type Person struct { - Name string `json:"name"` - Age *int `json:"age"` // age if known; nil otherwise -} - -func personJSON(name string, born time.Time) ([]byte, error) { - return json.Marshal(Person{ - Name: name, - Age: new(yearsSince(born)), - }) -} - -func yearsSince(t time.Time) int { - return int(time.Since(t).Hours() / (365.25 * 24)) // approximately -} -``` diff --git a/doc/next/3-tools.md b/doc/next/3-tools.md deleted file mode 100644 index c0a4601c0b9..00000000000 --- a/doc/next/3-tools.md +++ /dev/null @@ -1,21 +0,0 @@ -## Tools {#tools} - -### Go command {#go-command} - - -`cmd/doc`, and `go tool doc` have been deleted. `go doc` can be used as -a replacement for `go tool doc`: it takes the same flags and arguments and -has the same behavior. - - -The `go fix` command, following the pattern of `go vet` in Go 1.10, -now uses the Go analysis framework (`golang.org/x/tools/go/analysis`). -This means the same analyzers that provide diagnostics in `go vet` -can be used to suggest and apply fixes in `go fix`. -The `go fix` command's historical fixers, all of which were obsolete, -have been removed and replaced by a suite of new analyzers that -offer fixes to use newer features of the language and library. - - -### Cgo {#cgo} - diff --git a/doc/next/4-runtime.md b/doc/next/4-runtime.md deleted file mode 100644 index 1f8e445e0b1..00000000000 --- a/doc/next/4-runtime.md +++ /dev/null @@ -1 +0,0 @@ -## Runtime {#runtime} diff --git a/doc/next/5-toolchain.md b/doc/next/5-toolchain.md deleted file mode 100644 index b5893288e5c..00000000000 --- a/doc/next/5-toolchain.md +++ /dev/null @@ -1,16 +0,0 @@ -## Compiler {#compiler} - -## Assembler {#assembler} - -## Linker {#linker} - -On 64-bit ARM-based Windows (the `windows/arm64` port), the linker now supports internal -linking mode of cgo programs, which can be requested with the -`-ldflags=-linkmode=internal` flag. - -## Bootstrap {#bootstrap} - - -As mentioned in the [Go 1.24 release notes](/doc/go1.24#bootstrap), Go 1.26 now requires -Go 1.24.6 or later for bootstrap. -We expect that Go 1.28 will require a minor release of Go 1.26 or later for bootstrap. diff --git a/doc/next/6-stdlib/0-heading.md b/doc/next/6-stdlib/0-heading.md deleted file mode 100644 index a992170d433..00000000000 --- a/doc/next/6-stdlib/0-heading.md +++ /dev/null @@ -1,2 +0,0 @@ -## Standard library {#library} - diff --git a/doc/next/6-stdlib/99-minor/0-heading.md b/doc/next/6-stdlib/99-minor/0-heading.md deleted file mode 100644 index e80ebc64c38..00000000000 --- a/doc/next/6-stdlib/99-minor/0-heading.md +++ /dev/null @@ -1,10 +0,0 @@ -### Minor changes to the library {#minor_library_changes} - -#### go/types - -The `Var.Kind` method returns an enumeration of type `VarKind` that -classifies the variable (package-level, local, receiver, parameter, -result, or struct field). See issue #70250. - -Callers of `NewVar` or `NewParam` are encouraged to call `Var.SetKind` -to ensure that this attribute is set correctly in all cases. diff --git a/doc/next/6-stdlib/99-minor/README b/doc/next/6-stdlib/99-minor/README deleted file mode 100644 index fac778de050..00000000000 --- a/doc/next/6-stdlib/99-minor/README +++ /dev/null @@ -1 +0,0 @@ -API changes and other small changes to the standard library go here. diff --git a/doc/next/6-stdlib/99-minor/bytes/73794.md b/doc/next/6-stdlib/99-minor/bytes/73794.md deleted file mode 100644 index a44dfc10e69..00000000000 --- a/doc/next/6-stdlib/99-minor/bytes/73794.md +++ /dev/null @@ -1,2 +0,0 @@ -The new [Buffer.Peek] method returns the next n bytes from the buffer without -advancing it. diff --git a/doc/next/6-stdlib/99-minor/crypto/75300.md b/doc/next/6-stdlib/99-minor/crypto/75300.md deleted file mode 100644 index 02418ea3711..00000000000 --- a/doc/next/6-stdlib/99-minor/crypto/75300.md +++ /dev/null @@ -1,2 +0,0 @@ -The new [Encapsulator] and [Decapsulator] interfaces allow accepting abstract -KEM encapsulation or decapsulation keys. diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdh/75300.md b/doc/next/6-stdlib/99-minor/crypto/ecdh/75300.md deleted file mode 100644 index 5ca55b32158..00000000000 --- a/doc/next/6-stdlib/99-minor/crypto/ecdh/75300.md +++ /dev/null @@ -1,2 +0,0 @@ -The new [KeyExchanger] interface, implemented by [PrivateKey], makes it possible -to accept abstract ECDH private keys, e.g. those implemented in hardware. diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdsa/63963.md b/doc/next/6-stdlib/99-minor/crypto/ecdsa/63963.md deleted file mode 100644 index 81efc00bb5d..00000000000 --- a/doc/next/6-stdlib/99-minor/crypto/ecdsa/63963.md +++ /dev/null @@ -1 +0,0 @@ -The `big.Int` fields of [PublicKey] and [PrivateKey] are now deprecated. diff --git a/doc/next/6-stdlib/99-minor/crypto/mlkem/75300.md b/doc/next/6-stdlib/99-minor/crypto/mlkem/75300.md deleted file mode 100644 index c9cf95f01b2..00000000000 --- a/doc/next/6-stdlib/99-minor/crypto/mlkem/75300.md +++ /dev/null @@ -1,3 +0,0 @@ -The new [DecapsulationKey768.Encapsulator] and -[DecapsulationKey1024.Encapsulator] methods implement the new -[crypto.Decapsulator] interface. diff --git a/doc/next/6-stdlib/99-minor/crypto/mlkem/mlkemtest/73627.md b/doc/next/6-stdlib/99-minor/crypto/mlkem/mlkemtest/73627.md deleted file mode 100644 index 5a475c4ff6b..00000000000 --- a/doc/next/6-stdlib/99-minor/crypto/mlkem/mlkemtest/73627.md +++ /dev/null @@ -1,3 +0,0 @@ -The new [crypto/mlkem/mlkemtest] package exposes the [Encapsulate768] and -[Encapsulate1024] functions which implement derandomized ML-KEM encapsulation, -for use with known-answer tests. diff --git a/doc/next/6-stdlib/99-minor/crypto/rsa/74115.md b/doc/next/6-stdlib/99-minor/crypto/rsa/74115.md deleted file mode 100644 index a3647a79f2e..00000000000 --- a/doc/next/6-stdlib/99-minor/crypto/rsa/74115.md +++ /dev/null @@ -1,5 +0,0 @@ -If [PrivateKey] fields are modified after calling [PrivateKey.Precompute], -[PrivateKey.Validate] now fails. - -[PrivateKey.D] is now checked for consistency with precomputed values, even if -it is not used. diff --git a/doc/next/6-stdlib/99-minor/crypto/rsa/75302.md b/doc/next/6-stdlib/99-minor/crypto/rsa/75302.md deleted file mode 100644 index 611ba261586..00000000000 --- a/doc/next/6-stdlib/99-minor/crypto/rsa/75302.md +++ /dev/null @@ -1,2 +0,0 @@ -Unsafe PKCS #1 v1.5 encryption padding (implemented by [EncryptPKCS1v15], -[DecryptPKCS1v15], and [DecryptPKCS1v15SessionKey]) is now deprecated. diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/75108.md b/doc/next/6-stdlib/99-minor/crypto/tls/75108.md deleted file mode 100644 index 1913f6f5675..00000000000 --- a/doc/next/6-stdlib/99-minor/crypto/tls/75108.md +++ /dev/null @@ -1,2 +0,0 @@ -The [QUICConn] type used by QUIC implementations includes new event -for reporting TLS handshake errors. diff --git a/doc/next/6-stdlib/99-minor/database/sql/driver/67546.md b/doc/next/6-stdlib/99-minor/database/sql/driver/67546.md deleted file mode 100644 index 8cb9089583a..00000000000 --- a/doc/next/6-stdlib/99-minor/database/sql/driver/67546.md +++ /dev/null @@ -1 +0,0 @@ -A database driver may implement [RowsColumnScanner] to entirely override `Scan` behavior. diff --git a/doc/next/6-stdlib/99-minor/debug/elf/75562.md b/doc/next/6-stdlib/99-minor/debug/elf/75562.md deleted file mode 100644 index 306111ddd85..00000000000 --- a/doc/next/6-stdlib/99-minor/debug/elf/75562.md +++ /dev/null @@ -1,4 +0,0 @@ -Additional `R_LARCH_*` constants from [LoongArch ELF psABI v20250521][laelf-20250521] -(global version v2.40) are defined for use with LoongArch systems. - -[laelf-20250521]: https://github.com/loongson/la-abi-specs/blob/v2.40/laelf.adoc diff --git a/doc/next/6-stdlib/99-minor/errors/51945.md b/doc/next/6-stdlib/99-minor/errors/51945.md deleted file mode 100644 index 44ac7222e6d..00000000000 --- a/doc/next/6-stdlib/99-minor/errors/51945.md +++ /dev/null @@ -1,2 +0,0 @@ -The new [AsType] function is a generic version of [As]. It is type-safe, faster, -and, in most cases, easier to use. diff --git a/doc/next/6-stdlib/99-minor/go/ast/68021.md b/doc/next/6-stdlib/99-minor/go/ast/68021.md deleted file mode 100644 index 0ff1a0b11e8..00000000000 --- a/doc/next/6-stdlib/99-minor/go/ast/68021.md +++ /dev/null @@ -1,4 +0,0 @@ -The new [ParseDirective] function parses [directive -comments](/doc/comment#Syntax), which are comments such as `//go:generate`. -Source code tools can support their own directive comments and this new API -should help them implement the conventional syntax. diff --git a/doc/next/6-stdlib/99-minor/go/ast/76031.md b/doc/next/6-stdlib/99-minor/go/ast/76031.md deleted file mode 100644 index 964872f416a..00000000000 --- a/doc/next/6-stdlib/99-minor/go/ast/76031.md +++ /dev/null @@ -1,5 +0,0 @@ -The new [BasicLit.ValueEnd] field records the precise end position of -a literal so that the [BasicLit.End] method can now always return the -correct answer. (Previously it was computed using a heuristic that was -incorrect for multi-line raw string literals in Windows source files, -due to removal of carriage returns.) diff --git a/doc/next/6-stdlib/99-minor/go/token/75849.md b/doc/next/6-stdlib/99-minor/go/token/75849.md deleted file mode 100644 index 4b8a79ff9f1..00000000000 --- a/doc/next/6-stdlib/99-minor/go/token/75849.md +++ /dev/null @@ -1 +0,0 @@ -The new [File.End] convenience method returns the file's end position. diff --git a/doc/next/6-stdlib/99-minor/image/jpeg/75603.md b/doc/next/6-stdlib/99-minor/image/jpeg/75603.md deleted file mode 100644 index 43421761fda..00000000000 --- a/doc/next/6-stdlib/99-minor/image/jpeg/75603.md +++ /dev/null @@ -1,2 +0,0 @@ -The JPEG encoder and decoder have been replaced with new, faster, more accurate implementations. -Code that expects specific bit-for-bit outputs from the encoder or decoder may need to be updated. diff --git a/doc/next/6-stdlib/99-minor/log/slog/65954.md b/doc/next/6-stdlib/99-minor/log/slog/65954.md deleted file mode 100644 index 631ed665df2..00000000000 --- a/doc/next/6-stdlib/99-minor/log/slog/65954.md +++ /dev/null @@ -1,6 +0,0 @@ -The [`NewMultiHandler`](/pkg/log/slog#NewMultiHandler) function creates a -[`MultiHandler`](/pkg/log/slog#MultiHandler) that invokes all the given Handlers. -Its `Enable` method reports whether any of the handlers' `Enabled` methods -return true. -Its `Handle`, `WithAttr` and `WithGroup` methods call the corresponding method -on each of the enabled handlers. diff --git a/doc/next/6-stdlib/99-minor/net/49097.md b/doc/next/6-stdlib/99-minor/net/49097.md deleted file mode 100644 index bb7947b0a11..00000000000 --- a/doc/next/6-stdlib/99-minor/net/49097.md +++ /dev/null @@ -1 +0,0 @@ -Added context aware dial functions for TCP, UDP, IP and Unix networks. diff --git a/doc/next/6-stdlib/99-minor/net/http/67813.md b/doc/next/6-stdlib/99-minor/net/http/67813.md deleted file mode 100644 index 74b8c7644f8..00000000000 --- a/doc/next/6-stdlib/99-minor/net/http/67813.md +++ /dev/null @@ -1,4 +0,0 @@ -The new -[HTTP2Config.StrictMaxConcurrentRequests](/pkg/net/http#HTTP2Config.StrictMaxConcurrentRequests) -field controls whether a new connection should be opened -if an existing HTTP/2 connection has exceeded its stream limit. diff --git a/doc/next/6-stdlib/99-minor/net/http/httptest/31054.md b/doc/next/6-stdlib/99-minor/net/http/httptest/31054.md deleted file mode 100644 index ef6a4898f26..00000000000 --- a/doc/next/6-stdlib/99-minor/net/http/httptest/31054.md +++ /dev/null @@ -1,2 +0,0 @@ -The HTTP client returned by [Server.Client] will now redirect requests for -`example.com` and any subdomains to the server being tested. diff --git a/doc/next/6-stdlib/99-minor/net/http/httputil/73161.md b/doc/next/6-stdlib/99-minor/net/http/httputil/73161.md deleted file mode 100644 index f6318f85534..00000000000 --- a/doc/next/6-stdlib/99-minor/net/http/httputil/73161.md +++ /dev/null @@ -1,11 +0,0 @@ -The [ReverseProxy.Director] configuration field is deprecated -in favor of [ReverseProxy.Rewrite]. - -A malicious client can remove headers added by a `Director` function -by designating those headers as hop-by-hop. Since there is no way to address -this problem within the scope of the `Director` API, we added a new -`Rewrite` hook in Go 1.20. `Rewrite` hooks are provided with both the -unmodified inbound request received by the proxy and the outbound request -which will be sent by the proxy. - -Since the `Director` hook is fundamentally unsafe, we are now deprecating it. diff --git a/doc/next/6-stdlib/99-minor/net/netip/61642.md b/doc/next/6-stdlib/99-minor/net/netip/61642.md deleted file mode 100644 index 3d79f2e76ae..00000000000 --- a/doc/next/6-stdlib/99-minor/net/netip/61642.md +++ /dev/null @@ -1 +0,0 @@ -The new [Prefix.Compare] method compares two prefixes. diff --git a/doc/next/6-stdlib/99-minor/net/url/31024.md b/doc/next/6-stdlib/99-minor/net/url/31024.md deleted file mode 100644 index 11ed31e87c5..00000000000 --- a/doc/next/6-stdlib/99-minor/net/url/31024.md +++ /dev/null @@ -1,4 +0,0 @@ -[Parse] now rejects malformed URLs containing colons in the host subcomponent, -such as `http://::1/` or `http://localhost:80:80/`. -URLs containing bracketed IPv6 addresses, such as `http://[::1]/` are still accepted. -The new GODEBUG=urlstrictcolons=0 setting restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/os/70352.md b/doc/next/6-stdlib/99-minor/os/70352.md deleted file mode 100644 index 5651639dadb..00000000000 --- a/doc/next/6-stdlib/99-minor/os/70352.md +++ /dev/null @@ -1,4 +0,0 @@ -The new [Process.WithHandle] method provides access to an internal process -handle on supported platforms (Linux 5.4 or later and Windows). On Linux, -the process handle is a pidfd. The method returns [ErrNoHandle] on unsupported -platforms or when no process handle is available. diff --git a/doc/next/6-stdlib/99-minor/os/73676.md b/doc/next/6-stdlib/99-minor/os/73676.md deleted file mode 100644 index 70d01f262d8..00000000000 --- a/doc/next/6-stdlib/99-minor/os/73676.md +++ /dev/null @@ -1,4 +0,0 @@ -On Windows, the [OpenFile] `flag` parameter can now contain any combination of -Windows-specific file flags, such as `FILE_FLAG_OVERLAPPED` and -`FILE_FLAG_SEQUENTIAL_SCAN`, for control of file or device caching behavior, -access modes, and other special-purpose flags. \ No newline at end of file diff --git a/doc/next/6-stdlib/99-minor/os/signal/notifycontext.md b/doc/next/6-stdlib/99-minor/os/signal/notifycontext.md deleted file mode 100644 index 74b354b0850..00000000000 --- a/doc/next/6-stdlib/99-minor/os/signal/notifycontext.md +++ /dev/null @@ -1,2 +0,0 @@ -[NotifyContext] now cancels the returned context with [context.CancelCauseFunc] -and an error indicating which signal was received. diff --git a/doc/next/6-stdlib/99-minor/testing/71287.md b/doc/next/6-stdlib/99-minor/testing/71287.md deleted file mode 100644 index 82cac638101..00000000000 --- a/doc/next/6-stdlib/99-minor/testing/71287.md +++ /dev/null @@ -1,18 +0,0 @@ -The new methods [T.ArtifactDir], [B.ArtifactDir], and [F.ArtifactDir] -return a directory in which to write test output files (artifacts). - -When the `-artifacts` flag is provided to `go test`, -this directory will be located under the output directory -(specified with `-outputdir`, or the current directory by default). -Otherwise, artifacts are stored in a temporary directory -which is removed after the test completes. - -The first call to `ArtifactDir` when `-artifacts` is provided -writes the location of the directory to the test log. - -For example, in a test named `TestArtifacts`, -`t.ArtifactDir()` emits: - -``` -=== ARTIFACTS Test /path/to/artifact/dir -``` diff --git a/doc/next/7-ports.md b/doc/next/7-ports.md deleted file mode 100644 index 04e1285f03e..00000000000 --- a/doc/next/7-ports.md +++ /dev/null @@ -1,13 +0,0 @@ -## Ports {#ports} - -### Darwin - - - -Go 1.26 is the last release that will run on macOS 12 Monterey. Go 1.27 will require macOS 13 Ventura or later. - -### Windows - - - -As [announced](/doc/go1.25#windows) in the Go 1.25 release notes, the [broken](/doc/go1.24#windows) 32-bit windows/arm port (`GOOS=windows` `GOARCH=arm`) is removed. diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index b3fcf7db15e..460a6ae265a 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -33,13 +33,17 @@ lable2: MOVV R4, R5 // 85001500 MOVBU R4, R5 // 85fc4303 SUB R4, R5, R6 // a6101100 + SUBW R4, R5, R6 // a6101100 SUBV R4, R5, R6 // a6901100 ADD R4, R5, R6 // a6101000 + ADDW R4, R5, R6 // a6101000 ADDV R4, R5, R6 // a6901000 AND R4, R5, R6 // a6901400 SUB R4, R5 // a5101100 + SUBW R4, R5 // a5101100 SUBV R4, R5 // a5901100 ADD R4, R5 // a5101000 + ADDW R4, R5 // a5101000 ADDV R4, R5 // a5901000 AND R4, R5 // a5901400 NEGW R4, R5 // 05101100 @@ -115,6 +119,8 @@ lable2: MOVV $1, R4 // 04048003 ADD $-1, R4, R5 // 85fcbf02 ADD $-1, R4 // 84fcbf02 + ADDW $-1, R4, R5 // 85fcbf02 + ADDW $-1, R4 // 84fcbf02 ADDV $-1, R4, R5 // 85fcff02 ADDV $-1, R4 // 84fcff02 AND $1, R4, R5 // 85044003 @@ -165,6 +171,8 @@ lable2: // mul MUL R4, R5 // a5101c00 MUL R4, R5, R6 // a6101c00 + MULW R4, R5 // a5101c00 + MULW R4, R5, R6 // a6101c00 MULV R4, R5 // a5901d00 MULV R4, R5, R6 // a6901d00 MULVU R4, R5 // a5901d00 @@ -191,20 +199,26 @@ lable2: MOVHU R4, 1(R5) // a4044029 MOVHU y+8(FP), R4 // 6440402a MOVHU 1(R5), R4 // a404402a - MULU R4, R5 // a5101c00 - MULU R4, R5, R6 // a6101c00 MULH R4, R5 // a5901c00 MULH R4, R5, R6 // a6901c00 MULHU R4, R5 // a5101d00 MULHU R4, R5, R6 // a6101d00 REM R4, R5 // a5902000 REM R4, R5, R6 // a6902000 + REMW R4, R5 // a5902000 + REMW R4, R5, R6 // a6902000 REMU R4, R5 // a5902100 REMU R4, R5, R6 // a6902100 + REMWU R4, R5 // a5902100 + REMWU R4, R5, R6 // a6902100 DIV R4, R5 // a5102000 DIV R4, R5, R6 // a6102000 + DIVW R4, R5 // a5102000 + DIVW R4, R5, R6 // a6102000 DIVU R4, R5 // a5102100 DIVU R4, R5, R6 // a6102100 + DIVWU R4, R5 // a5102100 + DIVWU R4, R5, R6 // a6102100 SRLV R4, R5 // a5101900 SRLV R4, R5, R6 // a6101900 SRLV $4, R4, R5 // 85104500 @@ -1075,6 +1089,150 @@ lable2: XVMULWODVWUW X1, X2, X3 // 4304a374 XVMULWODQVUV X1, X2, X3 // 4384a374 + // [X]VADDW{EV/OD}.{H.B/W.H/D.W/Q.D} instructions + VADDWEVHB V1, V2, V3 // 43041e70 + VADDWEVWH V1, V2, V3 // 43841e70 + VADDWEVVW V1, V2, V3 // 43041f70 + VADDWEVQV V1, V2, V3 // 43841f70 + VADDWODHB V1, V2, V3 // 43042270 + VADDWODWH V1, V2, V3 // 43842270 + VADDWODVW V1, V2, V3 // 43042370 + VADDWODQV V1, V2, V3 // 43842370 + XVADDWEVHB X1, X2, X3 // 43041e74 + XVADDWEVWH X1, X2, X3 // 43841e74 + XVADDWEVVW X1, X2, X3 // 43041f74 + XVADDWEVQV X1, X2, X3 // 43841f74 + XVADDWODHB X1, X2, X3 // 43042274 + XVADDWODWH X1, X2, X3 // 43842274 + XVADDWODVW X1, X2, X3 // 43042374 + XVADDWODQV X1, X2, X3 // 43842374 + + // [X]VSUBW{EV/OD}.{H.B/W.H/D.W/Q.D} instructions + VSUBWEVHB V1, V2, V3 // 43042070 + VSUBWEVWH V1, V2, V3 // 43842070 + VSUBWEVVW V1, V2, V3 // 43042170 + VSUBWEVQV V1, V2, V3 // 43842170 + VSUBWODHB V1, V2, V3 // 43042470 + VSUBWODWH V1, V2, V3 // 43842470 + VSUBWODVW V1, V2, V3 // 43042570 + VSUBWODQV V1, V2, V3 // 43842570 + XVSUBWEVHB X1, X2, X3 // 43042074 + XVSUBWEVWH X1, X2, X3 // 43842074 + XVSUBWEVVW X1, X2, X3 // 43042174 + XVSUBWEVQV X1, X2, X3 // 43842174 + XVSUBWODHB X1, X2, X3 // 43042474 + XVSUBWODWH X1, X2, X3 // 43842474 + XVSUBWODVW X1, X2, X3 // 43042574 + XVSUBWODQV X1, X2, X3 // 43842574 + + // [X]VADDW{EV/OD}.{H.B/W.H/D.W/Q.D}U instructions + VADDWEVHBU V1, V2, V3 // 43042e70 + VADDWEVWHU V1, V2, V3 // 43042f70 + VADDWEVVWU V1, V2, V3 // 43042f70 + VADDWEVQVU V1, V2, V3 // 43842f70 + VADDWODHBU V1, V2, V3 // 43043270 + VADDWODWHU V1, V2, V3 // 43843270 + VADDWODVWU V1, V2, V3 // 43043370 + VADDWODQVU V1, V2, V3 // 43843370 + XVADDWEVHBU X1, X2, X3 // 43042e74 + XVADDWEVWHU X1, X2, X3 // 43842e74 + XVADDWEVVWU X1, X2, X3 // 43042f74 + XVADDWEVQVU X1, X2, X3 // 43842f74 + XVADDWODHBU X1, X2, X3 // 43043274 + XVADDWODWHU X1, X2, X3 // 43843274 + XVADDWODVWU X1, X2, X3 // 43043374 + XVADDWODQVU X1, X2, X3 // 43843374 + + // [X]VSUBW{EV/OD}.{H.B/W.H/D.W/Q.D}U instructions + VSUBWEVHBU V1, V2, V3 // 43043070 + VSUBWEVWHU V1, V2, V3 // 43843070 + VSUBWEVVWU V1, V2, V3 // 43043170 + VSUBWEVQVU V1, V2, V3 // 43843170 + VSUBWODHBU V1, V2, V3 // 43043470 + VSUBWODWHU V1, V2, V3 // 43843470 + VSUBWODVWU V1, V2, V3 // 43043570 + VSUBWODQVU V1, V2, V3 // 43843570 + XVSUBWEVHBU X1, X2, X3 // 43043074 + XVSUBWEVWHU X1, X2, X3 // 43843074 + XVSUBWEVVWU X1, X2, X3 // 43043174 + XVSUBWEVQVU X1, X2, X3 // 43843174 + XVSUBWODHBU X1, X2, X3 // 43043474 + XVSUBWODWHU X1, X2, X3 // 43843474 + XVSUBWODVWU X1, X2, X3 // 43043574 + XVSUBWODQVU X1, X2, X3 // 43843574 + + // [X]VMADD.{B/H/W/D}, [X]VMSUB.{B/H/W/D} instructions + VMADDB V1, V2, V3 // 4304a870 + VMADDH V1, V2, V3 // 4384a870 + VMADDW V1, V2, V3 // 4304a970 + VMADDV V1, V2, V3 // 4384a970 + VMSUBB V1, V2, V3 // 4304aa70 + VMSUBH V1, V2, V3 // 4384aa70 + VMSUBW V1, V2, V3 // 4304ab70 + VMSUBV V1, V2, V3 // 4384ab70 + XVMADDB X1, X2, X3 // 4304a874 + XVMADDH X1, X2, X3 // 4384a874 + XVMADDW X1, X2, X3 // 4304a974 + XVMADDV X1, X2, X3 // 4384a974 + XVMSUBB X1, X2, X3 // 4304aa74 + XVMSUBH X1, X2, X3 // 4384aa74 + XVMSUBW X1, X2, X3 // 4304ab74 + XVMSUBV X1, X2, X3 // 4384ab74 + + // [X]VMADDW{EV/OD}.{H.B/W.H/D.W/Q.D} instructions + VMADDWEVHB V1, V2, V3 // 4304ac70 + VMADDWEVWH V1, V2, V3 // 4384ac70 + VMADDWEVVW V1, V2, V3 // 4304ad70 + VMADDWEVQV V1, V2, V3 // 4384ad70 + VMADDWODHB V1, V2, V3 // 4304ae70 + VMADDWODWH V1, V2, V3 // 4384ae70 + VMADDWODVW V1, V2, V3 // 4304af70 + VMADDWODQV V1, V2, V3 // 4384af70 + XVMADDWEVHB X1, X2, X3 // 4304ac74 + XVMADDWEVWH X1, X2, X3 // 4384ac74 + XVMADDWEVVW X1, X2, X3 // 4304ad74 + XVMADDWEVQV X1, X2, X3 // 4384ad74 + XVMADDWODHB X1, X2, X3 // 4304ae74 + XVMADDWODWH X1, X2, X3 // 4384ae74 + XVMADDWODVW X1, X2, X3 // 4304af74 + XVMADDWODQV X1, X2, X3 // 4384af74 + + // [X]VMADDW{EV/OD}.{H.B/W.H/D.W/Q.D}U instructions + VMADDWEVHBU V1, V2, V3 // 4304b470 + VMADDWEVWHU V1, V2, V3 // 4384b470 + VMADDWEVVWU V1, V2, V3 // 4304b570 + VMADDWEVQVU V1, V2, V3 // 4384b570 + VMADDWODHBU V1, V2, V3 // 4304b670 + VMADDWODWHU V1, V2, V3 // 4384b670 + VMADDWODVWU V1, V2, V3 // 4304b770 + VMADDWODQVU V1, V2, V3 // 4384b770 + XVMADDWEVHBU X1, X2, X3 // 4304b474 + XVMADDWEVWHU X1, X2, X3 // 4384b474 + XVMADDWEVVWU X1, X2, X3 // 4304b574 + XVMADDWEVQVU X1, X2, X3 // 4384b574 + XVMADDWODHBU X1, X2, X3 // 4304b674 + XVMADDWODWHU X1, X2, X3 // 4384b674 + XVMADDWODVWU X1, X2, X3 // 4304b774 + XVMADDWODQVU X1, X2, X3 // 4384b774 + + // [X]VMADDW{EV/OD}.{H.BU.B/W.HU.H/D.WU.W/Q.DU.D} instructions + VMADDWEVHBUB V1, V2, V3 // 4304bc70 + VMADDWEVWHUH V1, V2, V3 // 4384bc70 + VMADDWEVVWUW V1, V2, V3 // 4304bd70 + VMADDWEVQVUV V1, V2, V3 // 4384bd70 + VMADDWODHBUB V1, V2, V3 // 4304be70 + VMADDWODWHUH V1, V2, V3 // 4384be70 + VMADDWODVWUW V1, V2, V3 // 4304bf70 + VMADDWODQVUV V1, V2, V3 // 4384bf70 + XVMADDWEVHBUB X1, X2, X3 // 4304bc74 + XVMADDWEVWHUH X1, X2, X3 // 4384bc74 + XVMADDWEVVWUW X1, X2, X3 // 4304bd74 + XVMADDWEVQVUV X1, X2, X3 // 4384bd74 + XVMADDWODHBUB X1, X2, X3 // 4304be74 + XVMADDWODWHUH X1, X2, X3 // 4384be74 + XVMADDWODVWUW X1, X2, X3 // 4304bf74 + XVMADDWODQVUV X1, X2, X3 // 4384bf74 + // [X]VSHUF4I.{B/H/W/D} instructions VSHUF4IB $0, V2, V1 // 41009073 VSHUF4IB $16, V2, V1 // 41409073 diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc2.s b/src/cmd/asm/internal/asm/testdata/loong64enc2.s index 91aed4e2c70..38f50d2bfc2 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc2.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc2.s @@ -21,6 +21,10 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 ADD $4096, R4, R5 // 3e00001485781000 ADD $65536, R4 // 1e02001484781000 ADD $4096, R4 // 3e00001484781000 + ADDW $65536, R4, R5 // 1e02001485781000 + ADDW $4096, R4, R5 // 3e00001485781000 + ADDW $65536, R4 // 1e02001484781000 + ADDW $4096, R4 // 3e00001484781000 ADDV $65536, R4, R5 // 1e02001485f81000 ADDV $4096, R4, R5 // 3e00001485f81000 ADDV $65536, R4 // 1e02001484f81000 @@ -37,10 +41,6 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 SGTU $4096, R4, R5 // 3e00001485f81200 SGTU $65536, R4 // 1e02001484f81200 SGTU $4096, R4 // 3e00001484f81200 - ADDU $65536, R4, R5 // 1e02001485781000 - ADDU $4096, R4, R5 // 3e00001485781000 - ADDU $65536, R4 // 1e02001484781000 - ADDU $4096, R4 // 3e00001484781000 ADDVU $65536, R4, R5 // 1e02001485f81000 ADDVU $4096, R4, R5 // 3e00001485f81000 ADDVU $65536, R4 // 1e02001484f81000 diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc3.s b/src/cmd/asm/internal/asm/testdata/loong64enc3.s index 2dc6529dcb0..8b5f96bf4a2 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc3.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc3.s @@ -11,12 +11,16 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 MOVV $4096(R4), R5 // 3e000014de03800385f81000 ADD $74565, R4 // 5e020014de178d0384781000 ADD $4097, R4 // 3e000014de07800384781000 + ADDW $74565, R4 // 5e020014de178d0384781000 + ADDW $4097, R4 // 3e000014de07800384781000 ADDV $74565, R4 // 5e020014de178d0384f81000 ADDV $4097, R4 // 3e000014de07800384f81000 AND $74565, R4 // 5e020014de178d0384f81400 AND $4097, R4 // 3e000014de07800384f81400 ADD $74565, R4, R5 // 5e020014de178d0385781000 ADD $4097, R4, R5 // 3e000014de07800385781000 + ADDW $74565, R4, R5 // 5e020014de178d0385781000 + ADDW $4097, R4, R5 // 3e000014de07800385781000 ADDV $74565, R4, R5 // 5e020014de178d0385f81000 ADDV $4097, R4, R5 // 3e000014de07800385f81000 AND $74565, R4, R5 // 5e020014de178d0385f81400 @@ -107,10 +111,6 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 SGTU $74565, R4, R5 // 5e020014de178d0385f81200 SGTU $4097, R4 // 3e000014de07800384f81200 SGTU $4097, R4, R5 // 3e000014de07800385f81200 - ADDU $74565, R4 // 5e020014de178d0384781000 - ADDU $74565, R4, R5 // 5e020014de178d0385781000 - ADDU $4097, R4 // 3e000014de07800384781000 - ADDU $4097, R4, R5 // 3e000014de07800385781000 ADDVU $4097, R4 // 3e000014de07800384f81000 ADDVU $4097, R4, R5 // 3e000014de07800385f81000 ADDVU $74565, R4 // 5e020014de178d0384f81000 diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index a4676cd0a9b..9a0fa27470a 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -1871,6 +1871,10 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // zeroX15 zeroes the X15 register. func zeroX15(s *ssagen.State) { + if !buildcfg.Experiment.SIMD { + opregreg(s, x86.AXORPS, x86.REG_X15, x86.REG_X15) + return + } vxorps := func(s *ssagen.State) { p := s.Prog(x86.AVXORPS) p.From.Type = obj.TYPE_REG diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go index 43ecb6b4b71..74371104a31 100644 --- a/src/cmd/compile/internal/arm64/ssa.go +++ b/src/cmd/compile/internal/arm64/ssa.go @@ -1322,6 +1322,11 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Name = obj.NAME_EXTERN // AuxInt encodes how many buffer entries we need. p.To.Sym = ir.Syms.GCWriteBarrier[v.AuxInt-1] + case ssa.OpARM64LoweredMemEq: + p := s.Prog(obj.ACALL) + p.To.Type = obj.TYPE_MEM + p.To.Name = obj.NAME_EXTERN + p.To.Sym = ir.Syms.Memequal case ssa.OpARM64LoweredPanicBoundsRR, ssa.OpARM64LoweredPanicBoundsRC, ssa.OpARM64LoweredPanicBoundsCR, ssa.OpARM64LoweredPanicBoundsCC: // Compute the constant we put in the PCData entry for this call. diff --git a/src/cmd/compile/internal/base/base.go b/src/cmd/compile/internal/base/base.go index ee3772c5ca2..405c7938a51 100644 --- a/src/cmd/compile/internal/base/base.go +++ b/src/cmd/compile/internal/base/base.go @@ -5,11 +5,7 @@ package base import ( - "fmt" "os" - "runtime" - "runtime/debug" - "runtime/metrics" ) var atExitFuncs []func() @@ -29,193 +25,3 @@ func Exit(code int) { // To enable tracing support (-t flag), set EnableTrace to true. const EnableTrace = false - -// forEachGC calls fn each GC cycle until it returns false. -func forEachGC(fn func() bool) { - type T [32]byte // large enough to avoid runtime's tiny object allocator - - var finalizer func(*T) - finalizer = func(p *T) { - if fn() { - runtime.SetFinalizer(p, finalizer) - } - } - - finalizer(new(T)) -} - -// AdjustStartingHeap modifies GOGC so that GC should not occur until the heap -// grows to the requested size. This is intended but not promised, though it -// is true-mostly, depending on when the adjustment occurs and on the -// compiler's input and behavior. Once this size is approximately reached -// GOGC is reset to 100; subsequent GCs may reduce the heap below the requested -// size, but this function does not affect that. -// -// -d=gcadjust=1 enables logging of GOGC adjustment events. -// -// NOTE: If you think this code would help startup time in your own -// application and you decide to use it, please benchmark first to see if it -// actually works for you (it may not: the Go compiler is not typical), and -// whatever the outcome, please leave a comment on bug #56546. This code -// uses supported interfaces, but depends more than we like on -// current+observed behavior of the garbage collector, so if many people need -// this feature, we should consider/propose a better way to accomplish it. -func AdjustStartingHeap(requestedHeapGoal uint64) { - logHeapTweaks := Debug.GCAdjust == 1 - mp := runtime.GOMAXPROCS(0) - gcConcurrency := Flag.LowerC - - const ( - goal = "/gc/heap/goal:bytes" - count = "/gc/cycles/total:gc-cycles" - allocs = "/gc/heap/allocs:bytes" - frees = "/gc/heap/frees:bytes" - ) - - sample := []metrics.Sample{{Name: goal}, {Name: count}, {Name: allocs}, {Name: frees}} - const ( - GOAL = 0 - COUNT = 1 - ALLOCS = 2 - FREES = 3 - ) - - // Assumptions and observations of Go's garbage collector, as of Go 1.17-1.20: - - // - the initial heap goal is 4M, by fiat. It is possible for Go to start - // with a heap as small as 512k, so this may change in the future. - - // - except for the first heap goal, heap goal is a function of - // observed-live at the previous GC and current GOGC. After the first - // GC, adjusting GOGC immediately updates GOGC; before the first GC, - // adjusting GOGC does not modify goal (but the change takes effect after - // the first GC). - - // - the before/after first GC behavior is not guaranteed anywhere, it's - // just behavior, and it's a bad idea to rely on it. - - // - we don't know exactly when GC will run, even after we adjust GOGC; the - // first GC may not have happened yet, may have already happened, or may - // be currently in progress, and GCs can start for several reasons. - - // - forEachGC above will run the provided function at some delay after each - // GC's mark phase terminates; finalizers are run after marking as the - // spans containing finalizable objects are swept, driven by GC - // background activity and allocation demand. - - // - "live at last GC" is not available through the current metrics - // interface. Instead, live is estimated by knowing the adjusted value of - // GOGC and the new heap goal following a GC (this requires knowing that - // at least one GC has occurred): - // estLive = 100 * newGoal / (100 + currentGogc) - // this new value of GOGC - // newGogc = 100*requestedHeapGoal/estLive - 100 - // will result in the desired goal. The logging code checks that the - // resulting goal is correct. - - // There's a small risk that the finalizer will be slow to run after a GC - // that expands the goal to a huge value, and that this will lead to - // out-of-memory. This doesn't seem to happen; in experiments on a variety - // of machines with a variety of extra loads to disrupt scheduling, the - // worst overshoot observed was 50% past requestedHeapGoal. - - metrics.Read(sample) - for _, s := range sample { - if s.Value.Kind() == metrics.KindBad { - // Just return, a slightly slower compilation is a tolerable outcome. - if logHeapTweaks { - fmt.Fprintf(os.Stderr, "GCAdjust: Regret unexpected KindBad for metric %s\n", s.Name) - } - return - } - } - - // Tinker with GOGC to make the heap grow rapidly at first. - currentGoal := sample[GOAL].Value.Uint64() // Believe this will be 4MByte or less, perhaps 512k - myGogc := 100 * requestedHeapGoal / currentGoal - if myGogc <= 150 { - return - } - - if logHeapTweaks { - sample := append([]metrics.Sample(nil), sample...) // avoid races with GC callback - AtExit(func() { - metrics.Read(sample) - goal := sample[GOAL].Value.Uint64() - count := sample[COUNT].Value.Uint64() - oldGogc := debug.SetGCPercent(100) - if oldGogc == 100 { - fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %d gogc %d count %d maxprocs %d gcConcurrency %d\n", - goal, oldGogc, count, mp, gcConcurrency) - } else { - inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() - overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) - fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %d gogc %d count %d maxprocs %d gcConcurrency %d overPct %d\n", - goal, oldGogc, count, mp, gcConcurrency, overPct) - - } - }) - } - - debug.SetGCPercent(int(myGogc)) - - adjustFunc := func() bool { - - metrics.Read(sample) - goal := sample[GOAL].Value.Uint64() - count := sample[COUNT].Value.Uint64() - - if goal <= requestedHeapGoal { // Stay the course - if logHeapTweaks { - fmt.Fprintf(os.Stderr, "GCAdjust: Reuse GOGC adjust, current goal %d, count is %d, current gogc %d\n", - goal, count, myGogc) - } - return true - } - - // Believe goal has been adjusted upwards, else it would be less-than-or-equal than requestedHeapGoal - calcLive := 100 * goal / (100 + myGogc) - - if 2*calcLive < requestedHeapGoal { // calcLive can exceed requestedHeapGoal! - myGogc = 100*requestedHeapGoal/calcLive - 100 - - if myGogc > 125 { - // Not done growing the heap. - oldGogc := debug.SetGCPercent(int(myGogc)) - - if logHeapTweaks { - // Check that the new goal looks right - inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() - metrics.Read(sample) - newGoal := sample[GOAL].Value.Uint64() - pctOff := 100 * (int64(newGoal) - int64(requestedHeapGoal)) / int64(requestedHeapGoal) - // Check that the new goal is close to requested. 3% of make.bash fails this test. Why, TBD. - if pctOff < 2 { - fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %d, count is %d, gogc was %d, is now %d, calcLive %d pctOff %d\n", - goal, count, oldGogc, myGogc, calcLive, pctOff) - } else { - // The GC is being annoying and not giving us the goal that we requested, say more to help understand when/why. - fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %d, count is %d, gogc was %d, is now %d, calcLive %d pctOff %d inUse %d\n", - goal, count, oldGogc, myGogc, calcLive, pctOff, inUse) - } - } - return true - } - } - - // In this case we're done boosting GOGC, set it to 100 and don't set a new finalizer. - oldGogc := debug.SetGCPercent(100) - // inUse helps estimate how late the finalizer ran; at the instant the previous GC ended, - // it was (in theory) equal to the previous GC's heap goal. In a growing heap it is - // expected to grow to the new heap goal. - inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() - overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) - if logHeapTweaks { - fmt.Fprintf(os.Stderr, "GCAdjust: Reset GOGC adjust, old goal %d, count is %d, gogc was %d, calcLive %d inUse %d overPct %d\n", - goal, count, oldGogc, calcLive, inUse, overPct) - } - return false - } - - forEachGC(adjustFunc) -} diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index b532bf435e8..5e0268bb881 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -32,12 +32,16 @@ type DebugFlags struct { DwarfInl int `help:"print information about DWARF inlined function creation"` EscapeMutationsCalls int `help:"print extra escape analysis diagnostics about mutations and calls" concurrent:"ok"` EscapeDebug int `help:"print information about escape analysis and resulting optimizations" concurrent:"ok"` + EscapeAlias int `help:"print information about alias analysis" concurrent:"ok"` + EscapeAliasCheck int `help:"enable additional validation for alias analysis" concurrent:"ok"` Export int `help:"print export data"` FIPSHash string `help:"hash value for FIPS debugging" concurrent:"ok"` Fmahash string `help:"hash value for use in debugging platform-dependent multiply-add use" concurrent:"ok"` + FreeAppend int `help:"insert frees of append results when proven safe (0 disabled, 1 enabled, 2 enabled + log)" concurrent:"ok"` GCAdjust int `help:"log adjustments to GOGC" concurrent:"ok"` GCCheck int `help:"check heap/gc use by compiler" concurrent:"ok"` GCProg int `help:"print dump of GC programs"` + GCStart int `help:"specify \"starting\" compiler's heap size in MiB" concurrent:"ok"` Gossahash string `help:"hash value for use in debugging the compiler"` InlFuncsWithClosures int `help:"allow functions with closures to be inlined" concurrent:"ok"` InlStaticInit int `help:"allow static initialization of inlined calls" concurrent:"ok"` diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go index 63cae41524c..4a2b7c5434f 100644 --- a/src/cmd/compile/internal/base/flag.go +++ b/src/cmd/compile/internal/base/flag.go @@ -182,6 +182,7 @@ func ParseFlags() { Debug.AlignHot = 1 Debug.InlFuncsWithClosures = 1 Debug.InlStaticInit = 1 + Debug.FreeAppend = 1 Debug.PGOInline = 1 Debug.PGODevirtualize = 2 Debug.SyncFrames = -1 // disable sync markers by default diff --git a/src/cmd/compile/internal/base/startheap.go b/src/cmd/compile/internal/base/startheap.go new file mode 100644 index 00000000000..1d2713efdbc --- /dev/null +++ b/src/cmd/compile/internal/base/startheap.go @@ -0,0 +1,272 @@ +// Copyright 2025 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 base + +import ( + "fmt" + "os" + "runtime" + "runtime/debug" + "runtime/metrics" + "sync" +) + +// forEachGC calls fn each GC cycle until it returns false. +func forEachGC(fn func() bool) { + type T [32]byte // large enough to avoid runtime's tiny object allocator + var finalizer func(*T) + finalizer = func(p *T) { + + if fn() { + runtime.SetFinalizer(p, finalizer) + } + } + + finalizer(new(T)) +} + +// AdjustStartingHeap modifies GOGC so that GC should not occur until the heap +// grows to the requested size. This is intended but not promised, though it +// is true-mostly, depending on when the adjustment occurs and on the +// compiler's input and behavior. Once the live heap is approximately half +// this size, GOGC is reset to its value when AdjustStartingHeap was called; +// subsequent GCs may reduce the heap below the requested size, but this +// function does not affect that. +// +// logHeapTweaks (-d=gcadjust=1) enables logging of GOGC adjustment events. +// +// The temporarily requested GOGC is derated from what would be the "obvious" +// value necessary to hit the starting heap goal because the obvious +// (goal/live-1)*100 value seems to grow RSS a little more than it "should" +// (compared to GOMEMLIMIT, e.g.) and the assumption is that the GC's control +// algorithms are tuned for GOGC near 100, and not tuned for huge values of +// GOGC. Different derating factors apply for "lo" and "hi" values of GOGC; +// lo is below derateBreak, hi is above derateBreak. The derating factors, +// expressed as integer percentages, are derateLoPct and derateHiPct. +// 60-75 is an okay value for derateLoPct, 30-65 seems like a good value for +// derateHiPct, and 600 seems like a good value for derateBreak. If these +// are zero, defaults are used instead. +// +// NOTE: If you think this code would help startup time in your own +// application and you decide to use it, please benchmark first to see if it +// actually works for you (it may not: the Go compiler is not typical), and +// whatever the outcome, please leave a comment on bug #56546. This code +// uses supported interfaces, but depends more than we like on +// current+observed behavior of the garbage collector, so if many people need +// this feature, we should consider/propose a better way to accomplish it. +func AdjustStartingHeap(requestedHeapGoal, derateBreak, derateLoPct, derateHiPct uint64, logHeapTweaks bool) { + mp := runtime.GOMAXPROCS(0) + + const ( + SHgoal = "/gc/heap/goal:bytes" + SHcount = "/gc/cycles/total:gc-cycles" + SHallocs = "/gc/heap/allocs:bytes" + SHfrees = "/gc/heap/frees:bytes" + ) + + var sample = []metrics.Sample{{Name: SHgoal}, {Name: SHcount}, {Name: SHallocs}, {Name: SHfrees}} + + const ( + SH_GOAL = 0 + SH_COUNT = 1 + SH_ALLOCS = 2 + SH_FREES = 3 + + MB = 1_000_000 + ) + + // These particular magic numbers are designed to make the RSS footprint of -d=-gcstart=2000 + // resemble that of GOMEMLIMIT=2000MiB GOGC=10000 when building large projects + // (e.g. the Go compiler itself, and the microsoft's typescript AST package), + // with the further restriction that these magic numbers did a good job of reducing user-cpu + // for builds at either gcstart=2000 or gcstart=128. + // + // The benchmarking to obtain this was (a version of): + // + // for i in {1..50} ; do + // for what in std cmd/compile cmd/fix cmd/go github.com/microsoft/typescript-go/internal/ast ; do + // whatbase=`basename ${what}` + // for sh in 128 2000 ; do + // for br in 500 600 ; do + // for shlo in 65 70; do + // for shhi in 55 60 ; do + // benchcmd -n=2 ${whatbase} go build -a \ + // -gcflags=all=-d=gcstart=${sh},gcstartloderate=${shlo},gcstarthiderate=${shhi},gcstartbreak=${br} \ + // ${what} | tee -a startheap${sh}_${br}_${shhi}_${shlo}.bench + // done + // done + // done + // done + // done + // done + // + // benchcmd is "go install github.com/aclements/go-misc/benchcmd@latest" + + if derateBreak == 0 { + derateBreak = 600 + } + if derateLoPct == 0 { + derateLoPct = 70 + } + if derateHiPct == 0 { + derateHiPct = 55 + } + + gogcDerate := func(myGogc uint64) uint64 { + if myGogc < derateBreak { + return (myGogc * derateLoPct) / 100 + } + return (myGogc * derateHiPct) / 100 + } + + // Assumptions and observations of Go's garbage collector, as of Go 1.17-1.20: + + // - the initial heap goal is 4MiB, by fiat. It is possible for Go to start + // with a heap as small as 512k, so this may change in the future. + + // - except for the first heap goal, heap goal is a function of + // observed-live at the previous GC and current GOGC. After the first + // GC, adjusting GOGC immediately updates GOGC; before the first GC, + // adjusting GOGC does not modify goal (but the change takes effect after + // the first GC). + + // - the before/after first GC behavior is not guaranteed anywhere, it's + // just behavior, and it's a bad idea to rely on it. + + // - we don't know exactly when GC will run, even after we adjust GOGC; the + // first GC may not have happened yet, may have already happened, or may + // be currently in progress, and GCs can start for several reasons. + + // - forEachGC above will run the provided function at some delay after each + // GC's mark phase terminates; finalizers are run after marking as the + // spans containing finalizable objects are swept, driven by GC + // background activity and allocation demand. + + // - "live at last GC" is not available through the current metrics + // interface. Instead, live is estimated by knowing the adjusted value of + // GOGC and the new heap goal following a GC (this requires knowing that + // at least one GC has occurred): + // estLive = 100 * newGoal / (100 + currentGogc) + // this new value of GOGC + // newGogc = 100*requestedHeapGoal/estLive - 100 + // will result in the desired goal. The logging code checks that the + // resulting goal is correct. + + // There's a small risk that the finalizer will be slow to run after a GC + // that expands the goal to a huge value, and that this will lead to + // out-of-memory. This doesn't seem to happen; in experiments on a variety + // of machines with a variety of extra loads to disrupt scheduling, the + // worst overshoot observed was 50% past requestedHeapGoal. + + metrics.Read(sample) + for _, s := range sample { + if s.Value.Kind() == metrics.KindBad { + // Just return, a slightly slower compilation is a tolerable outcome. + if logHeapTweaks { + fmt.Fprintf(os.Stderr, "GCAdjust: Regret unexpected KindBad for metric %s\n", s.Name) + } + return + } + } + + // Tinker with GOGC to make the heap grow rapidly at first. + currentGoal := sample[SH_GOAL].Value.Uint64() // Believe this will be 4MByte or less, perhaps 512k + myGogc := 100 * requestedHeapGoal / currentGoal + myGogc = gogcDerate(myGogc) + if myGogc <= 125 { + return + } + + if logHeapTweaks { + sample := append([]metrics.Sample(nil), sample...) // avoid races with GC callback + AtExit(func() { + metrics.Read(sample) + goal := sample[SH_GOAL].Value.Uint64() + count := sample[SH_COUNT].Value.Uint64() + oldGogc := debug.SetGCPercent(100) + if oldGogc == 100 { + fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %dMB gogc %d count %d maxprocs %d\n", + goal/MB, oldGogc, count, mp) + } else { + inUse := sample[SH_ALLOCS].Value.Uint64() - sample[SH_FREES].Value.Uint64() + overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) + fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %dMB gogc %d count %d maxprocs %d overPct %d\n", + goal/MB, oldGogc, count, mp, overPct) + + } + }) + } + + originalGOGC := debug.SetGCPercent(int(myGogc)) + + // forEachGC finalizers ought not overlap, but they could run in separate threads. + // This ought not matter, but just in case it bothers the/a race detector, + // use this mutex. + var forEachGCLock sync.Mutex + + adjustFunc := func() bool { + + forEachGCLock.Lock() + defer forEachGCLock.Unlock() + + metrics.Read(sample) + goal := sample[SH_GOAL].Value.Uint64() + count := sample[SH_COUNT].Value.Uint64() + + if goal <= requestedHeapGoal { // Stay the course + if logHeapTweaks { + fmt.Fprintf(os.Stderr, "GCAdjust: Reuse GOGC adjust, current goal %dMB, count is %d, current gogc %d\n", + goal/MB, count, myGogc) + } + return true + } + + // Believe goal has been adjusted upwards, else it would be less-than-or-equal to requestedHeapGoal + calcLive := 100 * goal / (100 + myGogc) + + if 2*calcLive < requestedHeapGoal { // calcLive can exceed requestedHeapGoal! + myGogc = 100*requestedHeapGoal/calcLive - 100 + myGogc = gogcDerate(myGogc) + + if myGogc > 125 { + // Not done growing the heap. + oldGogc := debug.SetGCPercent(int(myGogc)) + + if logHeapTweaks { + // Check that the new goal looks right + inUse := sample[SH_ALLOCS].Value.Uint64() - sample[SH_FREES].Value.Uint64() + metrics.Read(sample) + newGoal := sample[SH_GOAL].Value.Uint64() + pctOff := 100 * (int64(newGoal) - int64(requestedHeapGoal)) / int64(requestedHeapGoal) + // Check that the new goal is close to requested. 3% of make.bash fails this test. Why, TBD. + if pctOff < 2 { + fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %dMB, count is %d, gogc was %d, is now %d, calcLive %dMB pctOff %d\n", + goal/MB, count, oldGogc, myGogc, calcLive/MB, pctOff) + } else { + // The GC is being annoying and not giving us the goal that we requested, say more to help understand when/why. + fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %dMB, count is %d, gogc was %d, is now %d, calcLive %dMB pctOff %d inUse %dMB\n", + goal/MB, count, oldGogc, myGogc, calcLive/MB, pctOff, inUse/MB) + } + } + return true + } + } + + // In this case we're done boosting GOGC, set it to its original value and don't set a new finalizer. + oldGogc := debug.SetGCPercent(originalGOGC) + // inUse helps estimate how late the finalizer ran; at the instant the previous GC ended, + // it was (in theory) equal to the previous GC's heap goal. In a growing heap it is + // expected to grow to the new heap goal. + if logHeapTweaks { + inUse := sample[SH_ALLOCS].Value.Uint64() - sample[SH_FREES].Value.Uint64() + overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) + fmt.Fprintf(os.Stderr, "GCAdjust: Reset GOGC adjust, old goal %dMB, count is %d, gogc was %d, gogc is now %d, calcLive %dMB inUse %dMB overPct %d\n", + goal/MB, count, oldGogc, originalGOGC, calcLive/MB, inUse/MB, overPct) + } + return false + } + + forEachGC(adjustFunc) +} diff --git a/src/cmd/compile/internal/bloop/bloop.go b/src/cmd/compile/internal/bloop/bloop.go index 1e7f915daaa..56fe9a424d6 100644 --- a/src/cmd/compile/internal/bloop/bloop.go +++ b/src/cmd/compile/internal/bloop/bloop.go @@ -42,40 +42,50 @@ import ( "cmd/compile/internal/reflectdata" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" - "fmt" + "cmd/internal/src" ) // getNameFromNode tries to iteratively peel down the node to // get the name. func getNameFromNode(n ir.Node) *ir.Name { - var ret *ir.Name - if n.Op() == ir.ONAME { - ret = n.(*ir.Name) - } else { - // avoid infinite recursion on circular referencing nodes. - seen := map[ir.Node]bool{n: true} - var findName func(ir.Node) bool - findName = func(a ir.Node) bool { - if a.Op() == ir.ONAME { - ret = a.(*ir.Name) - return true - } - if !seen[a] { - seen[a] = true - return ir.DoChildren(a, findName) - } - return false + // Tries to iteratively peel down the node to get the names. + for n != nil { + switch n.Op() { + case ir.ONAME: + // Found the name, stop the loop. + return n.(*ir.Name) + case ir.OSLICE, ir.OSLICE3: + n = n.(*ir.SliceExpr).X + case ir.ODOT: + n = n.(*ir.SelectorExpr).X + case ir.OCONV, ir.OCONVIFACE, ir.OCONVNOP: + n = n.(*ir.ConvExpr).X + case ir.OADDR: + n = n.(*ir.AddrExpr).X + case ir.ODOTPTR: + n = n.(*ir.SelectorExpr).X + case ir.OINDEX, ir.OINDEXMAP: + n = n.(*ir.IndexExpr).X + default: + n = nil } - ir.DoChildren(n, findName) } - return ret + return nil +} + +// getAddressableNameFromNode is like getNameFromNode but returns nil if the node is not addressable. +func getAddressableNameFromNode(n ir.Node) *ir.Name { + if name := getNameFromNode(n); name != nil && ir.IsAddressable(name) { + return name + } + return nil } // keepAliveAt returns a statement that is either curNode, or a -// block containing curNode followed by a call to runtime.keepAlive for each -// ONAME in ns. These calls ensure that names in ns will be live until +// block containing curNode followed by a call to runtime.KeepAlive for each +// node in ns. These calls ensure that nodes in ns will be live until // after curNode's execution. -func keepAliveAt(ns []*ir.Name, curNode ir.Node) ir.Node { +func keepAliveAt(ns []ir.Node, curNode ir.Node) ir.Node { if len(ns) == 0 { return curNode } @@ -92,7 +102,10 @@ func keepAliveAt(ns []*ir.Name, curNode ir.Node) ir.Node { if n.Sym().IsBlank() { continue } - arg := ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], n) + if !ir.IsAddressable(n) { + base.FatalfAt(n.Pos(), "keepAliveAt: node %v is not addressable", n) + } + arg := ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TUNSAFEPTR], typecheck.NodAddr(n)) if !n.Type().IsInterface() { srcRType0 := reflectdata.TypePtrAt(pos, n.Type()) arg.TypeWord = srcRType0 @@ -109,12 +122,12 @@ func keepAliveAt(ns []*ir.Name, curNode ir.Node) ir.Node { return ir.NewBlockStmt(pos, calls) } -func debugName(name *ir.Name, line string) { - if base.Flag.LowerM > 0 { +func debugName(name *ir.Name, pos src.XPos) { + if base.Flag.LowerM > 1 { if name.Linksym() != nil { - fmt.Printf("%v: %s will be kept alive\n", line, name.Linksym().Name) + base.WarnfAt(pos, "%s will be kept alive", name.Linksym().Name) } else { - fmt.Printf("%v: expr will be kept alive\n", line) + base.WarnfAt(pos, "expr will be kept alive") } } } @@ -127,31 +140,52 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { switch n := stmt.(type) { case *ir.AssignStmt: // Peel down struct and slice indexing to get the names - name := getNameFromNode(n.X) + name := getAddressableNameFromNode(n.X) if name != nil { - debugName(name, ir.Line(stmt)) - ret = keepAliveAt([]*ir.Name{name}, n) + debugName(name, n.Pos()) + ret = keepAliveAt([]ir.Node{name}, n) + } else if deref := n.X.(*ir.StarExpr); deref != nil { + ret = keepAliveAt([]ir.Node{deref}, n) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "dereference will be kept alive") + } + } else if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "expr is unknown to bloop pass") } case *ir.AssignListStmt: - names := []*ir.Name{} + ns := []ir.Node{} for _, lhs := range n.Lhs { - name := getNameFromNode(lhs) + name := getAddressableNameFromNode(lhs) if name != nil { - debugName(name, ir.Line(stmt)) - names = append(names, name) + debugName(name, n.Pos()) + ns = append(ns, name) + } else if deref := lhs.(*ir.StarExpr); deref != nil { + ns = append(ns, deref) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "dereference will be kept alive") + } + } else if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "expr is unknown to bloop pass") } } - ret = keepAliveAt(names, n) + ret = keepAliveAt(ns, n) case *ir.AssignOpStmt: - name := getNameFromNode(n.X) + name := getAddressableNameFromNode(n.X) if name != nil { - debugName(name, ir.Line(stmt)) - ret = keepAliveAt([]*ir.Name{name}, n) + debugName(name, n.Pos()) + ret = keepAliveAt([]ir.Node{name}, n) + } else if deref := n.X.(*ir.StarExpr); deref != nil { + ret = keepAliveAt([]ir.Node{deref}, n) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "dereference will be kept alive") + } + } else if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "expr is unknown to bloop pass") } case *ir.CallExpr: - names := []*ir.Name{} curNode := stmt if n.Fun != nil && n.Fun.Type() != nil && n.Fun.Type().NumResults() != 0 { + ns := []ir.Node{} // This function's results are not assigned, assign them to // auto tmps and then keepAliveAt these autos. // Note: markStmt assumes the context that it's called - this CallExpr is @@ -161,7 +195,7 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { for i, res := range results { tmp := typecheck.TempAt(n.Pos(), curFn, res.Type) lhs[i] = tmp - names = append(names, tmp) + ns = append(ns, tmp) } // Create an assignment statement. @@ -174,33 +208,35 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { if len(results) > 1 { plural = "s" } - if base.Flag.LowerM > 0 { - fmt.Printf("%v: function result%s will be kept alive\n", ir.Line(stmt), plural) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "function result%s will be kept alive", plural) } + ret = keepAliveAt(ns, curNode) } else { // This function probably doesn't return anything, keep its args alive. argTmps := []ir.Node{} + names := []ir.Node{} for i, a := range n.Args { - if name := getNameFromNode(a); name != nil { + if name := getAddressableNameFromNode(a); name != nil { // If they are name, keep them alive directly. - debugName(name, ir.Line(stmt)) + debugName(name, n.Pos()) names = append(names, name) } else if a.Op() == ir.OSLICELIT { // variadic args are encoded as slice literal. s := a.(*ir.CompLitExpr) - ns := []*ir.Name{} - for i, n := range s.List { - if name := getNameFromNode(n); name != nil { - debugName(name, ir.Line(a)) + ns := []ir.Node{} + for i, elem := range s.List { + if name := getAddressableNameFromNode(elem); name != nil { + debugName(name, n.Pos()) ns = append(ns, name) } else { // We need a temporary to save this arg. - tmp := typecheck.TempAt(n.Pos(), curFn, n.Type()) - argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(n.Pos(), tmp, n))) + tmp := typecheck.TempAt(elem.Pos(), curFn, elem.Type()) + argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(elem.Pos(), tmp, elem))) names = append(names, tmp) s.List[i] = tmp - if base.Flag.LowerM > 0 { - fmt.Printf("%v: function arg will be kept alive\n", ir.Line(n)) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "function arg will be kept alive") } } } @@ -212,8 +248,8 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(n.Pos(), tmp, a))) names = append(names, tmp) n.Args[i] = tmp - if base.Flag.LowerM > 0 { - fmt.Printf("%v: function arg will be kept alive\n", ir.Line(stmt)) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "function arg will be kept alive") } } } @@ -221,8 +257,8 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { argTmps = append(argTmps, n) curNode = ir.NewBlockStmt(n.Pos(), argTmps) } + ret = keepAliveAt(names, curNode) } - ret = keepAliveAt(names, curNode) } return } @@ -282,6 +318,8 @@ func (e editor) edit(n ir.Node) ir.Node { preserveStmts(e.curFn, n.Body) case *ir.CommClause: preserveStmts(e.curFn, n.Body) + case *ir.RangeStmt: + preserveStmts(e.curFn, n.Body) } } return n diff --git a/src/cmd/compile/internal/escape/alias.go b/src/cmd/compile/internal/escape/alias.go new file mode 100644 index 00000000000..f0351e8f671 --- /dev/null +++ b/src/cmd/compile/internal/escape/alias.go @@ -0,0 +1,528 @@ +// Copyright 2025 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 escape + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/src" + "fmt" + "maps" + "path/filepath" +) + +type aliasAnalysis struct { + // fn is the function being analyzed. + fn *ir.Func + + // candidateSlices are declared slices that + // start unaliased and might still be unaliased. + candidateSlices map[*ir.Name]candidateSlice + + // noAliasAppends are appends that have been + // proven to use an unaliased slice. + noAliasAppends []*ir.CallExpr + + // loops is a stack of observed loops, + // each with a list of candidate appends. + loops [][]candidateAppend + + // State for optional validation checking (doubleCheck mode): + processed map[ir.Node]int // count of times each node was processed, for doubleCheck mode + doubleCheck bool // whether to do doubleCheck mode +} + +// candidateSlice tracks information about a declared slice +// that might be unaliased. +type candidateSlice struct { + loopDepth int // depth of loop when slice was declared +} + +// candidateAppend tracks information about an OAPPEND that +// might be using an unaliased slice. +type candidateAppend struct { + s *ir.Name // the slice argument in 's = append(s, ...)' + call *ir.CallExpr // the append call +} + +// aliasAnalysis looks for specific patterns of slice usage and proves +// that certain appends are operating on non-aliased slices. +// +// This allows us to emit calls to free the backing arrays for certain +// non-aliased slices at runtime when we know the memory is logically dead. +// +// The analysis is conservative, giving up on any operation we do not +// explicitly understand. +func (aa *aliasAnalysis) analyze(fn *ir.Func) { + // Walk the function body to discover slice declarations, their uses, + // and any append that we can prove is using an unaliased slice. + // + // An example is: + // + // var s []T + // for _, v := range input { + // f() + // s = append(s, g(v)) // s cannot be aliased here + // h() + // } + // return s + // + // Here, we can prove that the append to s is operating on an unaliased slice, + // and that conclusion is unaffected by s later being returned and escaping. + // + // In contrast, in this example, the aliasing of s in the loop body means the + // append can be operating on an aliased slice, so we do not record s as unaliased: + // + // var s []T + // var alias []T + // for _, v := range input { + // s = append(s, v) // s is aliased on second pass through loop body + // alias = s + // } + // + // Arbitrary uses of s after an append do not affect the aliasing conclusion + // for that append, but only if the append cannot be revisited at execution time + // via a loop or goto. + // + // We track the loop depth when a slice was declared and verify all uses of a slice + // are non-aliasing until we return to that depth. In other words, we make sure + // we have processed any possible execution-time revisiting of the slice prior + // to making our final determination. + // + // This approach helps for example with nested loops, such as: + // + // var s []int + // for range 10 { + // for range 10 { + // s = append(s, 0) // s is proven as non-aliased here + // } + // } + // alias = s // both loops are complete + // + // Or in contrast: + // + // var s []int + // for range 10 { + // for range 10 { + // s = append(s, 0) // s is treated as aliased here + // } + // alias = s // aliased, and outermost loop cycles back + // } + // + // As we walk the function, we look for things like: + // + // 1. Slice declarations (currently supporting 'var s []T', 's := make([]T, ...)', + // and 's := []T{...}'). + // 2. Appends to a slice of the form 's = append(s, ...)'. + // 3. Other uses of the slice, which we treat as potential aliasing outside + // of a few known safe cases. + // 4. A start of a loop, which we track in a stack so that + // any uses of a slice within a loop body are treated as potential + // aliasing, including statements in the loop body after an append. + // Candidate appends are stored in the loop stack at the loop depth of their + // corresponding slice declaration (rather than the loop depth of the append), + // which essentially postpones a decision about the candidate append. + // 5. An end of a loop, which pops the loop stack and allows us to + // conclusively treat candidate appends from the loop body based + // on the loop depth of the slice declaration. + // + // Note that as we pop a candidate append at the end of a loop, we know + // its corresponding slice was unaliased throughout the loop being popped + // if the slice is still in the candidate slice map (without having been + // removed for potential aliasing), and we know we can make a final decision + // about a candidate append if we have returned to the loop depth + // where its slice was declared. In other words, there is no unanalyzed + // control flow that could take us back at execution-time to the + // candidate append in the now analyzed loop. This helps for example + // with nested loops, such as in our examples just above. + // + // We give up on a particular candidate slice if we see any use of it + // that we don't explicitly understand, and we give up on all of + // our candidate slices if we see any goto or label, which could be + // unstructured control flow. (TODO(thepudds): we remove the goto/label + // restriction in a subsequent CL.) + // + // Note that the intended use is to indicate that a slice is safe to pass + // to runtime.freegc, which currently requires that the passed pointer + // point to the base of its heap object. + // + // Therefore, we currently do not allow any re-slicing of the slice, though we could + // potentially allow s[0:x] or s[:x] or similar. (Slice expressions that alter + // the capacity might be possible to allow with freegc changes, though they are + // currently disallowed here like all slice expressions). + // + // TODO(thepudds): we could support the slice being used as non-escaping function call parameter + // but to do that, we need to verify any creation of specials via user code triggers an escape, + // or mail better runtime.freegc support for specials, or have a temporary compile-time solution + // for specials. (Currently, this analysis side-steps specials because any use of a slice + // that might cause a user-created special will cause it to be treated as aliased, and + // separately, runtime.freegc handles profiling-related specials). + + // Initialize. + aa.fn = fn + aa.candidateSlices = make(map[*ir.Name]candidateSlice) // slices that might be unaliased + + // doubleCheck controls whether we do a sanity check of our processing logic + // by counting each node visited in our main pass, and then comparing those counts + // against a simple walk at the end. The main intent is to help catch missing + // any nodes squirreled away in some spot we forgot to examine in our main pass. + aa.doubleCheck = base.Debug.EscapeAliasCheck > 0 + aa.processed = make(map[ir.Node]int) + + if base.Debug.EscapeAlias >= 2 { + aa.diag(fn.Pos(), fn, "====== starting func", "======") + } + + ir.DoChildren(fn, aa.visit) + + for _, call := range aa.noAliasAppends { + if base.Debug.EscapeAlias >= 1 { + base.WarnfAt(call.Pos(), "alias analysis: append using non-aliased slice: %v in func %v", + call, fn) + } + if base.Debug.FreeAppend > 0 { + call.AppendNoAlias = true + } + } + + if aa.doubleCheck { + doubleCheckProcessed(fn, aa.processed) + } +} + +func (aa *aliasAnalysis) visit(n ir.Node) bool { + if n == nil { + return false + } + + if base.Debug.EscapeAlias >= 3 { + fmt.Printf("%-25s alias analysis: visiting node: %12s %-18T %v\n", + fmtPosShort(n.Pos())+":", n.Op().String(), n, n) + } + + // As we visit nodes, we want to ensure we handle all children + // without missing any (through ignorance or future changes). + // We do this by counting nodes as we visit them or otherwise + // declare a node to be fully processed. + // + // In particular, we want to ensure we don't miss the use + // of a slice in some expression that might be an aliasing usage. + // + // When doubleCheck is enabled, we compare the counts + // accumulated in our analysis against counts from a trivial walk, + // failing if there is any mismatch. + // + // This call here counts that we have visited this node n + // via our main visit method. (In contrast, some nodes won't + // be visited by the main visit method, but instead will be + // manually marked via countProcessed when we believe we have fully + // dealt with the node). + aa.countProcessed(n) + + switch n.Op() { + case ir.ODCL: + decl := n.(*ir.Decl) + + if decl.X != nil && decl.X.Type().IsSlice() && decl.X.Class == ir.PAUTO { + s := decl.X + if _, ok := aa.candidateSlices[s]; ok { + base.FatalfAt(n.Pos(), "candidate slice already tracked as candidate: %v", s) + } + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), s, "adding candidate slice", "(loop depth: %d)", len(aa.loops)) + } + aa.candidateSlices[s] = candidateSlice{loopDepth: len(aa.loops)} + } + // No children aside from the declared ONAME. + aa.countProcessed(decl.X) + return false + + case ir.ONAME: + + // We are seeing a name we have not already handled in another case, + // so remove any corresponding candidate slice. + if n.Type().IsSlice() { + name := n.(*ir.Name) + _, ok := aa.candidateSlices[name] + if ok { + delete(aa.candidateSlices, name) + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), name, "removing candidate slice", "") + } + } + } + // No children. + return false + + case ir.OAS2: + n := n.(*ir.AssignListStmt) + aa.analyzeAssign(n, n.Lhs, n.Rhs) + return false + + case ir.OAS: + assign := n.(*ir.AssignStmt) + aa.analyzeAssign(n, []ir.Node{assign.X}, []ir.Node{assign.Y}) + return false + + case ir.OFOR, ir.ORANGE: + aa.visitList(n.Init()) + + if n.Op() == ir.ORANGE { + // TODO(thepudds): previously we visited this range expression + // in the switch just below, after pushing the loop. This current placement + // is more correct, but generate a test or find an example in stdlib or similar + // where it matters. (Our current tests do not complain.) + aa.visit(n.(*ir.RangeStmt).X) + } + + // Push a new loop. + aa.loops = append(aa.loops, nil) + + // Process the loop. + switch n.Op() { + case ir.OFOR: + forstmt := n.(*ir.ForStmt) + aa.visit(forstmt.Cond) + aa.visitList(forstmt.Body) + aa.visit(forstmt.Post) + case ir.ORANGE: + rangestmt := n.(*ir.RangeStmt) + aa.visit(rangestmt.Key) + aa.visit(rangestmt.Value) + aa.visitList(rangestmt.Body) + default: + base.Fatalf("loop not OFOR or ORANGE: %v", n) + } + + // Pop the loop. + var candidateAppends []candidateAppend + candidateAppends, aa.loops = aa.loops[len(aa.loops)-1], aa.loops[:len(aa.loops)-1] + for _, a := range candidateAppends { + // We are done with the loop, so we can validate any candidate appends + // that have not had their slice removed yet. We know a slice is unaliased + // throughout the loop if the slice is still in the candidate slice map. + if cs, ok := aa.candidateSlices[a.s]; ok { + if cs.loopDepth == len(aa.loops) { + // We've returned to the loop depth where the slice was declared and + // hence made it all the way through any loops that started after + // that declaration. + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), a.s, "proved non-aliased append", + "(completed loop, decl at depth: %d)", cs.loopDepth) + } + aa.noAliasAppends = append(aa.noAliasAppends, a.call) + } else if cs.loopDepth < len(aa.loops) { + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), a.s, "cannot prove non-aliased append", + "(completed loop, decl at depth: %d)", cs.loopDepth) + } + } else { + panic("impossible: candidate slice loopDepth > current loop depth") + } + } + } + return false + + case ir.OLEN, ir.OCAP: + n := n.(*ir.UnaryExpr) + if n.X.Op() == ir.ONAME { + // This does not disqualify a candidate slice. + aa.visitList(n.Init()) + aa.countProcessed(n.X) + } else { + ir.DoChildren(n, aa.visit) + } + return false + + case ir.OCLOSURE: + // Give up on all our in-progress slices. + closure := n.(*ir.ClosureExpr) + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), closure.Func, "clearing all in-progress slices due to OCLOSURE", + "(was %d in-progress slices)", len(aa.candidateSlices)) + } + clear(aa.candidateSlices) + return ir.DoChildren(n, aa.visit) + + case ir.OLABEL, ir.OGOTO: + // Give up on all our in-progress slices. + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), n, "clearing all in-progress slices due to label or goto", + "(was %d in-progress slices)", len(aa.candidateSlices)) + } + clear(aa.candidateSlices) + return false + + default: + return ir.DoChildren(n, aa.visit) + } +} + +func (aa *aliasAnalysis) visitList(nodes []ir.Node) { + for _, n := range nodes { + aa.visit(n) + } +} + +// analyzeAssign evaluates the assignment dsts... = srcs... +// +// assign is an *ir.AssignStmt or *ir.AssignListStmt. +func (aa *aliasAnalysis) analyzeAssign(assign ir.Node, dsts, srcs []ir.Node) { + aa.visitList(assign.Init()) + for i := range dsts { + dst := dsts[i] + src := srcs[i] + + if dst.Op() != ir.ONAME || !dst.Type().IsSlice() { + // Nothing for us to do aside from visiting the remaining children. + aa.visit(dst) + aa.visit(src) + continue + } + + // We have a slice being assigned to an ONAME. + + // Check for simple zero value assignments to an ONAME, which we ignore. + if src == nil { + aa.countProcessed(dst) + continue + } + + if base.Debug.EscapeAlias >= 4 { + srcfn := "" + if src.Op() == ir.ONAME { + srcfn = fmt.Sprintf("%v.", src.Name().Curfn) + } + aa.diag(assign.Pos(), assign, "visiting slice assignment", "%v.%v = %s%v (%s %T = %s %T)", + dst.Name().Curfn, dst, srcfn, src, dst.Op().String(), dst, src.Op().String(), src) + } + + // Now check what we have on the RHS. + switch src.Op() { + // Cases: + + // Check for s := make([]T, ...) or s := []T{...}, along with the '=' version + // of those which does not alias s as long as s is not used in the make. + // + // TODO(thepudds): we need to be sure that 's := []T{1,2,3}' does not end up backed by a + // global static. Ad-hoc testing indicates that example and similar seem to be + // stack allocated, but that was not exhaustive testing. We do have runtime.freegc + // able to throw if it finds a global static, but should test more. + // + // TODO(thepudds): could also possibly allow 's := append([]T(nil), ...)' + // and 's := append([]T{}, ...)'. + case ir.OMAKESLICE, ir.OSLICELIT: + name := dst.(*ir.Name) + if name.Class == ir.PAUTO { + if base.Debug.EscapeAlias > 1 { + aa.diag(assign.Pos(), assign, "assignment from make or slice literal", "") + } + // If this is Def=true, the ODCL in the init will causes this to be tracked + // as a candidate slice. We walk the init and RHS but avoid visiting the name + // in the LHS, which would remove the slice from the candidate list after it + // was just added. + aa.visit(src) + aa.countProcessed(name) + continue + } + + // Check for s = append(s, <...>). + case ir.OAPPEND: + s := dst.(*ir.Name) + call := src.(*ir.CallExpr) + if call.Args[0] == s { + // Matches s = append(s, <...>). + // First visit other arguments in case they use s. + aa.visitList(call.Args[1:]) + // Mark the call as processed, and s twice. + aa.countProcessed(s, call, s) + + // We have now examined all non-ONAME children of assign. + + // This is now the heart of the analysis. + // Check to see if this slice is a live candidate. + cs, ok := aa.candidateSlices[s] + if ok { + if cs.loopDepth == len(aa.loops) { + // No new loop has started after the declaration of s, + // so this is definitive. + if base.Debug.EscapeAlias >= 2 { + aa.diag(assign.Pos(), assign, "proved non-aliased append", + "(loop depth: %d, equals decl depth)", len(aa.loops)) + } + aa.noAliasAppends = append(aa.noAliasAppends, call) + } else if cs.loopDepth < len(aa.loops) { + // A new loop has started since the declaration of s, + // so we can't validate this append yet, but + // remember it in case we can validate it later when + // all loops using s are done. + aa.loops[cs.loopDepth] = append(aa.loops[cs.loopDepth], + candidateAppend{s: s, call: call}) + } else { + panic("impossible: candidate slice loopDepth > current loop depth") + } + } + continue + } + } // End of switch on src.Op(). + + // Reached bottom of the loop over assignments. + // If we get here, we need to visit the dst and src normally. + aa.visit(dst) + aa.visit(src) + } +} + +func (aa *aliasAnalysis) countProcessed(nodes ...ir.Node) { + if aa.doubleCheck { + for _, n := range nodes { + aa.processed[n]++ + } + } +} + +func (aa *aliasAnalysis) diag(pos src.XPos, n ir.Node, what string, format string, args ...any) { + fmt.Printf("%-25s alias analysis: %-30s %-20s %s\n", + fmtPosShort(pos)+":", + what+":", + fmt.Sprintf("%v", n), + fmt.Sprintf(format, args...)) +} + +// doubleCheckProcessed does a sanity check for missed nodes in our visit. +func doubleCheckProcessed(fn *ir.Func, processed map[ir.Node]int) { + // Do a trivial walk while counting the nodes + // to compare against the counts in processed. + + observed := make(map[ir.Node]int) + var walk func(n ir.Node) bool + walk = func(n ir.Node) bool { + observed[n]++ + return ir.DoChildren(n, walk) + } + ir.DoChildren(fn, walk) + + if !maps.Equal(processed, observed) { + // The most likely mistake might be something was missed while building processed, + // so print extra details in that direction. + for n, observedCount := range observed { + processedCount, ok := processed[n] + if processedCount != observedCount || !ok { + base.WarnfAt(n.Pos(), + "alias analysis: mismatch for %T: %v: processed %d times, observed %d times", + n, n, processedCount, observedCount) + } + } + base.FatalfAt(fn.Pos(), "alias analysis: mismatch in visited nodes") + } +} + +func fmtPosShort(xpos src.XPos) string { + // TODO(thepudds): I think I did this a simpler way a while ago? Or maybe add base.FmtPosShort + // or similar? Or maybe just use base.FmtPos and give up on nicely aligned log messages? + pos := base.Ctxt.PosTable.Pos(xpos) + shortLine := filepath.Base(pos.AbsFilename()) + ":" + pos.LineNumber() + return shortLine +} diff --git a/src/cmd/compile/internal/escape/escape.go b/src/cmd/compile/internal/escape/escape.go index 59250edfef0..9d01156eb8e 100644 --- a/src/cmd/compile/internal/escape/escape.go +++ b/src/cmd/compile/internal/escape/escape.go @@ -8,6 +8,7 @@ import ( "fmt" "go/constant" "go/token" + "internal/goexperiment" "cmd/compile/internal/base" "cmd/compile/internal/ir" @@ -369,6 +370,16 @@ func (b *batch) finish(fns []*ir.Func) { } } } + + if goexperiment.RuntimeFreegc { + // Look for specific patterns of usage, such as appends + // to slices that we can prove are not aliased. + for _, fn := range fns { + a := aliasAnalysis{} + a.analyze(fn) + } + } + } // inMutualBatch reports whether function fn is in the batch of diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 2a3a3f786b3..afa16508d19 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -83,10 +83,13 @@ func Main(archInit func(*ssagen.ArchInfo)) { base.DebugSSA = ssa.PhaseOption base.ParseFlags() - if os.Getenv("GOGC") == "" { // GOGC set disables starting heap adjustment - // More processors will use more heap, but assume that more memory is available. - // So 1 processor -> 40MB, 4 -> 64MB, 12 -> 128MB - base.AdjustStartingHeap(uint64(32+8*base.Flag.LowerC) << 20) + if flagGCStart := base.Debug.GCStart; flagGCStart > 0 || // explicit flags overrides environment variable disable of GC boost + os.Getenv("GOGC") == "" && os.Getenv("GOMEMLIMIT") == "" && base.Flag.LowerC != 1 { // explicit GC knobs or no concurrency implies default heap + startHeapMB := int64(128) + if flagGCStart > 0 { + startHeapMB = int64(flagGCStart) + } + base.AdjustStartingHeap(uint64(startHeapMB)<<20, 0, 0, 0, base.Debug.GCAdjust == 1) } types.LocalPkg = types.NewPkg(base.Ctxt.Pkgpath, "") diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index 80a0cb97df1..d66f1a84cc9 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -20,6 +20,13 @@ import ( // DevirtualizeAndInlinePackage interleaves devirtualization and inlining on // all functions within pkg. func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { + if base.Flag.W > 1 { + for _, fn := range typecheck.Target.Funcs { + s := fmt.Sprintf("\nbefore devirtualize-and-inline %v", fn.Sym()) + ir.DumpList(s, fn.Body) + } + } + if profile != nil && base.Debug.PGODevirtualize > 0 { // TODO(mdempsky): Integrate into DevirtualizeAndInlineFunc below. ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) { diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 7f156b5e754..f32998f333e 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -184,15 +184,16 @@ func (n *BinaryExpr) SetOp(op Op) { // A CallExpr is a function call Fun(Args). type CallExpr struct { miniExpr - Fun Node - Args Nodes - DeferAt Node - RType Node `mknode:"-"` // see reflectdata/helpers.go - KeepAlive []*Name // vars to be kept alive until call returns - IsDDD bool - GoDefer bool // whether this call is part of a go or defer statement - NoInline bool // whether this call must not be inlined - UseBuf bool // use stack buffer for backing store (OAPPEND only) + Fun Node + Args Nodes + DeferAt Node + RType Node `mknode:"-"` // see reflectdata/helpers.go + KeepAlive []*Name // vars to be kept alive until call returns + IsDDD bool + GoDefer bool // whether this call is part of a go or defer statement + NoInline bool // whether this call must not be inlined + UseBuf bool // use stack buffer for backing store (OAPPEND only) + AppendNoAlias bool // backing store proven to be unaliased (OAPPEND only) // whether it's a runtime.KeepAlive call the compiler generates to // keep a variable alive. See #73137. IsCompilerVarLive bool diff --git a/src/cmd/compile/internal/ir/node_gen.go b/src/cmd/compile/internal/ir/node_gen.go index 4298b3a43d7..2ae0a43e496 100644 --- a/src/cmd/compile/internal/ir/node_gen.go +++ b/src/cmd/compile/internal/ir/node_gen.go @@ -1188,6 +1188,9 @@ func (n *MoveToHeapExpr) doChildren(do func(Node) bool) bool { if n.Slice != nil && do(n.Slice) { return true } + if n.RType != nil && do(n.RType) { + return true + } return false } func (n *MoveToHeapExpr) doChildrenWithHidden(do func(Node) bool) bool { @@ -1198,6 +1201,9 @@ func (n *MoveToHeapExpr) editChildren(edit func(Node) Node) { if n.Slice != nil { n.Slice = edit(n.Slice).(Node) } + if n.RType != nil { + n.RType = edit(n.RType).(Node) + } } func (n *MoveToHeapExpr) editChildrenWithHidden(edit func(Node) Node) { n.editChildren(edit) diff --git a/src/cmd/compile/internal/ir/symtab.go b/src/cmd/compile/internal/ir/symtab.go index 4b5bf17a3de..f0e91f6db36 100644 --- a/src/cmd/compile/internal/ir/symtab.go +++ b/src/cmd/compile/internal/ir/symtab.go @@ -30,6 +30,8 @@ type symsStruct struct { Goschedguarded *obj.LSym Growslice *obj.LSym GrowsliceBuf *obj.LSym + GrowsliceBufNoAlias *obj.LSym + GrowsliceNoAlias *obj.LSym MoveSlice *obj.LSym MoveSliceNoScan *obj.LSym MoveSliceNoCap *obj.LSym @@ -40,6 +42,7 @@ type symsStruct struct { MallocGCSmallScanNoHeader [27]*obj.LSym MallocGCTiny [16]*obj.LSym Memmove *obj.LSym + Memequal *obj.LSym Msanread *obj.LSym Msanwrite *obj.LSym Msanmove *obj.LSym diff --git a/src/cmd/compile/internal/slice/slice.go b/src/cmd/compile/internal/slice/slice.go index 7a32e7adbd2..17eba5b7726 100644 --- a/src/cmd/compile/internal/slice/slice.go +++ b/src/cmd/compile/internal/slice/slice.go @@ -152,6 +152,11 @@ func analyze(fn *ir.Func) { // least weight 2. (Note: appends in loops have weight >= 2.) appendWeight int + // Loop depth at declaration point. + // Use for heuristics only, it is not guaranteed to be correct + // in the presence of gotos. + declDepth int + // Whether we ever do cap(s), or other operations that use cap(s) // (possibly implicitly), like s[i:j]. capUsed bool @@ -209,6 +214,20 @@ func analyze(fn *ir.Func) { i.s.Opt = nil return } + if loopDepth > i.declDepth { + // Conservatively, we disable this optimization when the + // transition is inside a loop. This can result in adding + // overhead unnecessarily in cases like: + // func f(n int, p *[]byte) { + // var s []byte + // for i := range n { + // *p = s + // s = append(s, 0) + // } + // } + i.s.Opt = nil + return + } i.transition = loc } @@ -237,7 +256,7 @@ func analyze(fn *ir.Func) { // s = append(s, ...) is ok i.okUses += 2 i.appends = append(i.appends, y) - i.appendWeight += 1 + loopDepth + i.appendWeight += 1 + (loopDepth - i.declDepth) } // TODO: s = append(nil, ...)? } @@ -277,6 +296,7 @@ func analyze(fn *ir.Func) { n := n.(*ir.Decl) if i := tracking(n.X); i != nil { i.okUses++ + i.declDepth = loopDepth } case ir.OINDEX: n := n.(*ir.IndexExpr) diff --git a/src/cmd/compile/internal/ssa/_gen/ARM64.rules b/src/cmd/compile/internal/ssa/_gen/ARM64.rules index 53bb35d2897..4ade43f1a14 100644 --- a/src/cmd/compile/internal/ssa/_gen/ARM64.rules +++ b/src/cmd/compile/internal/ssa/_gen/ARM64.rules @@ -481,6 +481,7 @@ (GetClosurePtr ...) => (LoweredGetClosurePtr ...) (GetCallerSP ...) => (LoweredGetCallerSP ...) (GetCallerPC ...) => (LoweredGetCallerPC ...) +(MemEq ...) => (LoweredMemEq ...) // Absorb pseudo-ops into blocks. (If (Equal cc) yes no) => (EQ cc yes no) diff --git a/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go b/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go index b710724cca1..c84b24cad12 100644 --- a/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go +++ b/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go @@ -534,7 +534,8 @@ func init() { {name: "CALLinter", argLength: -1, reg: regInfo{inputs: []regMask{gp}, clobbers: callerSave}, aux: "CallOff", clobberFlags: true, call: true}, // call fn by pointer. arg0=codeptr, last arg=mem, auxint=argsize, returns mem // pseudo-ops - {name: "LoweredNilCheck", argLength: 2, reg: regInfo{inputs: []regMask{gpg}}, nilCheck: true, faultOnNilArg0: true}, // panic if arg0 is nil. arg1=mem. + {name: "LoweredNilCheck", argLength: 2, reg: regInfo{inputs: []regMask{gpg}}, nilCheck: true, faultOnNilArg0: true}, // panic if arg0 is nil. arg1=mem. + {name: "LoweredMemEq", argLength: 4, reg: regInfo{inputs: []regMask{buildReg("R0"), buildReg("R1"), buildReg("R2")}, outputs: []regMask{buildReg("R0")}, clobbers: callerSave}, typ: "Bool", faultOnNilArg0: true, faultOnNilArg1: true, clobberFlags: true, call: true}, // arg0, arg1 - pointers to memory, arg2=size, arg3=mem. {name: "Equal", argLength: 1, reg: readflags}, // bool, true flags encode x==y false otherwise. {name: "NotEqual", argLength: 1, reg: readflags}, // bool, true flags encode x!=y false otherwise. diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules index 90ff0b74eca..fe8fc5b2620 100644 --- a/src/cmd/compile/internal/ssa/_gen/generic.rules +++ b/src/cmd/compile/internal/ssa/_gen/generic.rules @@ -1525,6 +1525,41 @@ && isSamePtr(p, q) => (MakeResult (ConstBool [true]) mem) +(MemEq sptr tptr (Const64 [1]) mem) + => (Eq8 (Load sptr mem) (Load tptr mem)) + +(Load sptr:(Addr {scon} (SB)) mem) + && symIsRO(scon) + => (Const8 [int8(read8(scon,0))]) + +(MemEq sptr tptr (Const64 [2]) mem) + && canLoadUnaligned(config) + => (Eq16 (Load sptr mem) (Load tptr mem)) + +(Load sptr:(Addr {scon} (SB)) mem) + && symIsRO(scon) + => (Const16 [int16(read16(scon,0,config.ctxt.Arch.ByteOrder))]) + +(MemEq sptr tptr (Const64 [4]) mem) + && canLoadUnaligned(config) + => (Eq32 (Load sptr mem) (Load tptr mem)) + +(Load sptr:(Addr {scon} (SB)) mem) + && symIsRO(scon) + => (Const32 [int32(read32(scon,0,config.ctxt.Arch.ByteOrder))]) + +(MemEq sptr tptr (Const64 [8]) mem) + && canLoadUnaligned(config) && config.PtrSize == 8 + => (Eq64 (Load sptr mem) (Load tptr mem)) + +(Load sptr:(Addr {scon} (SB)) mem) + && symIsRO(scon) + => (Const64 [int64(read64(scon,0,config.ctxt.Arch.ByteOrder))]) + +(MemEq _ _ (Const64 [0]) _) => (ConstBool [true]) + +(MemEq p q _ _) && isSamePtr(p, q) => (ConstBool [true]) + // Turn known-size calls to memclrNoHeapPointers into a Zero. // Note that we are using types.Types[types.TUINT8] instead of sptr.Type.Elem() - see issue 55122 and CL 431496 for more details. (SelectN [0] call:(StaticCall {sym} sptr (Const(64|32) [c]) mem)) @@ -2019,8 +2054,13 @@ // See issue 56440. // Note there are 2 rules here, one for the pre-decomposed []T result and one for // the post-decomposed (*T,int,int) result. (The latter is generated after call expansion.) -(SliceLen (SelectN [0] (StaticLECall {sym} _ newLen:(Const(64|32)) _ _ _ _))) && isSameCall(sym, "runtime.growslice") => newLen -(SelectN [1] (StaticCall {sym} _ newLen:(Const(64|32)) _ _ _ _)) && v.Type.IsInteger() && isSameCall(sym, "runtime.growslice") => newLen +// TODO(thepudds): we probably need the new growsliceBuf and growsliceBufNoAlias here as well? +(SliceLen (SelectN [0] (StaticLECall {sym} _ newLen:(Const(64|32)) _ _ _ _))) + && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) + => newLen +(SelectN [1] (StaticCall {sym} _ newLen:(Const(64|32)) _ _ _ _)) && v.Type.IsInteger() + && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) + => newLen // Collapse moving A -> B -> C into just A -> C. // Later passes (deadstore, elim unread auto) will remove the A -> B move, if possible. diff --git a/src/cmd/compile/internal/ssa/_gen/genericOps.go b/src/cmd/compile/internal/ssa/_gen/genericOps.go index ce01f2c0e3d..8637133e5f7 100644 --- a/src/cmd/compile/internal/ssa/_gen/genericOps.go +++ b/src/cmd/compile/internal/ssa/_gen/genericOps.go @@ -679,6 +679,9 @@ var genericOps = []opData{ {name: "PrefetchCache", argLength: 2, hasSideEffects: true}, // Do prefetch arg0 to cache. arg0=addr, arg1=memory. {name: "PrefetchCacheStreamed", argLength: 2, hasSideEffects: true}, // Do non-temporal or streamed prefetch arg0 to cache. arg0=addr, arg1=memory. + // Helper instruction which is semantically equivalent to calling runtime.memequal, but some targets may prefer to custom lower it later, e.g. for specific constant sizes. + {name: "MemEq", argLength: 4, commutative: true, typ: "Bool"}, // arg0=ptr0, arg1=ptr1, arg2=size, arg3=memory. + // SIMD {name: "ZeroSIMD", argLength: 0}, // zero value of a vector diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index a875ac69f10..46a986a35a3 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -4409,6 +4409,7 @@ const ( OpARM64CALLclosure OpARM64CALLinter OpARM64LoweredNilCheck + OpARM64LoweredMemEq OpARM64Equal OpARM64NotEqual OpARM64LessThan @@ -6123,6 +6124,7 @@ const ( OpClobberReg OpPrefetchCache OpPrefetchCacheStreamed + OpMemEq OpZeroSIMD OpCvt16toMask8x16 OpCvt32toMask8x32 @@ -68796,6 +68798,25 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "LoweredMemEq", + argLen: 4, + clobberFlags: true, + call: true, + faultOnNilArg0: true, + faultOnNilArg1: true, + reg: regInfo{ + inputs: []inputInfo{ + {0, 1}, // R0 + {1, 2}, // R1 + {2, 4}, // R2 + }, + clobbers: 9223372035109945343, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + outputs: []outputInfo{ + {0, 1}, // R0 + }, + }, + }, { name: "Equal", argLen: 1, @@ -88793,6 +88814,12 @@ var opcodeTable = [...]opInfo{ hasSideEffects: true, generic: true, }, + { + name: "MemEq", + argLen: 4, + commutative: true, + generic: true, + }, { name: "ZeroSIMD", argLen: 0, diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index 5581da445d8..1aab7e3eb77 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -250,9 +250,51 @@ func fitsInBitsU(x uint64, b uint) bool { return x>>b == 0 } +func noLimitForBitsize(bitsize uint) limit { + return limit{min: -(1 << (bitsize - 1)), max: 1<<(bitsize-1) - 1, umin: 0, umax: 1<= slicelen - index >= K // -// Note that "index" is not useed for indexing in this pattern, but +// Note that "index" is not used for indexing in this pattern, but // in the motivating example (chunked slice iteration) it is. func (ft *factsTable) detectSliceLenRelation(v *Value) { if v.Op != OpSub64 { return } - if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpSliceCap) { + if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpStringLen || v.Args[0].Op == OpSliceCap) { return } @@ -2070,9 +2116,9 @@ func (ft *factsTable) detectSliceLenRelation(v *Value) { continue } var lenOffset *Value - if bound := ow.Args[0]; bound.Op == OpSliceLen && bound.Args[0] == slice { + if bound := ow.Args[0]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice { lenOffset = ow.Args[1] - } else if bound := ow.Args[1]; bound.Op == OpSliceLen && bound.Args[0] == slice { + } else if bound := ow.Args[1]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice { lenOffset = ow.Args[0] } if lenOffset == nil || lenOffset.Op != OpConst64 { @@ -2332,7 +2378,7 @@ func unsignedSubUnderflows(a, b uint64) bool { // iteration where the index is not directly compared to the length. // if isReslice, then delta can be equal to K. func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value, isReslice bool) bool { - if bound.Op != OpSliceLen && bound.Op != OpSliceCap { + if bound.Op != OpSliceLen && bound.Op != OpStringLen && bound.Op != OpSliceCap { return false } @@ -2367,9 +2413,9 @@ func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value, i } if ow := o.w; ow.Op == OpAdd64 { var lenOffset *Value - if bound := ow.Args[0]; bound.Op == OpSliceLen && bound.Args[0] == slice { + if bound := ow.Args[0]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice { lenOffset = ow.Args[1] - } else if bound := ow.Args[1]; bound.Op == OpSliceLen && bound.Args[0] == slice { + } else if bound := ow.Args[1]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice { lenOffset = ow.Args[0] } if lenOffset == nil || lenOffset.Op != OpConst64 { diff --git a/src/cmd/compile/internal/ssa/prove_test.go b/src/cmd/compile/internal/ssa/prove_test.go new file mode 100644 index 00000000000..6315049870a --- /dev/null +++ b/src/cmd/compile/internal/ssa/prove_test.go @@ -0,0 +1,76 @@ +// Copyright 2025 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 ssa + +import ( + "math" + "testing" +) + +func testLimitUnaryOpSigned8(t *testing.T, opName string, op func(l limit, bitsize uint) limit, opImpl func(int8) int8) { + sizeLimit := noLimitForBitsize(8) + for min := math.MinInt8; min <= math.MaxInt8; min++ { + for max := min; max <= math.MaxInt8; max++ { + realSmallest, realBiggest := int8(math.MaxInt8), int8(math.MinInt8) + for i := min; i <= max; i++ { + result := opImpl(int8(i)) + if result < realSmallest { + realSmallest = result + } + if result > realBiggest { + realBiggest = result + } + } + + l := limit{int64(min), int64(max), 0, math.MaxUint64} + l = op(l, 8) + l = l.intersect(sizeLimit) // We assume this is gonna be used by newLimit which is seeded by the op size already. + + if l.min != int64(realSmallest) || l.max != int64(realBiggest) { + t.Errorf("%s(%d..%d) = %d..%d; want %d..%d", opName, min, max, l.min, l.max, realSmallest, realBiggest) + } + } + } +} + +func testLimitUnaryOpUnsigned8(t *testing.T, opName string, op func(l limit, bitsize uint) limit, opImpl func(uint8) uint8) { + sizeLimit := noLimitForBitsize(8) + for min := 0; min <= math.MaxUint8; min++ { + for max := min; max <= math.MaxUint8; max++ { + realSmallest, realBiggest := uint8(math.MaxUint8), uint8(0) + for i := min; i <= max; i++ { + result := opImpl(uint8(i)) + if result < realSmallest { + realSmallest = result + } + if result > realBiggest { + realBiggest = result + } + } + + l := limit{math.MinInt64, math.MaxInt64, uint64(min), uint64(max)} + l = op(l, 8) + l = l.intersect(sizeLimit) // We assume this is gonna be used by newLimit which is seeded by the op size already. + + if l.umin != uint64(realSmallest) || l.umax != uint64(realBiggest) { + t.Errorf("%s(%d..%d) = %d..%d; want %d..%d", opName, min, max, l.umin, l.umax, realSmallest, realBiggest) + } + } + } +} + +func TestLimitNegSigned(t *testing.T) { + testLimitUnaryOpSigned8(t, "neg", limit.neg, func(x int8) int8 { return -x }) +} +func TestLimitNegUnsigned(t *testing.T) { + testLimitUnaryOpUnsigned8(t, "neg", limit.neg, func(x uint8) uint8 { return -x }) +} + +func TestLimitComSigned(t *testing.T) { + testLimitUnaryOpSigned8(t, "com", limit.com, func(x int8) int8 { return ^x }) +} +func TestLimitComUnsigned(t *testing.T) { + testLimitUnaryOpUnsigned8(t, "com", limit.com, func(x uint8) uint8 { return ^x }) +} diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go index 11dd53bfc7d..a0257f30641 100644 --- a/src/cmd/compile/internal/ssa/regalloc.go +++ b/src/cmd/compile/internal/ssa/regalloc.go @@ -897,7 +897,15 @@ func (s *regAllocState) dropIfUnused(v *Value) { } vi := &s.values[v.ID] r := vi.uses - if r == nil || (!opcodeTable[v.Op].fixedReg && r.dist > s.nextCall[s.curIdx]) { + nextCall := s.nextCall[s.curIdx] + if opcodeTable[v.Op].call { + if s.curIdx == len(s.nextCall)-1 { + nextCall = math.MaxInt32 + } else { + nextCall = s.nextCall[s.curIdx+1] + } + } + if r == nil || (!opcodeTable[v.Op].fixedReg && r.dist > nextCall) { s.freeRegs(vi.regs) } } @@ -1036,8 +1044,11 @@ func (s *regAllocState) regalloc(f *Func) { regValLiveSet.add(v.ID) } } - if len(s.nextCall) < len(b.Values) { - s.nextCall = append(s.nextCall, make([]int32, len(b.Values)-len(s.nextCall))...) + if cap(s.nextCall) < len(b.Values) { + c := cap(s.nextCall) + s.nextCall = append(s.nextCall[:c], make([]int32, len(b.Values)-c)...) + } else { + s.nextCall = s.nextCall[:len(b.Values)] } var nextCall int32 = math.MaxInt32 for i := len(b.Values) - 1; i >= 0; i-- { diff --git a/src/cmd/compile/internal/ssa/rewriteARM64.go b/src/cmd/compile/internal/ssa/rewriteARM64.go index b3f790dbda5..25a1c9c0fc1 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM64.go +++ b/src/cmd/compile/internal/ssa/rewriteARM64.go @@ -840,6 +840,9 @@ func rewriteValueARM64(v *Value) bool { case OpMax64F: v.Op = OpARM64FMAXD return true + case OpMemEq: + v.Op = OpARM64LoweredMemEq + return true case OpMin32F: v.Op = OpARM64FMINS return true diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index d40333f0196..dbbb7105afa 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -224,6 +224,8 @@ func rewriteValuegeneric(v *Value) bool { return rewriteValuegeneric_OpLsh8x64(v) case OpLsh8x8: return rewriteValuegeneric_OpLsh8x8(v) + case OpMemEq: + return rewriteValuegeneric_OpMemEq(v) case OpMod16: return rewriteValuegeneric_OpMod16(v) case OpMod16u: @@ -11869,6 +11871,8 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + config := b.Func.Config + typ := &b.Func.Config.Types // match: (Load p1 (Store {t2} p2 x _)) // cond: isSamePtr(p1, p2) && copyCompatibleType(t1, x.Type) && t1.Size() == t2.Size() // result: x @@ -12453,6 +12457,102 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { v.AddArg(v0) return true } + // match: (Load sptr:(Addr {scon} (SB)) mem) + // cond: symIsRO(scon) + // result: (Const8 [int8(read8(scon,0))]) + for { + if v.Type != typ.Int8 { + break + } + sptr := v_0 + if sptr.Op != OpAddr { + break + } + scon := auxToSym(sptr.Aux) + sptr_0 := sptr.Args[0] + if sptr_0.Op != OpSB { + break + } + if !(symIsRO(scon)) { + break + } + v.reset(OpConst8) + v.Type = typ.Int8 + v.AuxInt = int8ToAuxInt(int8(read8(scon, 0))) + return true + } + // match: (Load sptr:(Addr {scon} (SB)) mem) + // cond: symIsRO(scon) + // result: (Const16 [int16(read16(scon,0,config.ctxt.Arch.ByteOrder))]) + for { + if v.Type != typ.Int16 { + break + } + sptr := v_0 + if sptr.Op != OpAddr { + break + } + scon := auxToSym(sptr.Aux) + sptr_0 := sptr.Args[0] + if sptr_0.Op != OpSB { + break + } + if !(symIsRO(scon)) { + break + } + v.reset(OpConst16) + v.Type = typ.Int16 + v.AuxInt = int16ToAuxInt(int16(read16(scon, 0, config.ctxt.Arch.ByteOrder))) + return true + } + // match: (Load sptr:(Addr {scon} (SB)) mem) + // cond: symIsRO(scon) + // result: (Const32 [int32(read32(scon,0,config.ctxt.Arch.ByteOrder))]) + for { + if v.Type != typ.Int32 { + break + } + sptr := v_0 + if sptr.Op != OpAddr { + break + } + scon := auxToSym(sptr.Aux) + sptr_0 := sptr.Args[0] + if sptr_0.Op != OpSB { + break + } + if !(symIsRO(scon)) { + break + } + v.reset(OpConst32) + v.Type = typ.Int32 + v.AuxInt = int32ToAuxInt(int32(read32(scon, 0, config.ctxt.Arch.ByteOrder))) + return true + } + // match: (Load sptr:(Addr {scon} (SB)) mem) + // cond: symIsRO(scon) + // result: (Const64 [int64(read64(scon,0,config.ctxt.Arch.ByteOrder))]) + for { + if v.Type != typ.Int64 { + break + } + sptr := v_0 + if sptr.Op != OpAddr { + break + } + scon := auxToSym(sptr.Aux) + sptr_0 := sptr.Args[0] + if sptr_0.Op != OpSB { + break + } + if !(symIsRO(scon)) { + break + } + v.reset(OpConst64) + v.Type = typ.Int64 + v.AuxInt = int64ToAuxInt(int64(read64(scon, 0, config.ctxt.Arch.ByteOrder))) + return true + } // match: (Load (Addr {s} sb) _) // cond: isFixedLoad(v, s, 0) // result: rewriteFixedLoad(v, s, sb, 0) @@ -14767,6 +14867,124 @@ func rewriteValuegeneric_OpLsh8x8(v *Value) bool { } return false } +func rewriteValuegeneric_OpMemEq(v *Value) bool { + v_3 := v.Args[3] + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + config := b.Func.Config + typ := &b.Func.Config.Types + // match: (MemEq sptr tptr (Const64 [1]) mem) + // result: (Eq8 (Load sptr mem) (Load tptr mem)) + for { + sptr := v_0 + tptr := v_1 + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 1 { + break + } + mem := v_3 + v.reset(OpEq8) + v0 := b.NewValue0(v.Pos, OpLoad, typ.Int8) + v0.AddArg2(sptr, mem) + v1 := b.NewValue0(v.Pos, OpLoad, typ.Int8) + v1.AddArg2(tptr, mem) + v.AddArg2(v0, v1) + return true + } + // match: (MemEq sptr tptr (Const64 [2]) mem) + // cond: canLoadUnaligned(config) + // result: (Eq16 (Load sptr mem) (Load tptr mem)) + for { + sptr := v_0 + tptr := v_1 + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 2 { + break + } + mem := v_3 + if !(canLoadUnaligned(config)) { + break + } + v.reset(OpEq16) + v0 := b.NewValue0(v.Pos, OpLoad, typ.Int16) + v0.AddArg2(sptr, mem) + v1 := b.NewValue0(v.Pos, OpLoad, typ.Int16) + v1.AddArg2(tptr, mem) + v.AddArg2(v0, v1) + return true + } + // match: (MemEq sptr tptr (Const64 [4]) mem) + // cond: canLoadUnaligned(config) + // result: (Eq32 (Load sptr mem) (Load tptr mem)) + for { + sptr := v_0 + tptr := v_1 + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 4 { + break + } + mem := v_3 + if !(canLoadUnaligned(config)) { + break + } + v.reset(OpEq32) + v0 := b.NewValue0(v.Pos, OpLoad, typ.Int32) + v0.AddArg2(sptr, mem) + v1 := b.NewValue0(v.Pos, OpLoad, typ.Int32) + v1.AddArg2(tptr, mem) + v.AddArg2(v0, v1) + return true + } + // match: (MemEq sptr tptr (Const64 [8]) mem) + // cond: canLoadUnaligned(config) && config.PtrSize == 8 + // result: (Eq64 (Load sptr mem) (Load tptr mem)) + for { + sptr := v_0 + tptr := v_1 + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 8 { + break + } + mem := v_3 + if !(canLoadUnaligned(config) && config.PtrSize == 8) { + break + } + v.reset(OpEq64) + v0 := b.NewValue0(v.Pos, OpLoad, typ.Int64) + v0.AddArg2(sptr, mem) + v1 := b.NewValue0(v.Pos, OpLoad, typ.Int64) + v1.AddArg2(tptr, mem) + v.AddArg2(v0, v1) + return true + } + // match: (MemEq _ _ (Const64 [0]) _) + // result: (ConstBool [true]) + for { + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 0 { + break + } + v.reset(OpConstBool) + v.Type = typ.Bool + v.AuxInt = boolToAuxInt(true) + return true + } + // match: (MemEq p q _ _) + // cond: isSamePtr(p, q) + // result: (ConstBool [true]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + p := v_0 + q := v_1 + if !(isSamePtr(p, q)) { + continue + } + v.reset(OpConstBool) + v.Type = typ.Bool + v.AuxInt = boolToAuxInt(true) + return true + } + break + } + return false +} func rewriteValuegeneric_OpMod16(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -29939,7 +30157,7 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool { return true } // match: (SelectN [1] (StaticCall {sym} _ newLen:(Const64) _ _ _ _)) - // cond: v.Type.IsInteger() && isSameCall(sym, "runtime.growslice") + // cond: v.Type.IsInteger() && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) // result: newLen for { if auxIntToInt64(v.AuxInt) != 1 || v_0.Op != OpStaticCall || len(v_0.Args) != 6 { @@ -29948,14 +30166,14 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool { sym := auxToCall(v_0.Aux) _ = v_0.Args[1] newLen := v_0.Args[1] - if newLen.Op != OpConst64 || !(v.Type.IsInteger() && isSameCall(sym, "runtime.growslice")) { + if newLen.Op != OpConst64 || !(v.Type.IsInteger() && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias"))) { break } v.copyOf(newLen) return true } // match: (SelectN [1] (StaticCall {sym} _ newLen:(Const32) _ _ _ _)) - // cond: v.Type.IsInteger() && isSameCall(sym, "runtime.growslice") + // cond: v.Type.IsInteger() && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) // result: newLen for { if auxIntToInt64(v.AuxInt) != 1 || v_0.Op != OpStaticCall || len(v_0.Args) != 6 { @@ -29964,7 +30182,7 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool { sym := auxToCall(v_0.Aux) _ = v_0.Args[1] newLen := v_0.Args[1] - if newLen.Op != OpConst32 || !(v.Type.IsInteger() && isSameCall(sym, "runtime.growslice")) { + if newLen.Op != OpConst32 || !(v.Type.IsInteger() && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias"))) { break } v.copyOf(newLen) @@ -30376,7 +30594,7 @@ func rewriteValuegeneric_OpSliceLen(v *Value) bool { return true } // match: (SliceLen (SelectN [0] (StaticLECall {sym} _ newLen:(Const64) _ _ _ _))) - // cond: isSameCall(sym, "runtime.growslice") + // cond: (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) // result: newLen for { if v_0.Op != OpSelectN || auxIntToInt64(v_0.AuxInt) != 0 { @@ -30389,14 +30607,14 @@ func rewriteValuegeneric_OpSliceLen(v *Value) bool { sym := auxToCall(v_0_0.Aux) _ = v_0_0.Args[1] newLen := v_0_0.Args[1] - if newLen.Op != OpConst64 || !(isSameCall(sym, "runtime.growslice")) { + if newLen.Op != OpConst64 || !(isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) { break } v.copyOf(newLen) return true } // match: (SliceLen (SelectN [0] (StaticLECall {sym} _ newLen:(Const32) _ _ _ _))) - // cond: isSameCall(sym, "runtime.growslice") + // cond: (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) // result: newLen for { if v_0.Op != OpSelectN || auxIntToInt64(v_0.AuxInt) != 0 { @@ -30409,7 +30627,7 @@ func rewriteValuegeneric_OpSliceLen(v *Value) bool { sym := auxToCall(v_0_0.Aux) _ = v_0_0.Args[1] newLen := v_0_0.Args[1] - if newLen.Op != OpConst32 || !(isSameCall(sym, "runtime.growslice")) { + if newLen.Op != OpConst32 || !(isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) { break } v.copyOf(newLen) diff --git a/src/cmd/compile/internal/ssagen/intrinsics.go b/src/cmd/compile/internal/ssagen/intrinsics.go index e346b00a1b3..17beb7b8488 100644 --- a/src/cmd/compile/internal/ssagen/intrinsics.go +++ b/src/cmd/compile/internal/ssagen/intrinsics.go @@ -196,6 +196,12 @@ func initIntrinsics(cfg *intrinsicBuildConfig) { }, sys.AMD64, sys.I386, sys.ARM64, sys.ARM, sys.Loong64, sys.S390X) + addF("runtime", "memequal", + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue4(ssa.OpMemEq, s.f.Config.Types.Bool, args[0], args[1], args[2], s.mem()) + }, + sys.ARM64) + if cfg.goppc64 >= 10 { // Use only on Power10 as the new byte reverse instructions that Power10 provide // make it worthwhile as an intrinsic diff --git a/src/cmd/compile/internal/ssagen/intrinsics_test.go b/src/cmd/compile/internal/ssagen/intrinsics_test.go index 0c483d49c33..91b975c913f 100644 --- a/src/cmd/compile/internal/ssagen/intrinsics_test.go +++ b/src/cmd/compile/internal/ssagen/intrinsics_test.go @@ -327,6 +327,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"arm64", "math/bits", "TrailingZeros64"}: struct{}{}, {"arm64", "math/bits", "TrailingZeros8"}: struct{}{}, {"arm64", "runtime", "KeepAlive"}: struct{}{}, + {"arm64", "runtime", "memequal"}: struct{}{}, {"arm64", "runtime", "publicationBarrier"}: struct{}{}, {"arm64", "runtime", "slicebytetostringtmp"}: struct{}{}, {"arm64", "sync", "runtime_LoadAcquintptr"}: struct{}{}, diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 6dc150f5b2c..33fcf979c59 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -12,6 +12,7 @@ import ( "go/constant" "html" "internal/buildcfg" + "internal/goexperiment" "internal/runtime/gc" "os" "path/filepath" @@ -125,6 +126,8 @@ func InitConfig() { ir.Syms.Goschedguarded = typecheck.LookupRuntimeFunc("goschedguarded") ir.Syms.Growslice = typecheck.LookupRuntimeFunc("growslice") ir.Syms.GrowsliceBuf = typecheck.LookupRuntimeFunc("growsliceBuf") + ir.Syms.GrowsliceBufNoAlias = typecheck.LookupRuntimeFunc("growsliceBufNoAlias") + ir.Syms.GrowsliceNoAlias = typecheck.LookupRuntimeFunc("growsliceNoAlias") ir.Syms.MoveSlice = typecheck.LookupRuntimeFunc("moveSlice") ir.Syms.MoveSliceNoScan = typecheck.LookupRuntimeFunc("moveSliceNoScan") ir.Syms.MoveSliceNoCap = typecheck.LookupRuntimeFunc("moveSliceNoCap") @@ -141,6 +144,7 @@ func InitConfig() { } ir.Syms.MallocGC = typecheck.LookupRuntimeFunc("mallocgc") ir.Syms.Memmove = typecheck.LookupRuntimeFunc("memmove") + ir.Syms.Memequal = typecheck.LookupRuntimeFunc("memequal") ir.Syms.Msanread = typecheck.LookupRuntimeFunc("msanread") ir.Syms.Msanwrite = typecheck.LookupRuntimeFunc("msanwrite") ir.Syms.Msanmove = typecheck.LookupRuntimeFunc("msanmove") @@ -4047,9 +4051,25 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value { s.defvars[s.f.Entry.ID][memVar] = mem info.usedStatic = true } - r = s.rtcall(ir.Syms.GrowsliceBuf, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr, s.addr(info.store), s.constInt(types.Types[types.TINT], info.K)) + fn := ir.Syms.GrowsliceBuf + if goexperiment.RuntimeFreegc && n.AppendNoAlias && !et.HasPointers() { + // The append is for a non-aliased slice where the runtime knows how to free + // the old logically dead backing store after growth. + // TODO(thepudds): for now, we only use the NoAlias version for element types + // without pointers while waiting on additional runtime support (CL 698515). + fn = ir.Syms.GrowsliceBufNoAlias + } + r = s.rtcall(fn, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr, s.addr(info.store), s.constInt(types.Types[types.TINT], info.K)) } else { - r = s.rtcall(ir.Syms.Growslice, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr) + fn := ir.Syms.Growslice + if goexperiment.RuntimeFreegc && n.AppendNoAlias && !et.HasPointers() { + // The append is for a non-aliased slice where the runtime knows how to free + // the old logically dead backing store after growth. + // TODO(thepudds): for now, we only use the NoAlias version for element types + // without pointers while waiting on additional runtime support (CL 698515). + fn = ir.Syms.GrowsliceNoAlias + } + r = s.rtcall(fn, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr) } // Decompose output slice diff --git a/src/cmd/compile/internal/test/eq_test.go b/src/cmd/compile/internal/test/eq_test.go new file mode 100644 index 00000000000..336ec01e08e --- /dev/null +++ b/src/cmd/compile/internal/test/eq_test.go @@ -0,0 +1,298 @@ +// Copyright 2025 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. + +// Tests of generated equality functions. + +package test + +import ( + "reflect" + "testing" + "unsafe" +) + +//go:noinline +func checkEq(t *testing.T, x, y any) { + // Make sure we don't inline the equality test. + if x != y { + t.Errorf("%#v != %#v, wanted equal", x, y) + } +} + +//go:noinline +func checkNe(t *testing.T, x, y any) { + // Make sure we don't inline the equality test. + if x == y { + t.Errorf("%#v == %#v, wanted not equal", x, y) + } +} + +//go:noinline +func checkPanic(t *testing.T, x, y any) { + defer func() { + if recover() == nil { + t.Errorf("%#v == %#v didn't panic", x, y) + } + }() + _ = x == y +} + +type fooComparable struct { + x int +} + +func (f fooComparable) foo() { +} + +type fooIncomparable struct { + b func() +} + +func (i fooIncomparable) foo() { +} + +type eqResult int + +const ( + eq eqResult = iota + ne + panic_ +) + +func (x eqResult) String() string { + return []string{eq: "eq", ne: "ne", panic_: "panic"}[x] +} + +// testEq returns eq if x==y, ne if x!=y, or panic_ if the comparison panics. +func testEq(x, y any) (r eqResult) { + defer func() { + if e := recover(); e != nil { + r = panic_ + } + }() + r = ne + if x == y { + r = eq + } + return +} + +// testCompare make two instances of struct type typ, then +// assigns its len(vals) fields one value from each slice in vals. +// Then it checks the results against a "manual" comparison field +// by field. +func testCompare(t *testing.T, typ reflect.Type, vals [][]any) { + if len(vals) != typ.NumField() { + t.Fatalf("bad test, have %d fields in the list, but %d fields in the type", len(vals), typ.NumField()) + } + + x := reflect.New(typ).Elem() + y := reflect.New(typ).Elem() + ps := powerSet(vals) // all possible settings of fields of the test type. + for _, xf := range ps { // Pick fields for x + for _, yf := range ps { // Pick fields for y + // Make x and y from their chosen fields. + for i, f := range xf { + x.Field(i).Set(reflect.ValueOf(f)) + } + for i, f := range yf { + y.Field(i).Set(reflect.ValueOf(f)) + } + // Compute what we want the result to be. + want := eq + for i := range len(vals) { + if c := testEq(xf[i], yf[i]); c != eq { + want = c + break + } + } + // Compute actual result using generated equality function. + got := testEq(x.Interface(), y.Interface()) + if got != want { + t.Errorf("%#v == %#v, got %s want %s\n", x, y, got, want) + } + } + } +} + +// powerset returns all possible sequences of choosing one +// element from each entry in s. +// For instance, if s = {{1,2}, {a,b}}, then +// it returns {{1,a},{1,b},{2,a},{2,b}}. +func powerSet(s [][]any) [][]any { + if len(s) == 0 { + return [][]any{{}} + } + p := powerSet(s[:len(s)-1]) // powerset from first len(s)-1 entries + var r [][]any + for _, head := range p { + // add one more entry. + for _, v := range s[len(s)-1] { + x := make([]any, 0, len(s)) + x = append(x, head...) + x = append(x, v) + r = append(r, x) + } + } + return r +} + +func TestCompareKinds1(t *testing.T) { + type S struct { + X0 int8 + X1 int16 + X2 int32 + X3 int64 + X4 float32 + X5 float64 + } + testCompare(t, reflect.TypeOf(S{}), [][]any{ + {int8(0), int8(1)}, + {int16(0), int16(1), int16(1 << 14)}, + {int32(0), int32(1), int32(1 << 30)}, + {int64(0), int64(1), int64(1 << 62)}, + {float32(0), float32(1.0)}, + {0.0, 1.0}, + }) +} +func TestCompareKinds2(t *testing.T) { + type S struct { + X0 uint8 + X1 uint16 + X2 uint32 + X3 uint64 + X4 uintptr + X5 bool + } + testCompare(t, reflect.TypeOf(S{}), [][]any{ + {uint8(0), uint8(1)}, + {uint16(0), uint16(1), uint16(1 << 15)}, + {uint32(0), uint32(1), uint32(1 << 31)}, + {uint64(0), uint64(1), uint64(1 << 63)}, + {uintptr(0), uintptr(1)}, + {false, true}, + }) +} +func TestCompareKinds3(t *testing.T) { + type S struct { + X0 complex64 + X1 complex128 + X2 *byte + X3 chan int + X4 unsafe.Pointer + } + testCompare(t, reflect.TypeOf(S{}), [][]any{ + {complex64(1 + 1i), complex64(1 + 2i), complex64(2 + 1i)}, + {complex128(1 + 1i), complex128(1 + 2i), complex128(2 + 1i)}, + {new(byte), new(byte)}, + {make(chan int), make(chan int)}, + {unsafe.Pointer(new(byte)), unsafe.Pointer(new(byte))}, + }) +} + +func TestCompareOrdering(t *testing.T) { + type S struct { + A string + E any + B string + } + + testCompare(t, reflect.TypeOf(S{}), [][]any{ + {"a", "b", "cc"}, + {3, []byte{0}, []byte{1}}, + {"a", "b", "cc"}, + }) +} +func TestCompareInterfaces(t *testing.T) { + type S struct { + A any + B fooer + } + testCompare(t, reflect.TypeOf(S{}), [][]any{ + {3, []byte{0}}, + {fooComparable{x: 3}, fooIncomparable{b: nil}}, + }) +} + +func TestCompareSkip(t *testing.T) { + type S struct { + A int8 + B int16 + } + type S2 struct { + A int8 + padding int8 + B int16 + } + x := S{A: 1, B: 3} + y := S{A: 1, B: 3} + (*S2)(unsafe.Pointer(&x)).padding = 88 + (*S2)(unsafe.Pointer(&y)).padding = 99 + + want := eq + if got := testEq(x, y); got != want { + t.Errorf("%#v == %#v, got %s want %s", x, y, got, want) + } +} + +func TestCompareMemequal(t *testing.T) { + type S struct { + s1 string + d [100]byte + s2 string + } + var x, y S + + checkEq(t, x, y) + y.d[0] = 1 + checkNe(t, x, y) + y.d[0] = 0 + y.d[99] = 1 + checkNe(t, x, y) +} + +func TestComparePanic(t *testing.T) { + type S struct { + X0 string + X1 any + X2 string + X3 fooer + X4 string + } + testCompare(t, reflect.TypeOf(S{}), [][]any{ + {"a", "b", "cc"}, // length equal, as well as length unequal + {3, []byte{1}}, // comparable and incomparable + {"a", "b", "cc"}, // length equal, as well as length unequal + {fooComparable{x: 3}, fooIncomparable{b: nil}}, // comparable and incomparable + {"a", "b", "cc"}, // length equal, as well as length unequal + }) +} + +func TestCompareArray(t *testing.T) { + type S struct { + X0 string + X1 [100]string + X2 string + } + x := S{X0: "a", X2: "b"} + y := x + checkEq(t, x, y) + x.X0 = "c" + checkNe(t, x, y) + x.X0 = "a" + x.X2 = "c" + checkNe(t, x, y) + x.X2 = "b" + checkEq(t, x, y) + + for i := 0; i < 100; i++ { + x.X1[i] = "d" + checkNe(t, x, y) + y.X1[i] = "e" + checkNe(t, x, y) + x.X1[i] = "" + y.X1[i] = "" + checkEq(t, x, y) + } +} diff --git a/src/cmd/compile/internal/test/free_test.go b/src/cmd/compile/internal/test/free_test.go new file mode 100644 index 00000000000..061cc9a6e4e --- /dev/null +++ b/src/cmd/compile/internal/test/free_test.go @@ -0,0 +1,55 @@ +// Copyright 2025 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 test + +import ( + "internal/asan" + "internal/goexperiment" + "internal/msan" + "internal/race" + "testing" +) + +func TestFreeAppendAllocations(t *testing.T) { + t.Run("slice-no-alias", func(t *testing.T) { + if !goexperiment.RuntimeFreegc { + t.Skip("skipping allocation test when runtime.freegc is disabled") + } + if race.Enabled || msan.Enabled || asan.Enabled { + // TODO(thepudds): we get 8 allocs for slice-no-alias instead of 1 with -race. This + // might be expected given some allocation optimizations are already disabled + // under race, but if not, we might need to update walk. + t.Skip("skipping allocation test under race detector and other sanitizers") + } + + allocs := testing.AllocsPerRun(100, func() { + var s []int64 + for i := range 100 { + s = append(s, int64(i)) + } + _ = s + }) + t.Logf("allocs: %v", allocs) + if allocs != 1 { + t.Errorf("allocs: %v, want 1", allocs) + } + }) + + t.Run("slice-aliased", func(t *testing.T) { + allocs := testing.AllocsPerRun(100, func() { + var s []int64 + var alias []int64 + for i := range 100 { + s = append(s, int64(i)) + alias = s + } + _ = alias + }) + t.Logf("allocs: %v", allocs) + if allocs < 2 { + t.Errorf("allocs: %v, want >= 2", allocs) + } + }) +} diff --git a/src/cmd/compile/internal/typecheck/_builtin/runtime.go b/src/cmd/compile/internal/typecheck/_builtin/runtime.go index d43a9e5bf2d..a7603c3e33c 100644 --- a/src/cmd/compile/internal/typecheck/_builtin/runtime.go +++ b/src/cmd/compile/internal/typecheck/_builtin/runtime.go @@ -57,6 +57,7 @@ func printuint(uint64) func printcomplex128(complex128) func printcomplex64(complex64) func printstring(string) +func printquoted(string) func printpointer(any) func printuintptr(uintptr) func printiface(any) @@ -196,6 +197,8 @@ func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer func makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer func growslice(oldPtr *any, newLen, oldCap, num int, et *byte) (ary []any) func growsliceBuf(oldPtr *any, newLen, oldCap, num int, et *byte, buf *any, bufLen int) (ary []any) +func growsliceBufNoAlias(oldPtr *any, newLen, oldCap, num int, et *byte, buf *any, bufLen int) (ary []any) +func growsliceNoAlias(oldPtr *any, newLen, oldCap, num int, et *byte) (ary []any) func unsafeslicecheckptr(typ *byte, ptr unsafe.Pointer, len int64) func panicunsafeslicelen() func panicunsafeslicenilptr() diff --git a/src/cmd/compile/internal/typecheck/builtin.go b/src/cmd/compile/internal/typecheck/builtin.go index dd9f1593f38..955e65e5988 100644 --- a/src/cmd/compile/internal/typecheck/builtin.go +++ b/src/cmd/compile/internal/typecheck/builtin.go @@ -64,6 +64,7 @@ var runtimeDecls = [...]struct { {"printcomplex128", funcTag, 27}, {"printcomplex64", funcTag, 29}, {"printstring", funcTag, 31}, + {"printquoted", funcTag, 31}, {"printpointer", funcTag, 32}, {"printuintptr", funcTag, 33}, {"printiface", funcTag, 32}, @@ -161,6 +162,8 @@ var runtimeDecls = [...]struct { {"makeslicecopy", funcTag, 125}, {"growslice", funcTag, 127}, {"growsliceBuf", funcTag, 128}, + {"growsliceBufNoAlias", funcTag, 128}, + {"growsliceNoAlias", funcTag, 127}, {"unsafeslicecheckptr", funcTag, 129}, {"panicunsafeslicelen", funcTag, 9}, {"panicunsafeslicenilptr", funcTag, 9}, diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 52dc33b8cd5..3461c890a8b 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -669,7 +669,7 @@ var cgoPrefixes = [...]string{ "_Cmacro_", // function to evaluate the expanded expression } -func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName, wantType bool) { +func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, wantType bool) { // these must be declared before the "goto Error" statements var ( obj Object @@ -715,7 +715,7 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName } goto Error } - check.objDecl(exp, nil) + check.objDecl(exp) } else { exp = pkg.scope.Lookup(sel) if exp == nil { @@ -777,12 +777,6 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName check.exprOrType(x, e.X, false) switch x.mode { - case typexpr: - // don't crash for "type T T.x" (was go.dev/issue/51509) - if def != nil && def.typ == x.typ { - check.cycleError([]Object{def}, 0) - goto Error - } case builtin: check.errorf(e.Pos(), UncalledBuiltin, "invalid use of %s in selector expression", x) goto Error @@ -844,7 +838,7 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName // methods may not have a fully set up signature yet if m, _ := obj.(*Func); m != nil { - check.objDecl(m, nil) + check.objDecl(m) } if x.mode == typexpr { diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index 4f9f7c25e74..25152545ece 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -45,8 +45,7 @@ func pathString(path []Object) string { } // objDecl type-checks the declaration of obj in its respective (file) environment. -// For the meaning of def, see Checker.definedType, in typexpr.go. -func (check *Checker) objDecl(obj Object, def *TypeName) { +func (check *Checker) objDecl(obj Object) { if tracePos { check.pushPos(obj.Pos()) defer func() { @@ -156,7 +155,7 @@ func (check *Checker) objDecl(obj Object, def *TypeName) { check.varDecl(obj, d.lhs, d.vtyp, d.init) case *TypeName: // invalid recursive types are detected via path - check.typeDecl(obj, d.tdecl, def) + check.typeDecl(obj, d.tdecl) check.collectMethods(obj) // methods can only be added to top-level types case *Func: // functions may be recursive - no need to track dependencies @@ -440,7 +439,7 @@ func (check *Checker) isImportedConstraint(typ Type) bool { return u != nil && !u.IsMethodSet() } -func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeName) { +func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl) { assert(obj.typ == nil) // Only report a version error if we have not reported one already. @@ -474,7 +473,6 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN if check.conf.EnableAlias { alias := check.newAlias(obj, nil) - setDefType(def, alias) // If we could not type the RHS, set it to invalid. This should // only ever happen if we panic before setting. @@ -521,7 +519,6 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN } named := check.newNamed(obj, nil, nil) - setDefType(def, named) // The RHS of a named N can be nil if, for example, N is defined as a cycle of aliases with // gotypesalias=0. Consider: @@ -878,7 +875,7 @@ func (check *Checker) declStmt(list []syntax.Decl) { scopePos := s.Name.Pos() check.declare(check.scope, s.Name, obj, scopePos) check.push(obj) // mark as grey - check.typeDecl(obj, s, nil) + check.typeDecl(obj, s) check.pop() default: diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index 39bf4055a37..637cbaee5d3 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -993,6 +993,13 @@ func (check *Checker) rawExpr(T *target, x *operand, e syntax.Expr, hint Type, a check.nonGeneric(T, x) } + // Here, x is a value, meaning it has a type. If that type is pending, then we have + // a cycle. As an example: + // + // type T [unsafe.Sizeof(T{})]int + // + // has a cycle T->T which is deemed valid (by decl.go), but which is in fact invalid. + check.pendingType(x) check.record(x) return kind @@ -1027,6 +1034,22 @@ func (check *Checker) nonGeneric(T *target, x *operand) { } } +// If x has a pending type (i.e. its declaring object is on the object path), pendingType +// reports an error and invalidates x.mode and x.typ. +// Otherwise it leaves x alone. +func (check *Checker) pendingType(x *operand) { + if x.mode == invalid || x.mode == novalue { + return + } + if n, ok := Unalias(x.typ).(*Named); ok { + if i, ok := check.objPathIdx[n.obj]; ok { + check.cycleError(check.objPath, i) + x.mode = invalid + x.typ = Typ[Invalid] + } + } +} + // exprInternal contains the core of type checking of expressions. // Must only be called by rawExpr. // (See rawExpr for an explanation of the parameters.) @@ -1044,7 +1067,7 @@ func (check *Checker) exprInternal(T *target, x *operand, e syntax.Expr, hint Ty goto Error // error was reported before case *syntax.Name: - check.ident(x, e, nil, false) + check.ident(x, e, false) case *syntax.DotsType: // dots are handled explicitly where they are valid @@ -1079,7 +1102,7 @@ func (check *Checker) exprInternal(T *target, x *operand, e syntax.Expr, hint Ty return kind case *syntax.SelectorExpr: - check.selector(x, e, nil, false) + check.selector(x, e, false) case *syntax.IndexExpr: if check.indexExpr(x, e) { diff --git a/src/cmd/compile/internal/types2/format.go b/src/cmd/compile/internal/types2/format.go index b61dfda1c81..d7aa2399c7b 100644 --- a/src/cmd/compile/internal/types2/format.go +++ b/src/cmd/compile/internal/types2/format.go @@ -23,6 +23,17 @@ func sprintf(qf Qualifier, tpSubscripts bool, format string, args ...any) string panic("got operand instead of *operand") case *operand: arg = operandString(a, qf) + case []*operand: + var buf strings.Builder + buf.WriteByte('[') + for i, x := range a { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(operandString(x, qf)) + } + buf.WriteByte(']') + arg = buf.String() case syntax.Pos: arg = a.String() case syntax.Expr: diff --git a/src/cmd/compile/internal/types2/literals.go b/src/cmd/compile/internal/types2/literals.go index 5b2dae9b13a..ed1c3f695c8 100644 --- a/src/cmd/compile/internal/types2/literals.go +++ b/src/cmd/compile/internal/types2/literals.go @@ -145,13 +145,6 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type switch u, _ := commonUnder(base, nil); utyp := u.(type) { case *Struct: - // Prevent crash if the struct referred to is not yet set up. - // See analogous comment for *Array. - if utyp.fields == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } if len(e.ElemList) == 0 { break } @@ -225,14 +218,6 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type } case *Array: - // Prevent crash if the array referred to is not yet set up. Was go.dev/issue/18643. - // This is a stop-gap solution. Should use Checker.objPath to report entire - // path starting with earliest declaration in the source. TODO(gri) fix this. - if utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } n := check.indexedElts(e.ElemList, utyp.elem, utyp.len) // If we have an array of unknown length (usually [...]T arrays, but also // arrays [n]T where n is invalid) set the length now that we know it and @@ -254,23 +239,9 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type } case *Slice: - // Prevent crash if the slice referred to is not yet set up. - // See analogous comment for *Array. - if utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } check.indexedElts(e.ElemList, utyp.elem, -1) case *Map: - // Prevent crash if the map referred to is not yet set up. - // See analogous comment for *Array. - if utyp.key == nil || utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } // If the map key type is an interface (but not a type parameter), // the type of a constant key must be considered when checking for // duplicates. diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go index 3e18db09f5c..81a126b9c7d 100644 --- a/src/cmd/compile/internal/types2/lookup.go +++ b/src/cmd/compile/internal/types2/lookup.go @@ -447,7 +447,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y // methods may not have a fully set up signature yet if check != nil { - check.objDecl(f, nil) + check.objDecl(f) } if !equivalent(f.typ, m.typ) { @@ -466,7 +466,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y // This method may be formatted in funcString below, so must have a fully // set up signature. if check != nil { - check.objDecl(f, nil) + check.objDecl(f) } } switch state { diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index b5c8ed142aa..edd6357248e 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -456,7 +456,7 @@ func (t *Named) expandMethod(i int) *Func { check := t.check // Ensure that the original method is type-checked. if check != nil { - check.objDecl(origm, nil) + check.objDecl(origm) } origSig := origm.typ.(*Signature) diff --git a/src/cmd/compile/internal/types2/resolver.go b/src/cmd/compile/internal/types2/resolver.go index 4c9eeb329c8..2418de52686 100644 --- a/src/cmd/compile/internal/types2/resolver.go +++ b/src/cmd/compile/internal/types2/resolver.go @@ -664,7 +664,7 @@ func (check *Checker) packageObjects() { // // Investigate and reenable this branch. for _, obj := range check.objList { - check.objDecl(obj, nil) + check.objDecl(obj) } } else { // Without Alias nodes, we process non-alias type declarations first, followed by @@ -680,7 +680,7 @@ func (check *Checker) packageObjects() { if check.objMap[tname].tdecl.Alias { aliasList = append(aliasList, tname) } else { - check.objDecl(obj, nil) + check.objDecl(obj) } } else { othersList = append(othersList, obj) @@ -688,11 +688,11 @@ func (check *Checker) packageObjects() { } // phase 2: alias type declarations for _, obj := range aliasList { - check.objDecl(obj, nil) + check.objDecl(obj) } // phase 3: all other declarations for _, obj := range othersList { - check.objDecl(obj, nil) + check.objDecl(obj) } } diff --git a/src/cmd/compile/internal/types2/signature.go b/src/cmd/compile/internal/types2/signature.go index ea60254fa68..d569ba80139 100644 --- a/src/cmd/compile/internal/types2/signature.go +++ b/src/cmd/compile/internal/types2/signature.go @@ -28,17 +28,28 @@ type Signature struct { recv *Var // nil if not a method params *Tuple // (incoming) parameters from left to right; or nil results *Tuple // (outgoing) results from left to right; or nil - variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only) + variadic bool // true if the last parameter's type is of the form ...T + + // If variadic, the last element of params ordinarily has an + // unnamed Slice type. As a special case, in a call to append, + // it may be string, or a TypeParam T whose typeset ⊇ {string, []byte}. + // It may even be a named []byte type if a client instantiates + // T at such a type. } // NewSignatureType creates a new function type for the given receiver, // receiver type parameters, type parameters, parameters, and results. +// // If variadic is set, params must hold at least one parameter and the // last parameter must be an unnamed slice or a type parameter whose // type set has an unnamed slice as common underlying type. -// As a special case, for variadic signatures the last parameter may -// also be a string type, or a type parameter containing a mix of byte -// slices and string types in its type set. +// +// As a special case, to support append([]byte, str...), for variadic +// signatures the last parameter may also be a string type, or a type +// parameter containing a mix of byte slices and string types in its +// type set. It may even be a named []byte slice type resulting from +// substitution of such a type parameter. +// // If recv is non-nil, typeParams must be empty. If recvTypeParams is // non-empty, recv must be non-nil. func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params, results *Tuple, variadic bool) *Signature { @@ -54,17 +65,29 @@ func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params if isString(t) { s = NewSlice(universeByte) } else { - s, _ = Unalias(t).(*Slice) // don't accept a named slice type + // Variadic Go functions have a last parameter of type []T, + // suggesting we should reject a named slice type B here. + // + // However, a call to built-in append(slice, x...) + // where x has a TypeParam type [T ~string | ~[]byte], + // has the type func([]byte, T). Since a client may + // instantiate this type at T=B, we must permit + // named slice types, even when this results in a + // signature func([]byte, B) where type B []byte. + // + // (The caller of NewSignatureType may have no way to + // know that it is dealing with the append special case.) + s, _ = t.Underlying().(*Slice) } if S == nil { S = s - } else if !Identical(S, s) { + } else if s == nil || !Identical(S, s) { S = nil break } } if S == nil { - panic(fmt.Sprintf("got %s, want variadic parameter of unnamed slice or string type", last)) + panic(fmt.Sprintf("got %s, want variadic parameter of slice or string type", last)) } } sig := &Signature{recv: recv, params: params, results: results, variadic: variadic} @@ -98,6 +121,7 @@ func (s *Signature) TypeParams() *TypeParamList { return s.tparams } func (s *Signature) RecvTypeParams() *TypeParamList { return s.rparams } // Params returns the parameters of signature s, or nil. +// See [NewSignatureType] for details of variadic functions. func (s *Signature) Params() *Tuple { return s.params } // Results returns the results of signature s, or nil. diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go index b1f0d0929ba..e68f6a28865 100644 --- a/src/cmd/compile/internal/types2/typestring.go +++ b/src/cmd/compile/internal/types2/typestring.go @@ -449,22 +449,25 @@ func (w *typeWriter) tuple(tup *Tuple, variadic bool) { } typ := v.typ if variadic && i == len(tup.vars)-1 { - if s, ok := typ.(*Slice); ok { + if slice, ok := typ.(*Slice); ok { w.string("...") - typ = s.elem + w.typ(slice.elem) } else { - // special case: - // append(s, "foo"...) leads to signature func([]byte, string...) - if t, _ := typ.Underlying().(*Basic); t == nil || t.kind != String { - w.error("expected string type") - continue - } + // append(slice, str...) entails various special + // cases, especially in conjunction with generics. + // str may be: + // - a string, + // - a TypeParam whose typeset includes string, or + // - a named []byte slice type B resulting from + // a client instantiating append([]byte, T) at T=B. + // For such cases we use the irregular notation + // func([]byte, T...), with the dots after the type. w.typ(typ) w.string("...") - continue } + } else { + w.typ(typ) } - w.typ(typ) } } w.byte(')') diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index 303f782ac49..a79b54eaccf 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -16,9 +16,8 @@ import ( // ident type-checks identifier e and initializes x with the value or type of e. // If an error occurred, x.mode is set to invalid. -// For the meaning of def, see Checker.declaredType, below. // If wantType is set, the identifier e is expected to denote a type. -func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType bool) { +func (check *Checker) ident(x *operand, e *syntax.Name, wantType bool) { x.mode = invalid x.expr = e @@ -73,7 +72,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType // packages, to avoid races: see issue #69912. typ := obj.Type() if typ == nil || (gotType && wantType && obj.Pkg() == check.pkg) { - check.objDecl(obj, def) + check.objDecl(obj) typ = obj.Type() // type must have been assigned by Checker.objDecl } assert(typ != nil) @@ -257,13 +256,11 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.Name: var x operand - check.ident(&x, e, def, true) + check.ident(&x, e, true) switch x.mode { case typexpr: - typ := x.typ - setDefType(def, typ) - return typ + return x.typ case invalid: // ignore - error reported before case novalue: @@ -274,13 +271,11 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.SelectorExpr: var x operand - check.selector(&x, e, def, true) + check.selector(&x, e, true) switch x.mode { case typexpr: - typ := x.typ - setDefType(def, typ) - return typ + return x.typ case invalid: // ignore - error reported before case novalue: @@ -291,7 +286,7 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.IndexExpr: check.verifyVersionf(e, go1_18, "type instantiation") - return check.instantiatedType(e.X, syntax.UnpackListExpr(e.Index), def) + return check.instantiatedType(e.X, syntax.UnpackListExpr(e.Index)) case *syntax.ParenExpr: // Generic types must be instantiated before they can be used in any form. @@ -300,7 +295,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.ArrayType: typ := new(Array) - setDefType(def, typ) if e.Len != nil { typ.len = check.arrayLength(e.Len) } else { @@ -316,7 +310,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.SliceType: typ := new(Slice) - setDefType(def, typ) typ.elem = check.varType(e.Elem) return typ @@ -326,7 +319,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.StructType: typ := new(Struct) - setDefType(def, typ) check.structType(typ, e) return typ @@ -334,7 +326,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { if e.Op == syntax.Mul && e.Y == nil { typ := new(Pointer) typ.base = Typ[Invalid] // avoid nil base in invalid recursive type declaration - setDefType(def, typ) typ.base = check.varType(e.X) // If typ.base is invalid, it's unlikely that *base is particularly // useful - even a valid dereferenciation will lead to an invalid @@ -351,20 +342,16 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.FuncType: typ := new(Signature) - setDefType(def, typ) check.funcType(typ, nil, nil, e) return typ case *syntax.InterfaceType: typ := check.newInterface() - setDefType(def, typ) check.interfaceType(typ, e, def) return typ case *syntax.MapType: typ := new(Map) - setDefType(def, typ) - typ.key = check.varType(e.Key) typ.elem = check.varType(e.Value) @@ -388,7 +375,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.ChanType: typ := new(Chan) - setDefType(def, typ) dir := SendRecv switch e.Dir { @@ -413,26 +399,10 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { } typ := Typ[Invalid] - setDefType(def, typ) return typ } -func setDefType(def *TypeName, typ Type) { - if def != nil { - switch t := def.typ.(type) { - case *Alias: - t.fromRHS = typ - case *Basic: - assert(t == Typ[Invalid]) - case *Named: - t.fromRHS = typ - default: - panic(fmt.Sprintf("unexpected type %T", t)) - } - } -} - -func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def *TypeName) (res Type) { +func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr) (res Type) { if check.conf.Trace { check.trace(x.Pos(), "-- instantiating type %s with %s", x, xlist) check.indent++ @@ -443,10 +413,6 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * }() } - defer func() { - setDefType(def, res) - }() - var cause string typ := check.genericType(x, &cause) if cause != "" { diff --git a/src/cmd/compile/internal/walk/builtin.go b/src/cmd/compile/internal/walk/builtin.go index 2f2a2c62f16..c698caddce9 100644 --- a/src/cmd/compile/internal/walk/builtin.go +++ b/src/cmd/compile/internal/walk/builtin.go @@ -729,13 +729,18 @@ func walkPrint(nn *ir.CallExpr, init *ir.Nodes) ir.Node { if ir.IsConst(n, constant.String) { cs = ir.StringVal(n) } - switch cs { - case " ": - on = typecheck.LookupRuntime("printsp") - case "\n": - on = typecheck.LookupRuntime("printnl") - default: - on = typecheck.LookupRuntime("printstring") + // Print values of the named type `quoted` using printquoted. + if types.RuntimeSymName(n.Type().Sym()) == "quoted" { + on = typecheck.LookupRuntime("printquoted") + } else { + switch cs { + case " ": + on = typecheck.LookupRuntime("printsp") + case "\n": + on = typecheck.LookupRuntime("printnl") + default: + on = typecheck.LookupRuntime("printstring") + } } default: badtype(ir.OPRINT, n.Type(), nil) diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index 2b382a1c025..e4250e12de8 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -1822,7 +1822,7 @@ var cgoEnabled = map[string]bool{ // get filtered out of cgoEnabled for 'dist list'. // See go.dev/issue/56679. var broken = map[string]bool{ - "freebsd/riscv64": true, // Broken: go.dev/issue/73568. + "freebsd/riscv64": true, // Broken: go.dev/issue/76475. "linux/sparc64": true, // An incomplete port. See CL 132155. "openbsd/mips64": true, // Broken: go.dev/issue/58110. } diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 73ea5c4015a..f8d19ac34c5 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -753,6 +753,15 @@ func (t *tester) registerTests() { }) } + // Test GOEXPERIMENT=runtimesecret. + if !strings.Contains(goexperiment, "runtimesecret") { + t.registerTest("GOEXPERIMENT=runtimesecret go test runtime/secret/...", &goTest{ + variant: "runtimesecret", + env: []string{"GOEXPERIMENT=runtimesecret"}, + pkg: "runtime/secret/...", + }) + } + // Test ios/amd64 for the iOS simulator. if goos == "darwin" && goarch == "amd64" && t.cgoEnabled { t.registerTest("GOOS=ios on darwin/amd64", diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 2f13ca0dc96..9a1a38bccca 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -3,19 +3,19 @@ module cmd go 1.26 require ( - github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 - golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 - golang.org/x/build v0.0.0-20250806225920-b7c66c047964 - golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526 + github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 + golang.org/x/arch v0.23.0 + golang.org/x/build v0.0.0-20251128064159-b9bfd88b30e8 + golang.org/x/mod v0.30.1-0.20251115032019-269c237cf350 golang.org/x/sync v0.18.0 - golang.org/x/sys v0.38.0 - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 - golang.org/x/term v0.34.0 - golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 + golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 + golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec + golang.org/x/term v0.37.0 + golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 ) require ( github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect - golang.org/x/text v0.30.0 // indirect + golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 // indirect rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect ) diff --git a/src/cmd/go.sum b/src/cmd/go.sum index d75d9339fbf..52d55d81732 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -1,28 +1,28 @@ -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 h1:VJ182b/ajNehMFRltVfCh/FR0jAH+QX6hs9zqYod/mU= -golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/build v0.0.0-20250806225920-b7c66c047964 h1:yRs1K51GKq7hsIO+YHJ8LsslrvwFceNPIv0tYjpcBd0= -golang.org/x/build v0.0.0-20250806225920-b7c66c047964/go.mod h1:i9Vx7+aOQUpYJRxSO+OpRStVBCVL/9ccI51xblWm5WY= -golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526 h1:LPpBM4CGUFMC47OqgAr2YIUxEUjH1Ur+D3KR/1LiuuQ= -golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/build v0.0.0-20251128064159-b9bfd88b30e8 h1:Mp+uRtHbKFW85lGBTOkOOfkPBz7AUKmZGcflkavmGRM= +golang.org/x/build v0.0.0-20251128064159-b9bfd88b30e8/go.mod h1:Jx2RBBeTWGRSCwfSZ+w2Hg1f7LjWycsSkx+EciLAmPE= +golang.org/x/mod v0.30.1-0.20251115032019-269c237cf350 h1:JGDMsCp8NahDR9HSvwrF6V8tzEf87m7Bo4oZ07vRxdU= +golang.org/x/mod v0.30.1-0.20251115032019-269c237cf350/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 h1:BGuEUnbWU1H+VhF4Z52lwCvzRT8Q/Z7kJC3okSME58w= -golang.org/x/tools v0.39.1-0.20251120214200-68724afed209/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 h1:s8+qM6u6X24AFOioI7tH2p/6zxCHqt3G7zwUYm7MgUc= +golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec h1:dRVkWZl6bUOp+oxnOe4BuyhWSIPmt29N4ooHarm7Ic8= +golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 h1:IjQf87/qLz2y0SiCc0uY3DwajALXkAgP1Pxal0mmdrM= +golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 h1:i4GzAuZW4RuKXltwKyLYAfk7E1TSKQBxRAI7XKfLjSk= +golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ= diff --git a/src/cmd/go/internal/auth/userauth.go b/src/cmd/go/internal/auth/userauth.go index 2649a9c271d..44c0b3cff09 100644 --- a/src/cmd/go/internal/auth/userauth.go +++ b/src/cmd/go/internal/auth/userauth.go @@ -48,7 +48,7 @@ func runAuthCommand(command string, url string, res *http.Response) (map[string] // parseUserAuth parses the output from a GOAUTH command and // returns a mapping of prefix → http.Header without the leading "https://" // or an error if the data does not follow the expected format. -// Returns an nil error and an empty map if the data is empty. +// Returns a nil error and an empty map if the data is empty. // See the expected format in 'go help goauth'. func parseUserAuth(data string) (map[string]http.Header, error) { credentials := make(map[string]http.Header) diff --git a/src/cmd/go/internal/fips140/fips140.go b/src/cmd/go/internal/fips140/fips140.go index 4194f0ff6aa..09e4141f997 100644 --- a/src/cmd/go/internal/fips140/fips140.go +++ b/src/cmd/go/internal/fips140/fips140.go @@ -85,11 +85,6 @@ package fips140 import ( - "cmd/go/internal/base" - "cmd/go/internal/cfg" - "cmd/go/internal/fsys" - "cmd/go/internal/modfetch" - "cmd/go/internal/str" "context" "os" "path" @@ -97,6 +92,12 @@ import ( "slices" "strings" + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "cmd/go/internal/fsys" + "cmd/go/internal/modfetch" + "cmd/go/internal/str" + "golang.org/x/mod/module" "golang.org/x/mod/semver" ) @@ -224,12 +225,11 @@ func initDir() { mod := module.Version{Path: "golang.org/fips140", Version: v} file := filepath.Join(cfg.GOROOT, "lib/fips140", v+".zip") - zdir, err := modfetch.Unzip(context.Background(), mod, file) + zdir, err := modfetch.NewFetcher().Unzip(context.Background(), mod, file) if err != nil { base.Fatalf("go: unpacking GOFIPS140=%v: %v", v, err) } dir = filepath.Join(zdir, "fips140") - return } // ResolveImport resolves the import path imp. diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 3b8bbdc91b8..e2a77d7d7df 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -7,7 +7,6 @@ package load import ( "bytes" - "cmd/internal/objabi" "context" "encoding/json" "errors" @@ -31,6 +30,8 @@ import ( "unicode" "unicode/utf8" + "cmd/internal/objabi" + "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/fips140" @@ -2049,7 +2050,7 @@ func (p *Package) load(loaderstate *modload.State, ctx context.Context, opts Pac // Consider starting this as a background goroutine and retrieving the result // asynchronously when we're actually ready to build the package, or when we // actually need to evaluate whether the package's metadata is stale. - p.setBuildInfo(ctx, opts.AutoVCS) + p.setBuildInfo(ctx, loaderstate.Fetcher(), opts.AutoVCS) } // If cgo is not enabled, ignore cgo supporting sources @@ -2322,7 +2323,7 @@ func appendBuildSetting(info *debug.BuildInfo, key, value string) { // // Note that the GoVersion field is not set here to avoid encoding it twice. // It is stored separately in the binary, mostly for historical reasons. -func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) { +func (p *Package) setBuildInfo(ctx context.Context, f *modfetch.Fetcher, autoVCS bool) { setPkgErrorf := func(format string, args ...any) { if p.Error == nil { p.Error = &PackageError{Err: fmt.Errorf(format, args...)} @@ -2594,7 +2595,7 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) { if !ok { goto omitVCS } - repo := modfetch.LookupLocal(ctx, codeRoot, p.Module.Path, repoDir) + repo := f.LookupLocal(ctx, codeRoot, p.Module.Path, repoDir) revInfo, err := repo.Stat(ctx, st.Revision) if err != nil { goto omitVCS @@ -3416,7 +3417,7 @@ func PackagesAndErrorsOutsideModule(loaderstate *modload.State, ctx context.Cont if deprecation != "" { fmt.Fprintf(os.Stderr, "go: module %s is deprecated: %s\n", rootMod.Path, modload.ShortMessage(deprecation, "")) } - data, err := modfetch.GoMod(ctx, rootMod.Path, rootMod.Version) + data, err := loaderstate.Fetcher().GoMod(ctx, rootMod.Path, rootMod.Version) if err != nil { return nil, fmt.Errorf("%s: %w", args[0], err) } diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go index c7c58fd5487..e5c074fa193 100644 --- a/src/cmd/go/internal/load/test.go +++ b/src/cmd/go/internal/load/test.go @@ -301,7 +301,7 @@ func TestPackagesAndErrors(loaderstate *modload.State, ctx context.Context, done // pmain won't have buildinfo set (since we copy it from the package under test). If the default GODEBUG // used for the package under test is different from that of the test main, the BuildInfo assigned above from the package // under test incorrect for the test main package. Either set or correct pmain's build info. - pmain.setBuildInfo(ctx, opts.AutoVCS) + pmain.setBuildInfo(ctx, loaderstate.Fetcher(), opts.AutoVCS) } // The generated main also imports testing, regexp, and os. diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go index 150d0c88607..661cddcd79d 100644 --- a/src/cmd/go/internal/modcmd/download.go +++ b/src/cmd/go/internal/modcmd/download.go @@ -264,7 +264,7 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { } sem <- token{} go func() { - err := DownloadModule(ctx, m) + err := DownloadModule(ctx, moduleLoaderState.Fetcher(), m) if err != nil { downloadErrs.Store(m, err) m.Error = err.Error() @@ -364,27 +364,27 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { // DownloadModule runs 'go mod download' for m.Path@m.Version, // leaving the results (including any error) in m itself. -func DownloadModule(ctx context.Context, m *ModuleJSON) error { +func DownloadModule(ctx context.Context, f *modfetch.Fetcher, m *ModuleJSON) error { var err error - _, file, err := modfetch.InfoFile(ctx, m.Path, m.Version) + _, file, err := f.InfoFile(ctx, m.Path, m.Version) if err != nil { return err } m.Info = file - m.GoMod, err = modfetch.GoModFile(ctx, m.Path, m.Version) + m.GoMod, err = f.GoModFile(ctx, m.Path, m.Version) if err != nil { return err } - m.GoModSum, err = modfetch.GoModSum(ctx, m.Path, m.Version) + m.GoModSum, err = f.GoModSum(ctx, m.Path, m.Version) if err != nil { return err } mod := module.Version{Path: m.Path, Version: m.Version} - m.Zip, err = modfetch.DownloadZip(ctx, mod) + m.Zip, err = f.DownloadZip(ctx, mod) if err != nil { return err } m.Sum = modfetch.Sum(ctx, mod) - m.Dir, err = modfetch.Download(ctx, mod) + m.Dir, err = f.Download(ctx, mod) return err } diff --git a/src/cmd/go/internal/modfetch/cache.go b/src/cmd/go/internal/modfetch/cache.go index 30020d24a71..f035c359b13 100644 --- a/src/cmd/go/internal/modfetch/cache.go +++ b/src/cmd/go/internal/modfetch/cache.go @@ -156,7 +156,7 @@ func lockVersion(ctx context.Context, mod module.Version) (unlock func(), err er if err != nil { return nil, err } - if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil { return nil, err } return lockedfile.MutexAt(path).Lock() @@ -172,7 +172,7 @@ func SideLock(ctx context.Context) (unlock func(), err error) { } path := filepath.Join(cfg.GOMODCACHE, "cache", "lock") - if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil { return nil, fmt.Errorf("failed to create cache directory: %w", err) } @@ -194,12 +194,14 @@ type cachingRepo struct { once sync.Once initRepo func(context.Context) (Repo, error) r Repo + fetcher *Fetcher } -func newCachingRepo(ctx context.Context, path string, initRepo func(context.Context) (Repo, error)) *cachingRepo { +func newCachingRepo(ctx context.Context, fetcher *Fetcher, path string, initRepo func(context.Context) (Repo, error)) *cachingRepo { return &cachingRepo{ path: path, initRepo: initRepo, + fetcher: fetcher, } } @@ -226,7 +228,6 @@ func (r *cachingRepo) Versions(ctx context.Context, prefix string) (*Versions, e v, err := r.versionsCache.Do(prefix, func() (*Versions, error) { return r.repo(ctx).Versions(ctx, prefix) }) - if err != nil { return nil, err } @@ -309,7 +310,7 @@ func (r *cachingRepo) GoMod(ctx context.Context, version string) ([]byte, error) return r.repo(ctx).GoMod(ctx, version) } text, err := r.gomodCache.Do(version, func() ([]byte, error) { - file, text, err := readDiskGoMod(ctx, r.path, version) + file, text, err := r.fetcher.readDiskGoMod(ctx, r.path, version) if err == nil { // Note: readDiskGoMod already called checkGoMod. return text, nil @@ -317,7 +318,7 @@ func (r *cachingRepo) GoMod(ctx context.Context, version string) ([]byte, error) text, err = r.repo(ctx).GoMod(ctx, version) if err == nil { - if err := checkGoMod(r.path, version, text); err != nil { + if err := checkGoMod(r.fetcher, r.path, version, text); err != nil { return text, err } if err := writeDiskGoMod(ctx, file, text); err != nil { @@ -341,7 +342,7 @@ func (r *cachingRepo) Zip(ctx context.Context, dst io.Writer, version string) er // InfoFile is like Lookup(ctx, path).Stat(version) but also returns the name of the file // containing the cached information. -func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, error) { +func (f *Fetcher) InfoFile(ctx context.Context, path, version string) (*RevInfo, string, error) { if !gover.ModIsValid(path, version) { return nil, "", fmt.Errorf("invalid version %q", version) } @@ -353,7 +354,7 @@ func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, erro var info *RevInfo var err2info map[error]*RevInfo err := TryProxies(func(proxy string) error { - i, err := Lookup(ctx, proxy, path).Stat(ctx, version) + i, err := f.Lookup(ctx, proxy, path).Stat(ctx, version) if err == nil { info = i } else { @@ -379,7 +380,7 @@ func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, erro // GoMod is like Lookup(ctx, path).GoMod(rev) but avoids the // repository path resolution in Lookup if the result is // already cached on local disk. -func GoMod(ctx context.Context, path, rev string) ([]byte, error) { +func (f *Fetcher) GoMod(ctx context.Context, path, rev string) ([]byte, error) { // Convert commit hash to pseudo-version // to increase cache hit rate. if !gover.ModIsValid(path, rev) { @@ -390,7 +391,7 @@ func GoMod(ctx context.Context, path, rev string) ([]byte, error) { return nil, err } err := TryProxies(func(proxy string) error { - info, err := Lookup(ctx, proxy, path).Stat(ctx, rev) + info, err := f.Lookup(ctx, proxy, path).Stat(ctx, rev) if err == nil { rev = info.Version } @@ -402,13 +403,13 @@ func GoMod(ctx context.Context, path, rev string) ([]byte, error) { } } - _, data, err := readDiskGoMod(ctx, path, rev) + _, data, err := f.readDiskGoMod(ctx, path, rev) if err == nil { return data, nil } err = TryProxies(func(proxy string) (err error) { - data, err = Lookup(ctx, proxy, path).GoMod(ctx, rev) + data, err = f.Lookup(ctx, proxy, path).GoMod(ctx, rev) return err }) return data, err @@ -416,11 +417,11 @@ func GoMod(ctx context.Context, path, rev string) ([]byte, error) { // GoModFile is like GoMod but returns the name of the file containing // the cached information. -func GoModFile(ctx context.Context, path, version string) (string, error) { +func (f *Fetcher) GoModFile(ctx context.Context, path, version string) (string, error) { if !gover.ModIsValid(path, version) { return "", fmt.Errorf("invalid version %q", version) } - if _, err := GoMod(ctx, path, version); err != nil { + if _, err := f.GoMod(ctx, path, version); err != nil { return "", err } // GoMod should have populated the disk cache for us. @@ -433,11 +434,11 @@ func GoModFile(ctx context.Context, path, version string) (string, error) { // GoModSum returns the go.sum entry for the module version's go.mod file. // (That is, it returns the entry listed in go.sum as "path version/go.mod".) -func GoModSum(ctx context.Context, path, version string) (string, error) { +func (f *Fetcher) GoModSum(ctx context.Context, path, version string) (string, error) { if !gover.ModIsValid(path, version) { return "", fmt.Errorf("invalid version %q", version) } - data, err := GoMod(ctx, path, version) + data, err := f.GoMod(ctx, path, version) if err != nil { return "", err } @@ -565,7 +566,7 @@ var oldVgoPrefix = []byte("//vgo 0.0.") // returning the name of the cache file and the result. // If the read fails, the caller can use // writeDiskGoMod(file, data) to write a new cache entry. -func readDiskGoMod(ctx context.Context, path, rev string) (file string, data []byte, err error) { +func (f *Fetcher) readDiskGoMod(ctx context.Context, path, rev string) (file string, data []byte, err error) { if gover.IsToolchain(path) { return "", nil, errNotCached } @@ -578,7 +579,7 @@ func readDiskGoMod(ctx context.Context, path, rev string) (file string, data []b } if err == nil { - if err := checkGoMod(path, rev, data); err != nil { + if err := checkGoMod(f, path, rev, data); err != nil { return "", nil, err } } @@ -653,13 +654,13 @@ func writeDiskCache(ctx context.Context, file string, data []byte) error { return nil } // Make sure directory for file exists. - if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(file), 0o777); err != nil { return err } // Write the file to a temporary location, and then rename it to its final // path to reduce the likelihood of a corrupt file existing at that final path. - f, err := tempFile(ctx, filepath.Dir(file), filepath.Base(file), 0666) + f, err := tempFile(ctx, filepath.Dir(file), filepath.Base(file), 0o666) if err != nil { return err } @@ -812,7 +813,7 @@ func checkCacheDir(ctx context.Context) error { statCacheErr = fmt.Errorf("could not create module cache: %w", err) return } - if err := os.MkdirAll(cfg.GOMODCACHE, 0777); err != nil { + if err := os.MkdirAll(cfg.GOMODCACHE, 0o777); err != nil { statCacheErr = fmt.Errorf("could not create module cache: %w", err) return } diff --git a/src/cmd/go/internal/modfetch/coderepo_test.go b/src/cmd/go/internal/modfetch/coderepo_test.go index 68594746601..a403e83b847 100644 --- a/src/cmd/go/internal/modfetch/coderepo_test.go +++ b/src/cmd/go/internal/modfetch/coderepo_test.go @@ -36,7 +36,6 @@ func TestMain(m *testing.M) { } func testMain(m *testing.M) (err error) { - cfg.GOPROXY = "direct" // The sum database is populated using a released version of the go command, @@ -56,7 +55,7 @@ func testMain(m *testing.M) (err error) { }() cfg.GOMODCACHE = filepath.Join(dir, "modcache") - if err := os.Mkdir(cfg.GOMODCACHE, 0755); err != nil { + if err := os.Mkdir(cfg.GOMODCACHE, 0o755); err != nil { return err } @@ -589,6 +588,7 @@ var codeRepoTests = []codeRepoTest{ func TestCodeRepo(t *testing.T) { testenv.MustHaveExternalNetwork(t) tmpdir := t.TempDir() + fetcher := NewFetcher() for _, tt := range codeRepoTests { f := func(tt codeRepoTest) func(t *testing.T) { @@ -603,7 +603,7 @@ func TestCodeRepo(t *testing.T) { } ctx := context.Background() - repo := Lookup(ctx, "direct", tt.path) + repo := fetcher.Lookup(ctx, "direct", tt.path) if tt.mpath == "" { tt.mpath = tt.path @@ -817,7 +817,7 @@ var codeRepoVersionsTests = []struct { func TestCodeRepoVersions(t *testing.T) { testenv.MustHaveExternalNetwork(t) - + fetcher := NewFetcher() for _, tt := range codeRepoVersionsTests { tt := tt t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) { @@ -831,7 +831,7 @@ func TestCodeRepoVersions(t *testing.T) { } ctx := context.Background() - repo := Lookup(ctx, "direct", tt.path) + repo := fetcher.Lookup(ctx, "direct", tt.path) list, err := repo.Versions(ctx, tt.prefix) if err != nil { t.Fatalf("Versions(%q): %v", tt.prefix, err) @@ -898,7 +898,7 @@ var latestTests = []struct { func TestLatest(t *testing.T) { testenv.MustHaveExternalNetwork(t) - + fetcher := NewFetcher() for _, tt := range latestTests { name := strings.ReplaceAll(tt.path, "/", "_") t.Run(name, func(t *testing.T) { @@ -909,7 +909,7 @@ func TestLatest(t *testing.T) { } ctx := context.Background() - repo := Lookup(ctx, "direct", tt.path) + repo := fetcher.Lookup(ctx, "direct", tt.path) info, err := repo.Latest(ctx) if err != nil { if tt.err != "" { diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 0a84aecd426..7c9280f1d09 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -40,7 +40,7 @@ var ErrToolchain = errors.New("internal error: invalid operation on toolchain mo // Download downloads the specific module version to the // local download cache and returns the name of the directory // corresponding to the root of the module's file tree. -func Download(ctx context.Context, mod module.Version) (dir string, err error) { +func (f *Fetcher) Download(ctx context.Context, mod module.Version) (dir string, err error) { if gover.IsToolchain(mod.Path) { return "", ErrToolchain } @@ -49,12 +49,12 @@ func Download(ctx context.Context, mod module.Version) (dir string, err error) { } // The par.Cache here avoids duplicate work. - return ModuleFetchState.downloadCache.Do(mod, func() (string, error) { - dir, err := download(ctx, mod) + return f.downloadCache.Do(mod, func() (string, error) { + dir, err := f.download(ctx, mod) if err != nil { return "", err } - checkMod(ctx, mod) + f.checkMod(ctx, mod) // If go.mod exists (not an old legacy module), check version is not too new. if data, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil { @@ -73,12 +73,12 @@ func Download(ctx context.Context, mod module.Version) (dir string, err error) { // Unzip is like Download but is given the explicit zip file to use, // rather than downloading it. This is used for the GOFIPS140 zip files, // which ship in the Go distribution itself. -func Unzip(ctx context.Context, mod module.Version, zipfile string) (dir string, err error) { +func (f *Fetcher) Unzip(ctx context.Context, mod module.Version, zipfile string) (dir string, err error) { if err := checkCacheDir(ctx); err != nil { base.Fatal(err) } - return ModuleFetchState.downloadCache.Do(mod, func() (string, error) { + return f.downloadCache.Do(mod, func() (string, error) { ctx, span := trace.StartSpan(ctx, "modfetch.Unzip "+mod.String()) defer span.Done() @@ -94,7 +94,7 @@ func Unzip(ctx context.Context, mod module.Version, zipfile string) (dir string, }) } -func download(ctx context.Context, mod module.Version) (dir string, err error) { +func (f *Fetcher) download(ctx context.Context, mod module.Version) (dir string, err error) { ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String()) defer span.Done() @@ -109,7 +109,7 @@ func download(ctx context.Context, mod module.Version) (dir string, err error) { // To avoid cluttering the cache with extraneous files, // DownloadZip uses the same lockfile as Download. // Invoke DownloadZip before locking the file. - zipfile, err := DownloadZip(ctx, mod) + zipfile, err := f.DownloadZip(ctx, mod) if err != nil { return "", err } @@ -171,10 +171,10 @@ func unzip(ctx context.Context, mod module.Version, zipfile string) (dir string, // Go 1.14.2 and higher respect .partial files. Older versions may use // partially extracted directories. 'go mod verify' can detect this, // and 'go clean -modcache' can fix it. - if err := os.MkdirAll(parentDir, 0777); err != nil { + if err := os.MkdirAll(parentDir, 0o777); err != nil { return "", err } - if err := os.WriteFile(partialPath, nil, 0666); err != nil { + if err := os.WriteFile(partialPath, nil, 0o666); err != nil { return "", err } if err := modzip.Unzip(dir, mod, zipfile); err != nil { @@ -198,7 +198,7 @@ var downloadZipCache par.ErrCache[module.Version, string] // DownloadZip downloads the specific module version to the // local zip cache and returns the name of the zip file. -func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) { +func (f *Fetcher) DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) { // The par.Cache here avoids duplicate work. return downloadZipCache.Do(mod, func() (string, error) { zipfile, err := CachePath(ctx, mod, "zip") @@ -210,8 +210,8 @@ func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err e // Return early if the zip and ziphash files exist. if _, err := os.Stat(zipfile); err == nil { if _, err := os.Stat(ziphashfile); err == nil { - if !HaveSum(mod) { - checkMod(ctx, mod) + if !HaveSum(f, mod) { + f.checkMod(ctx, mod) } return zipfile, nil } @@ -238,14 +238,14 @@ func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err e } defer unlock() - if err := downloadZip(ctx, mod, zipfile); err != nil { + if err := f.downloadZip(ctx, mod, zipfile); err != nil { return "", err } return zipfile, nil }) } -func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) { +func (f *Fetcher) downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) { ctx, span := trace.StartSpan(ctx, "modfetch.downloadZip "+zipfile) defer span.Done() @@ -264,7 +264,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e } // Create parent directories. - if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(zipfile), 0o777); err != nil { return err } @@ -281,7 +281,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // If the zip file exists, the ziphash file must have been deleted // or lost after a file system crash. Re-hash the zip without downloading. if zipExists { - return hashZip(mod, zipfile, ziphashfile) + return hashZip(f, mod, zipfile, ziphashfile) } // From here to the os.Rename call below is functionally almost equivalent to @@ -289,14 +289,14 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // contents of the file (by hashing it) before we commit it. Because the file // is zip-compressed, we need an actual file — or at least an io.ReaderAt — to // validate it: we can't just tee the stream as we write it. - f, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0666) + file, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666) if err != nil { return err } defer func() { if err != nil { - f.Close() - os.Remove(f.Name()) + file.Close() + os.Remove(file.Name()) } }() @@ -305,18 +305,18 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e if unrecoverableErr != nil { return unrecoverableErr } - repo := Lookup(ctx, proxy, mod.Path) - err := repo.Zip(ctx, f, mod.Version) + repo := f.Lookup(ctx, proxy, mod.Path) + err := repo.Zip(ctx, file, mod.Version) if err != nil { // Zip may have partially written to f before failing. // (Perhaps the server crashed while sending the file?) // Since we allow fallback on error in some cases, we need to fix up the // file to be empty again for the next attempt. - if _, err := f.Seek(0, io.SeekStart); err != nil { + if _, err := file.Seek(0, io.SeekStart); err != nil { unrecoverableErr = err return err } - if err := f.Truncate(0); err != nil { + if err := file.Truncate(0); err != nil { unrecoverableErr = err return err } @@ -330,30 +330,30 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // Double-check that the paths within the zip file are well-formed. // // TODO(bcmills): There is a similar check within the Unzip function. Can we eliminate one? - fi, err := f.Stat() + fi, err := file.Stat() if err != nil { return err } - z, err := zip.NewReader(f, fi.Size()) + z, err := zip.NewReader(file, fi.Size()) if err != nil { return err } prefix := mod.Path + "@" + mod.Version + "/" - for _, f := range z.File { - if !strings.HasPrefix(f.Name, prefix) { - return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name) + for _, zf := range z.File { + if !strings.HasPrefix(zf.Name, prefix) { + return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], zf.Name) } } - if err := f.Close(); err != nil { + if err := file.Close(); err != nil { return err } // Hash the zip file and check the sum before renaming to the final location. - if err := hashZip(mod, f.Name(), ziphashfile); err != nil { + if err := hashZip(f, mod, file.Name(), ziphashfile); err != nil { return err } - if err := os.Rename(f.Name(), zipfile); err != nil { + if err := os.Rename(file.Name(), zipfile); err != nil { return err } @@ -367,12 +367,12 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // // If the hash does not match go.sum (or the sumdb if enabled), hashZip returns // an error and does not write ziphashfile. -func hashZip(mod module.Version, zipfile, ziphashfile string) (err error) { +func hashZip(f *Fetcher, mod module.Version, zipfile, ziphashfile string) (err error) { hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash) if err != nil { return err } - if err := checkModSum(mod, hash); err != nil { + if err := checkModSum(f, mod, hash); err != nil { return err } hf, err := lockedfile.Create(ziphashfile) @@ -404,7 +404,7 @@ func makeDirsReadOnly(dir string) { filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err == nil && d.IsDir() { info, err := d.Info() - if err == nil && info.Mode()&0222 != 0 { + if err == nil && info.Mode()&0o222 != 0 { dirs = append(dirs, pathMode{path, info.Mode()}) } } @@ -413,7 +413,7 @@ func makeDirsReadOnly(dir string) { // Run over list backward to chmod children before parents. for i := len(dirs) - 1; i >= 0; i-- { - os.Chmod(dirs[i].path, dirs[i].mode&^0222) + os.Chmod(dirs[i].path, dirs[i].mode&^0o222) } } @@ -426,7 +426,7 @@ func RemoveAll(dir string) error { return nil // ignore errors walking in file system } if info.IsDir() { - os.Chmod(path, 0777) + os.Chmod(path, 0o777) } return nil }) @@ -442,11 +442,6 @@ type modSum struct { sum string } -var goSum struct { - mu sync.Mutex - sumState -} - type sumState struct { m map[module.Version][]string // content of go.sum file w map[string]map[module.Version][]string // sum file in workspace -> content of that sum file @@ -459,12 +454,12 @@ type modSumStatus struct { used, dirty bool } -// State holds a snapshot of the global state of the modfetch package. -type State struct { +// Fetcher holds a snapshot of the global state of the modfetch package. +type Fetcher struct { // path to go.sum; set by package modload - GoSumFile string + goSumFile string // path to module go.sums in workspace; set by package modload - WorkspaceGoSumFiles []string + workspaceGoSumFiles []string // The Lookup cache is used cache the work done by Lookup. // It is important that the global functions of this package that access it do not // do so after they return. @@ -476,29 +471,40 @@ type State struct { // non-thread-safe SetState function. downloadCache *par.ErrCache[module.Version, string] // version → directory; + mu sync.Mutex sumState sumState } -var ModuleFetchState *State = NewState() +func NewFetcher() *Fetcher { + f := new(Fetcher) + f.lookupCache = new(par.Cache[lookupCacheKey, Repo]) + f.downloadCache = new(par.ErrCache[module.Version, string]) + return f +} -func NewState() *State { - s := new(State) - s.lookupCache = new(par.Cache[lookupCacheKey, Repo]) - s.downloadCache = new(par.ErrCache[module.Version, string]) - return s +func (f *Fetcher) GoSumFile() string { + return f.goSumFile +} + +func (f *Fetcher) SetGoSumFile(str string) { + f.goSumFile = str +} + +func (f *Fetcher) AddWorkspaceGoSumFile(file string) { + f.workspaceGoSumFiles = append(f.workspaceGoSumFiles, file) } // Reset resets globals in the modfetch package, so previous loads don't affect // contents of go.sum files. -func Reset() { - SetState(State{}) +func (f *Fetcher) Reset() { + f.SetState(NewFetcher()) } // SetState sets the global state of the modfetch package to the newState, and returns the previous // global state. newState should have been returned by SetState, or be an empty State. // There should be no concurrent calls to any of the exported functions of this package with // a call to SetState because it will modify the global state in a non-thread-safe way. -func SetState(newState State) (oldState State) { +func (f *Fetcher) SetState(newState *Fetcher) (oldState *Fetcher) { if newState.lookupCache == nil { newState.lookupCache = new(par.Cache[lookupCacheKey, Repo]) } @@ -506,26 +512,26 @@ func SetState(newState State) (oldState State) { newState.downloadCache = new(par.ErrCache[module.Version, string]) } - goSum.mu.Lock() - defer goSum.mu.Unlock() + f.mu.Lock() + defer f.mu.Unlock() - oldState = State{ - GoSumFile: ModuleFetchState.GoSumFile, - WorkspaceGoSumFiles: ModuleFetchState.WorkspaceGoSumFiles, - lookupCache: ModuleFetchState.lookupCache, - downloadCache: ModuleFetchState.downloadCache, - sumState: goSum.sumState, + oldState = &Fetcher{ + goSumFile: f.goSumFile, + workspaceGoSumFiles: f.workspaceGoSumFiles, + lookupCache: f.lookupCache, + downloadCache: f.downloadCache, + sumState: f.sumState, } - ModuleFetchState.GoSumFile = newState.GoSumFile - ModuleFetchState.WorkspaceGoSumFiles = newState.WorkspaceGoSumFiles + f.SetGoSumFile(newState.goSumFile) + f.workspaceGoSumFiles = newState.workspaceGoSumFiles // Uses of lookupCache and downloadCache both can call checkModSum, // which in turn sets the used bit on goSum.status for modules. // Set (or reset) them so used can be computed properly. - ModuleFetchState.lookupCache = newState.lookupCache - ModuleFetchState.downloadCache = newState.downloadCache + f.lookupCache = newState.lookupCache + f.downloadCache = newState.downloadCache // Set, or reset all fields on goSum. If being reset to empty, it will be initialized later. - goSum.sumState = newState.sumState + f.sumState = newState.sumState return oldState } @@ -534,28 +540,28 @@ func SetState(newState State) (oldState State) { // The boolean it returns reports whether the // use of go.sum is now enabled. // The goSum lock must be held. -func initGoSum() (bool, error) { - if ModuleFetchState.GoSumFile == "" { +func (f *Fetcher) initGoSum() (bool, error) { + if f.goSumFile == "" { return false, nil } - if goSum.m != nil { + if f.sumState.m != nil { return true, nil } - goSum.m = make(map[module.Version][]string) - goSum.status = make(map[modSum]modSumStatus) - goSum.w = make(map[string]map[module.Version][]string) + f.sumState.m = make(map[module.Version][]string) + f.sumState.status = make(map[modSum]modSumStatus) + f.sumState.w = make(map[string]map[module.Version][]string) - for _, f := range ModuleFetchState.WorkspaceGoSumFiles { - goSum.w[f] = make(map[module.Version][]string) - _, err := readGoSumFile(goSum.w[f], f) + for _, fn := range f.workspaceGoSumFiles { + f.sumState.w[fn] = make(map[module.Version][]string) + _, err := readGoSumFile(f.sumState.w[fn], fn) if err != nil { return false, err } } - enabled, err := readGoSumFile(goSum.m, ModuleFetchState.GoSumFile) - goSum.enabled = enabled + enabled, err := readGoSumFile(f.sumState.m, f.goSumFile) + f.sumState.enabled = enabled return enabled, err } @@ -624,28 +630,28 @@ func readGoSum(dst map[module.Version][]string, file string, data []byte) { // The entry's hash must be generated with a known hash algorithm. // mod.Version may have a "/go.mod" suffix to distinguish sums for // .mod and .zip files. -func HaveSum(mod module.Version) bool { - goSum.mu.Lock() - defer goSum.mu.Unlock() - inited, err := initGoSum() +func HaveSum(f *Fetcher, mod module.Version) bool { + f.mu.Lock() + defer f.mu.Unlock() + inited, err := f.initGoSum() if err != nil || !inited { return false } - for _, goSums := range goSum.w { + for _, goSums := range f.sumState.w { for _, h := range goSums[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !goSum.status[modSum{mod, h}].dirty { + if !f.sumState.status[modSum{mod, h}].dirty { return true } } } - for _, h := range goSum.m[mod] { + for _, h := range f.sumState.m[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !goSum.status[modSum{mod, h}].dirty { + if !f.sumState.status[modSum{mod, h}].dirty { return true } } @@ -658,20 +664,20 @@ func HaveSum(mod module.Version) bool { // The entry's hash must be generated with a known hash algorithm. // mod.Version may have a "/go.mod" suffix to distinguish sums for // .mod and .zip files. -func RecordedSum(mod module.Version) (sum string, ok bool) { - goSum.mu.Lock() - defer goSum.mu.Unlock() - inited, err := initGoSum() +func (f *Fetcher) RecordedSum(mod module.Version) (sum string, ok bool) { + f.mu.Lock() + defer f.mu.Unlock() + inited, err := f.initGoSum() foundSum := "" if err != nil || !inited { return "", false } - for _, goSums := range goSum.w { + for _, goSums := range f.sumState.w { for _, h := range goSums[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !goSum.status[modSum{mod, h}].dirty { + if !f.sumState.status[modSum{mod, h}].dirty { if foundSum != "" && foundSum != h { // conflicting sums exist return "", false } @@ -679,11 +685,11 @@ func RecordedSum(mod module.Version) (sum string, ok bool) { } } } - for _, h := range goSum.m[mod] { + for _, h := range f.sumState.m[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !goSum.status[modSum{mod, h}].dirty { + if !f.sumState.status[modSum{mod, h}].dirty { if foundSum != "" && foundSum != h { // conflicting sums exist return "", false } @@ -694,7 +700,7 @@ func RecordedSum(mod module.Version) (sum string, ok bool) { } // checkMod checks the given module's checksum and Go version. -func checkMod(ctx context.Context, mod module.Version) { +func (f *Fetcher) checkMod(ctx context.Context, mod module.Version) { // Do the file I/O before acquiring the go.sum lock. ziphash, err := CachePath(ctx, mod, "ziphash") if err != nil { @@ -711,7 +717,7 @@ func checkMod(ctx context.Context, mod module.Version) { if err != nil { base.Fatalf("verifying %v", module.VersionError(mod, err)) } - err = hashZip(mod, zip, ziphash) + err = hashZip(f, mod, zip, ziphash) if err != nil { base.Fatalf("verifying %v", module.VersionError(mod, err)) } @@ -722,7 +728,7 @@ func checkMod(ctx context.Context, mod module.Version) { base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h))) } - if err := checkModSum(mod, h); err != nil { + if err := checkModSum(f, mod, h); err != nil { base.Fatalf("%s", err) } } @@ -736,39 +742,39 @@ func goModSum(data []byte) (string, error) { // checkGoMod checks the given module's go.mod checksum; // data is the go.mod content. -func checkGoMod(path, version string, data []byte) error { +func checkGoMod(f *Fetcher, path, version string, data []byte) error { h, err := goModSum(data) if err != nil { return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)} } - return checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h) + return checkModSum(f, module.Version{Path: path, Version: version + "/go.mod"}, h) } // checkModSum checks that the recorded checksum for mod is h. // // mod.Version may have the additional suffix "/go.mod" to request the checksum // for the module's go.mod file only. -func checkModSum(mod module.Version, h string) error { +func checkModSum(f *Fetcher, mod module.Version, h string) error { // We lock goSum when manipulating it, // but we arrange to release the lock when calling checkSumDB, // so that parallel calls to checkModHash can execute parallel calls // to checkSumDB. // Check whether mod+h is listed in go.sum already. If so, we're done. - goSum.mu.Lock() - inited, err := initGoSum() + f.mu.Lock() + inited, err := f.initGoSum() if err != nil { - goSum.mu.Unlock() + f.mu.Unlock() return err } - done := inited && haveModSumLocked(mod, h) + done := inited && haveModSumLocked(f, mod, h) if inited { - st := goSum.status[modSum{mod, h}] + st := f.sumState.status[modSum{mod, h}] st.used = true - goSum.status[modSum{mod, h}] = st + f.sumState.status[modSum{mod, h}] = st } - goSum.mu.Unlock() + f.mu.Unlock() if done { return nil @@ -785,12 +791,12 @@ func checkModSum(mod module.Version, h string) error { // Add mod+h to go.sum, if it hasn't appeared already. if inited { - goSum.mu.Lock() - addModSumLocked(mod, h) - st := goSum.status[modSum{mod, h}] + f.mu.Lock() + addModSumLocked(f, mod, h) + st := f.sumState.status[modSum{mod, h}] st.dirty = true - goSum.status[modSum{mod, h}] = st - goSum.mu.Unlock() + f.sumState.status[modSum{mod, h}] = st + f.mu.Unlock() } return nil } @@ -798,12 +804,12 @@ func checkModSum(mod module.Version, h string) error { // haveModSumLocked reports whether the pair mod,h is already listed in go.sum. // If it finds a conflicting pair instead, it calls base.Fatalf. // goSum.mu must be locked. -func haveModSumLocked(mod module.Version, h string) bool { +func haveModSumLocked(f *Fetcher, mod module.Version, h string) bool { sumFileName := "go.sum" - if strings.HasSuffix(ModuleFetchState.GoSumFile, "go.work.sum") { + if strings.HasSuffix(f.goSumFile, "go.work.sum") { sumFileName = "go.work.sum" } - for _, vh := range goSum.m[mod] { + for _, vh := range f.sumState.m[mod] { if h == vh { return true } @@ -815,7 +821,7 @@ func haveModSumLocked(mod module.Version, h string) bool { foundMatch := false // Check sums from all files in case there are conflicts between // the files. - for goSumFile, goSums := range goSum.w { + for goSumFile, goSums := range f.sumState.w { for _, vh := range goSums[mod] { if h == vh { foundMatch = true @@ -829,14 +835,14 @@ func haveModSumLocked(mod module.Version, h string) bool { // addModSumLocked adds the pair mod,h to go.sum. // goSum.mu must be locked. -func addModSumLocked(mod module.Version, h string) { - if haveModSumLocked(mod, h) { +func addModSumLocked(f *Fetcher, mod module.Version, h string) { + if haveModSumLocked(f, mod, h) { return } - if len(goSum.m[mod]) > 0 { - fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h) + if len(f.sumState.m[mod]) > 0 { + fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(f.sumState.m[mod], ", "), h) } - goSum.m[mod] = append(goSum.m[mod], h) + f.sumState.m[mod] = append(f.sumState.m[mod], h) } // checkSumDB checks the mod, h pair against the Go checksum database. @@ -915,12 +921,12 @@ var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=reado // It should have entries for both module content sums and go.mod sums // (version ends with "/go.mod"). Existing sums will be preserved unless they // have been marked for deletion with TrimGoSum. -func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error { - goSum.mu.Lock() - defer goSum.mu.Unlock() +func (f *Fetcher) WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error { + f.mu.Lock() + defer f.mu.Unlock() // If we haven't read the go.sum file yet, don't bother writing it. - if !goSum.enabled { + if !f.sumState.enabled { return nil } @@ -929,9 +935,9 @@ func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool // just return without opening go.sum. dirty := false Outer: - for m, hs := range goSum.m { + for m, hs := range f.sumState.m { for _, h := range hs { - st := goSum.status[modSum{m, h}] + st := f.sumState.status[modSum{m, h}] if st.dirty && (!st.used || keep[m]) { dirty = true break Outer @@ -944,7 +950,7 @@ Outer: if readonly { return ErrGoSumDirty } - if fsys.Replaced(ModuleFetchState.GoSumFile) { + if fsys.Replaced(f.goSumFile) { base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay") } @@ -954,64 +960,63 @@ Outer: defer unlock() } - err := lockedfile.Transform(ModuleFetchState.GoSumFile, func(data []byte) ([]byte, error) { - tidyGoSum := tidyGoSum(data, keep) + err := lockedfile.Transform(f.goSumFile, func(data []byte) ([]byte, error) { + tidyGoSum := tidyGoSum(f, data, keep) return tidyGoSum, nil }) - if err != nil { return fmt.Errorf("updating go.sum: %w", err) } - goSum.status = make(map[modSum]modSumStatus) - goSum.overwrite = false + f.sumState.status = make(map[modSum]modSumStatus) + f.sumState.overwrite = false return nil } // TidyGoSum returns a tidy version of the go.sum file. // A missing go.sum file is treated as if empty. -func TidyGoSum(keep map[module.Version]bool) (before, after []byte) { - goSum.mu.Lock() - defer goSum.mu.Unlock() - before, err := lockedfile.Read(ModuleFetchState.GoSumFile) +func (f *Fetcher) TidyGoSum(keep map[module.Version]bool) (before, after []byte) { + f.mu.Lock() + defer f.mu.Unlock() + before, err := lockedfile.Read(f.goSumFile) if err != nil && !errors.Is(err, fs.ErrNotExist) { base.Fatalf("reading go.sum: %v", err) } - after = tidyGoSum(before, keep) + after = tidyGoSum(f, before, keep) return before, after } // tidyGoSum returns a tidy version of the go.sum file. // The goSum lock must be held. -func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { - if !goSum.overwrite { +func tidyGoSum(f *Fetcher, data []byte, keep map[module.Version]bool) []byte { + if !f.sumState.overwrite { // Incorporate any sums added by other processes in the meantime. // Add only the sums that we actually checked: the user may have edited or // truncated the file to remove erroneous hashes, and we shouldn't restore // them without good reason. - goSum.m = make(map[module.Version][]string, len(goSum.m)) - readGoSum(goSum.m, ModuleFetchState.GoSumFile, data) - for ms, st := range goSum.status { - if st.used && !sumInWorkspaceModulesLocked(ms.mod) { - addModSumLocked(ms.mod, ms.sum) + f.sumState.m = make(map[module.Version][]string, len(f.sumState.m)) + readGoSum(f.sumState.m, f.goSumFile, data) + for ms, st := range f.sumState.status { + if st.used && !sumInWorkspaceModulesLocked(f, ms.mod) { + addModSumLocked(f, ms.mod, ms.sum) } } } - mods := make([]module.Version, 0, len(goSum.m)) - for m := range goSum.m { + mods := make([]module.Version, 0, len(f.sumState.m)) + for m := range f.sumState.m { mods = append(mods, m) } module.Sort(mods) var buf bytes.Buffer for _, m := range mods { - list := goSum.m[m] + list := f.sumState.m[m] sort.Strings(list) str.Uniq(&list) for _, h := range list { - st := goSum.status[modSum{m, h}] - if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(m) { + st := f.sumState.status[modSum{m, h}] + if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(f, m) { fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) } } @@ -1019,8 +1024,8 @@ func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { return buf.Bytes() } -func sumInWorkspaceModulesLocked(m module.Version) bool { - for _, goSums := range goSum.w { +func sumInWorkspaceModulesLocked(f *Fetcher, m module.Version) bool { + for _, goSums := range f.sumState.w { if _, ok := goSums[m]; ok { return true } @@ -1034,10 +1039,10 @@ func sumInWorkspaceModulesLocked(m module.Version) bool { // keep is used to check whether a sum should be retained in go.mod. It should // have entries for both module content sums and go.mod sums (version ends // with "/go.mod"). -func TrimGoSum(keep map[module.Version]bool) { - goSum.mu.Lock() - defer goSum.mu.Unlock() - inited, err := initGoSum() +func (f *Fetcher) TrimGoSum(keep map[module.Version]bool) { + f.mu.Lock() + defer f.mu.Unlock() + inited, err := f.initGoSum() if err != nil { base.Fatalf("%s", err) } @@ -1045,12 +1050,12 @@ func TrimGoSum(keep map[module.Version]bool) { return } - for m, hs := range goSum.m { + for m, hs := range f.sumState.m { if !keep[m] { for _, h := range hs { - goSum.status[modSum{m, h}] = modSumStatus{used: false, dirty: true} + f.sumState.status[modSum{m, h}] = modSumStatus{used: false, dirty: true} } - goSum.overwrite = true + f.sumState.overwrite = true } } } diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go index bb5dfc4655d..5ed2f259a00 100644 --- a/src/cmd/go/internal/modfetch/repo.go +++ b/src/cmd/go/internal/modfetch/repo.go @@ -200,14 +200,14 @@ type lookupCacheKey struct { // // A successful return does not guarantee that the module // has any defined versions. -func Lookup(ctx context.Context, proxy, path string) Repo { +func (f *Fetcher) Lookup(ctx context.Context, proxy, path string) Repo { if traceRepo { defer logCall("Lookup(%q, %q)", proxy, path)() } - return ModuleFetchState.lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo { - return newCachingRepo(ctx, path, func(ctx context.Context) (Repo, error) { - r, err := lookup(ctx, proxy, path) + return f.lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo { + return newCachingRepo(ctx, f, path, func(ctx context.Context) (Repo, error) { + r, err := lookup(f, ctx, proxy, path) if err == nil && traceRepo { r = newLoggingRepo(r) } @@ -223,13 +223,13 @@ var lookupLocalCache = new(par.Cache[string, Repo]) // path, Repo // codeRoot is the module path of the root module in the repository. // path is the module path of the module being looked up. // dir is the file system path of the repository containing the module. -func LookupLocal(ctx context.Context, codeRoot string, path string, dir string) Repo { +func (f *Fetcher) LookupLocal(ctx context.Context, codeRoot string, path string, dir string) Repo { if traceRepo { defer logCall("LookupLocal(%q)", path)() } return lookupLocalCache.Do(path, func() Repo { - return newCachingRepo(ctx, path, func(ctx context.Context) (Repo, error) { + return newCachingRepo(ctx, f, path, func(ctx context.Context) (Repo, error) { repoDir, vcsCmd, err := vcs.FromDir(dir, "") if err != nil { return nil, err @@ -248,14 +248,14 @@ func LookupLocal(ctx context.Context, codeRoot string, path string, dir string) } // lookup returns the module with the given module path. -func lookup(ctx context.Context, proxy, path string) (r Repo, err error) { +func lookup(fetcher_ *Fetcher, ctx context.Context, proxy, path string) (r Repo, err error) { if cfg.BuildMod == "vendor" { return nil, errLookupDisabled } switch path { case "go", "toolchain": - return &toolchainRepo{path, Lookup(ctx, proxy, "golang.org/toolchain")}, nil + return &toolchainRepo{path, fetcher_.Lookup(ctx, proxy, "golang.org/toolchain")}, nil } if module.MatchPrefixPatterns(cfg.GONOPROXY, path) { diff --git a/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go b/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go index edae1d8f3cf..6b2312cbed0 100644 --- a/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go +++ b/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go @@ -112,6 +112,7 @@ func TestZipSums(t *testing.T) { // Download modules with a rate limit. We may run out of file descriptors // or cause timeouts without a limit. needUpdate := false + fetcher := modfetch.NewFetcher() for i := range tests { test := &tests[i] name := fmt.Sprintf("%s@%s", strings.ReplaceAll(test.m.Path, "/", "_"), test.m.Version) @@ -119,7 +120,7 @@ func TestZipSums(t *testing.T) { t.Parallel() ctx := context.Background() - zipPath, err := modfetch.DownloadZip(ctx, test.m) + zipPath, err := fetcher.DownloadZip(ctx, test.m) if err != nil { if *updateTestData { t.Logf("%s: could not download module: %s (will remove from testdata)", test.m, err) diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index c8dc6e29bf6..b731ccc047d 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -1788,11 +1788,11 @@ func (r *resolver) checkPackageProblems(loaderstate *modload.State, ctx context. if oldRepl := modload.Replacement(loaderstate, old); oldRepl.Path != "" { oldActual = oldRepl } - if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(oldActual) { + if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(loaderstate.Fetcher(), oldActual) { continue } r.work.Add(func() { - if _, err := modfetch.DownloadZip(ctx, mActual); err != nil { + if _, err := loaderstate.Fetcher().DownloadZip(ctx, mActual); err != nil { verb := "upgraded" if gover.ModCompare(m.Path, m.Version, old.Version) < 0 { verb = "downgraded" diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index 4f334a47203..b560dd6a617 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -329,7 +329,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod checksumOk := func(suffix string) bool { return rs == nil || m.Version == "" || !mustHaveSums(loaderstate) || - modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix}) + modfetch.HaveSum(loaderstate.Fetcher(), module.Version{Path: m.Path, Version: m.Version + suffix}) } mod := module.Version{Path: m.Path, Version: m.Version} @@ -368,7 +368,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod m.GoMod = gomod } } - if gomodsum, ok := modfetch.RecordedSum(modkey(mod)); ok { + if gomodsum, ok := loaderstate.fetcher.RecordedSum(modkey(mod)); ok { m.GoModSum = gomodsum } } @@ -377,7 +377,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod if err == nil { m.Dir = dir } - if sum, ok := modfetch.RecordedSum(mod); ok { + if sum, ok := loaderstate.fetcher.RecordedSum(mod); ok { m.Sum = sum } } diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go index 1996b7c26b0..8038a77b0b3 100644 --- a/src/cmd/go/internal/modload/edit.go +++ b/src/cmd/go/internal/modload/edit.go @@ -5,10 +5,6 @@ package modload import ( - "cmd/go/internal/cfg" - "cmd/go/internal/gover" - "cmd/go/internal/mvs" - "cmd/internal/par" "context" "errors" "fmt" @@ -16,6 +12,11 @@ import ( "os" "slices" + "cmd/go/internal/cfg" + "cmd/go/internal/gover" + "cmd/go/internal/mvs" + "cmd/internal/par" + "golang.org/x/mod/module" ) diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go index 0ec4102cc61..04e95b7a8d9 100644 --- a/src/cmd/go/internal/modload/import.go +++ b/src/cmd/go/internal/modload/import.go @@ -817,11 +817,11 @@ func fetch(loaderstate *State, ctx context.Context, mod module.Version) (dir str mod = r } - if mustHaveSums(loaderstate) && !modfetch.HaveSum(mod) { + if mustHaveSums(loaderstate) && !modfetch.HaveSum(loaderstate.Fetcher(), mod) { return "", false, module.VersionError(mod, &sumMissingError{}) } - dir, err = modfetch.Download(ctx, mod) + dir, err = loaderstate.Fetcher().Download(ctx, mod) return dir, false, err } diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index c4ff9656694..8bfae266928 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -60,7 +60,7 @@ func EnterModule(loaderstate *State, ctx context.Context, enterModroot string) { loaderstate.MainModules = nil // reset MainModules loaderstate.requirements = nil loaderstate.workFilePath = "" // Force module mode - modfetch.Reset() + loaderstate.Fetcher().Reset() loaderstate.modRoots = []string{enterModroot} LoadModFile(loaderstate, ctx) @@ -80,7 +80,7 @@ func EnterWorkspace(loaderstate *State, ctx context.Context) (exit func(), err e } // Reset the state to a clean state. - oldstate := loaderstate.setState(State{}) + oldstate := loaderstate.setState(NewState()) loaderstate.ForceUseModules = true // Load in workspace mode. @@ -385,11 +385,11 @@ func WorkFilePath(loaderstate *State) string { // Reset clears all the initialized, cached state about the use of modules, // so that we can start over. func (s *State) Reset() { - s.setState(State{}) + s.setState(NewState()) } -func (s *State) setState(new State) State { - oldState := State{ +func (s *State) setState(new *State) (old *State) { + old = &State{ initialized: s.initialized, ForceUseModules: s.ForceUseModules, RootMode: s.RootMode, @@ -397,6 +397,8 @@ func (s *State) setState(new State) State { modulesEnabled: cfg.ModulesEnabled, MainModules: s.MainModules, requirements: s.requirements, + workFilePath: s.workFilePath, + fetcher: s.fetcher, } s.initialized = new.initialized s.ForceUseModules = new.ForceUseModules @@ -409,8 +411,9 @@ func (s *State) setState(new State) State { // The modfetch package's global state is used to compute // the go.sum file, so save and restore it along with the // modload state. - oldState.modfetchState = modfetch.SetState(new.modfetchState) - return oldState + old.fetcher = s.fetcher.SetState(new.fetcher) + + return old } type State struct { @@ -447,11 +450,19 @@ type State struct { // Set to the path to the go.work file, or "" if workspace mode is // disabled - workFilePath string - modfetchState modfetch.State + workFilePath string + fetcher *modfetch.Fetcher } -func NewState() *State { return &State{} } +func NewState() *State { + s := new(State) + s.fetcher = modfetch.NewFetcher() + return s +} + +func (s *State) Fetcher() *modfetch.Fetcher { + return s.fetcher +} // Init determines whether module mode is enabled, locates the root of the // current module (if any), sets environment variables for Git subprocesses, and @@ -929,9 +940,9 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R } for _, modRoot := range loaderstate.modRoots { sumFile := strings.TrimSuffix(modFilePath(modRoot), ".mod") + ".sum" - modfetch.ModuleFetchState.WorkspaceGoSumFiles = append(modfetch.ModuleFetchState.WorkspaceGoSumFiles, sumFile) + loaderstate.Fetcher().AddWorkspaceGoSumFile(sumFile) } - modfetch.ModuleFetchState.GoSumFile = loaderstate.workFilePath + ".sum" + loaderstate.Fetcher().SetGoSumFile(loaderstate.workFilePath + ".sum") } else if len(loaderstate.modRoots) == 0 { // We're in module mode, but not inside a module. // @@ -951,7 +962,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R // // See golang.org/issue/32027. } else { - modfetch.ModuleFetchState.GoSumFile = strings.TrimSuffix(modFilePath(loaderstate.modRoots[0]), ".mod") + ".sum" + loaderstate.Fetcher().SetGoSumFile(strings.TrimSuffix(modFilePath(loaderstate.modRoots[0]), ".mod") + ".sum") } if len(loaderstate.modRoots) == 0 { // TODO(#49228): Instead of creating a fake module with an empty modroot, @@ -1957,7 +1968,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) if loaderstate.inWorkspaceMode() { // go.mod files aren't updated in workspace mode, but we still want to // update the go.work.sum file. - return modfetch.WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) + return loaderstate.Fetcher().WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) } _, updatedGoMod, modFile, err := UpdateGoModFromReqs(loaderstate, ctx, opts) if err != nil { @@ -1981,7 +1992,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) // Don't write go.mod, but write go.sum in case we added or trimmed sums. // 'go mod init' shouldn't write go.sum, since it will be incomplete. if cfg.CmdName != "mod init" { - if err := modfetch.WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)); err != nil { + if err := loaderstate.Fetcher().WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)); err != nil { return err } } @@ -2004,7 +2015,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) // 'go mod init' shouldn't write go.sum, since it will be incomplete. if cfg.CmdName != "mod init" { if err == nil { - err = modfetch.WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) + err = loaderstate.Fetcher().WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) } } }() diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index b4d128fe9a1..a432862429b 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -462,13 +462,13 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat } goModDiff := diff.Diff("current/go.mod", currentGoMod, "tidy/go.mod", updatedGoMod) - modfetch.TrimGoSum(keep) + loaderstate.Fetcher().TrimGoSum(keep) // Dropping compatibility for 1.16 may result in a strictly smaller go.sum. // Update the keep map with only the loaded.requirements. if gover.Compare(compatVersion, "1.16") > 0 { keep = keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums) } - currentGoSum, tidyGoSum := modfetch.TidyGoSum(keep) + currentGoSum, tidyGoSum := loaderstate.fetcher.TidyGoSum(keep) goSumDiff := diff.Diff("current/go.sum", currentGoSum, "tidy/go.sum", tidyGoSum) if len(goModDiff) > 0 { @@ -483,14 +483,14 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat } if !ExplicitWriteGoMod { - modfetch.TrimGoSum(keep) + loaderstate.Fetcher().TrimGoSum(keep) // commitRequirements below will also call WriteGoSum, but the "keep" map // we have here could be strictly larger: commitRequirements only commits // loaded.requirements, but here we may have also loaded (and want to // preserve checksums for) additional entities from compatRS, which are // only needed for compatibility with ld.TidyCompatibleVersion. - if err := modfetch.WriteGoSum(ctx, keep, mustHaveCompleteRequirements(loaderstate)); err != nil { + if err := loaderstate.Fetcher().WriteGoSum(ctx, keep, mustHaveCompleteRequirements(loaderstate)); err != nil { base.Fatal(err) } } diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index 7191833a0dc..1e54a580345 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -604,7 +604,7 @@ func goModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) actual := resolveReplacement(loaderstate, m) if mustHaveSums(loaderstate) && actual.Version != "" { key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"} - if !modfetch.HaveSum(key) { + if !modfetch.HaveSum(loaderstate.Fetcher(), key) { suggestion := fmt.Sprintf(" for go.mod file; to add it:\n\tgo mod download %s", m.Path) return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion}) } @@ -810,7 +810,7 @@ func rawGoModData(loaderstate *State, m module.Version) (name string, data []byt base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) } name = "go.mod" - data, err = modfetch.GoMod(context.TODO(), m.Path, m.Version) + data, err = loaderstate.Fetcher().GoMod(context.TODO(), m.Path, m.Version) } return name, data, err } diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go index f710ce2c624..b6bf0803c10 100644 --- a/src/cmd/go/internal/modload/query.go +++ b/src/cmd/go/internal/modload/query.go @@ -1124,7 +1124,7 @@ func lookupRepo(loaderstate *State, ctx context.Context, proxy, path string) (re err = module.CheckPath(path) } if err == nil { - repo = modfetch.Lookup(ctx, proxy, path) + repo = loaderstate.Fetcher().Lookup(ctx, proxy, path) } else { repo = emptyRepo{path: path, err: err} } @@ -1150,9 +1150,11 @@ func (er emptyRepo) ModulePath() string { return er.path } func (er emptyRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error { return fmt.Errorf("empty repo") } + func (er emptyRepo) Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) { return &modfetch.Versions{}, nil } + func (er emptyRepo) Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) { return nil, er.err } diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go index 4c7e7a5e576..192fb62fc26 100644 --- a/src/cmd/go/internal/toolchain/select.go +++ b/src/cmd/go/internal/toolchain/select.go @@ -25,7 +25,6 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/gover" - "cmd/go/internal/modfetch" "cmd/go/internal/modload" "cmd/go/internal/run" "cmd/go/internal/work" @@ -86,8 +85,10 @@ func FilterEnv(env []string) []string { return out } -var counterErrorsInvalidToolchainInFile = counter.New("go/errors:invalid-toolchain-in-file") -var toolchainTrace = godebug.New("#toolchaintrace").Value() == "1" +var ( + counterErrorsInvalidToolchainInFile = counter.New("go/errors:invalid-toolchain-in-file") + toolchainTrace = godebug.New("#toolchaintrace").Value() == "1" +) // Select invokes a different Go toolchain if directed by // the GOTOOLCHAIN environment variable or the user's configuration @@ -360,7 +361,7 @@ func Exec(s *modload.State, gotoolchain string) { Path: gotoolchainModule, Version: gotoolchainVersion + "-" + gotoolchain + "." + runtime.GOOS + "-" + runtime.GOARCH, } - dir, err := modfetch.Download(context.Background(), m) + dir, err := s.Fetcher().Download(context.Background(), m) if err != nil { if errors.Is(err, fs.ErrNotExist) { toolVers := gover.FromToolchain(gotoolchain) @@ -380,7 +381,7 @@ func Exec(s *modload.State, gotoolchain string) { if err != nil { base.Fatalf("download %s: %v", gotoolchain, err) } - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { // allowExec sets the exec permission bits on all files found in dir if pattern is the empty string, // or only those files that match the pattern if it's non-empty. allowExec := func(dir, pattern string) { @@ -399,7 +400,7 @@ func Exec(s *modload.State, gotoolchain string) { if err != nil { return err } - if err := os.Chmod(path, info.Mode()&0777|0111); err != nil { + if err := os.Chmod(path, info.Mode()&0o777|0o111); err != nil { return err } } diff --git a/src/cmd/go/internal/toolchain/switch.go b/src/cmd/go/internal/toolchain/switch.go index 76b608fdef4..ff4fce03074 100644 --- a/src/cmd/go/internal/toolchain/switch.go +++ b/src/cmd/go/internal/toolchain/switch.go @@ -97,7 +97,7 @@ func (s *Switcher) Switch(ctx context.Context) { } // Switch to newer Go toolchain if necessary and possible. - tv, err := NewerToolchain(ctx, s.TooNew.GoVersion) + tv, err := NewerToolchain(ctx, s.loaderstate.Fetcher(), s.TooNew.GoVersion) if err != nil { for _, err := range s.Errors { base.Error(err) @@ -130,8 +130,11 @@ func SwitchOrFatal(loaderstate *modload.State, ctx context.Context, err error) { // If the latest major release is 1.N.0, we use the latest patch release of 1.(N-1) if that's >= version. // Otherwise we use the latest 1.N if that's allowed. // Otherwise we use the latest release. -func NewerToolchain(ctx context.Context, version string) (string, error) { - fetch := autoToolchains +func NewerToolchain(ctx context.Context, f *modfetch.Fetcher, version string) (string, error) { + fetch := func(ctx context.Context) ([]string, error) { + return autoToolchains(ctx, f) + } + if !HasAuto() { fetch = pathToolchains } @@ -143,10 +146,10 @@ func NewerToolchain(ctx context.Context, version string) (string, error) { } // autoToolchains returns the list of toolchain versions available to GOTOOLCHAIN=auto or =min+auto mode. -func autoToolchains(ctx context.Context) ([]string, error) { +func autoToolchains(ctx context.Context, f *modfetch.Fetcher) ([]string, error) { var versions *modfetch.Versions err := modfetch.TryProxies(func(proxy string) error { - v, err := modfetch.Lookup(ctx, proxy, "go").Versions(ctx, "") + v, err := f.Lookup(ctx, proxy, "go").Versions(ctx, "") if err != nil { return err } diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index c483c19c65b..75d05d65de2 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -238,8 +238,6 @@ See also: go install, go get, go clean. `, } -const concurrentGCBackendCompilationEnabledByDefault = true - func init() { // break init cycle CmdBuild.Run = runBuild diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index 71c36e80cba..9a5e6c924c3 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -10,11 +10,11 @@ import ( "internal/buildcfg" "internal/platform" "io" - "log" "os" "path/filepath" "runtime" "strings" + "sync" "cmd/go/internal/base" "cmd/go/internal/cfg" @@ -127,7 +127,9 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg gcflags = append(gcflags, fuzzInstrumentFlags()...) } // Add -c=N to use concurrent backend compilation, if possible. - if c := gcBackendConcurrency(gcflags); c > 1 { + c, release := compilerConcurrency() + defer release() + if c > 1 { defaultGcFlags = append(defaultGcFlags, fmt.Sprintf("-c=%d", c)) } @@ -177,31 +179,9 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg return ofile, output, err } -// gcBackendConcurrency returns the backend compiler concurrency level for a package compilation. -func gcBackendConcurrency(gcflags []string) int { - // First, check whether we can use -c at all for this compilation. - canDashC := concurrentGCBackendCompilationEnabledByDefault - - switch e := os.Getenv("GO19CONCURRENTCOMPILATION"); e { - case "0": - canDashC = false - case "1": - canDashC = true - case "": - // Not set. Use default. - default: - log.Fatalf("GO19CONCURRENTCOMPILATION must be 0, 1, or unset, got %q", e) - } - - // TODO: Test and delete these conditions. - if cfg.ExperimentErr != nil || cfg.Experiment.FieldTrack || cfg.Experiment.PreemptibleLoops { - canDashC = false - } - - if !canDashC { - return 1 - } - +// compilerConcurrency returns the compiler concurrency level for a package compilation. +// The returned function must be called after the compile finishes. +func compilerConcurrency() (int, func()) { // Decide how many concurrent backend compilations to allow. // // If we allow too many, in theory we might end up with p concurrent processes, @@ -212,29 +192,60 @@ func gcBackendConcurrency(gcflags []string) int { // of the overall compiler execution, so c==1 for much of the build. // So don't worry too much about that interaction for now. // - // However, in practice, setting c above 4 tends not to help very much. - // See the analysis in CL 41192. + // But to keep things reasonable, we maintain a cap on the total number of + // concurrent backend compiles. (If we gave each compile action the full GOMAXPROCS, we could + // potentially have GOMAXPROCS^2 running compile goroutines) In the past, we'd limit + // the number of concurrent backend compiles per process to 4, which would result in a worst-case number + // of backend compiles of 4*cfg.BuildP. Because some compile processes benefit from having + // a larger number of compiles, especially when the compile action is the only + // action running, we'll allow the max value to be larger, but ensure that the + // total number of backend compiles never exceeds that previous worst-case number. + // This is implemented using a pool of tokens that are given out. We'll set aside enough + // tokens to make sure we don't run out, and then give half of the remaining tokens (up to + // GOMAXPROCS) to each compile action that requests it. // - // TODO(josharian): attempt to detect whether this particular compilation - // is likely to be a bottleneck, e.g. when: - // - it has no successor packages to compile (usually package main) - // - all paths through the build graph pass through it - // - critical path scheduling says it is high priority - // and in such a case, set c to runtime.GOMAXPROCS(0). - // By default this is the same as runtime.NumCPU. - // We do this now when p==1. - // To limit parallelism, set GOMAXPROCS below numCPU; this may be useful + // As a user, to limit parallelism, set GOMAXPROCS below numCPU; this may be useful // on a low-memory builder, or if a deterministic build order is required. - c := runtime.GOMAXPROCS(0) if cfg.BuildP == 1 { // No process parallelism, do not cap compiler parallelism. - return c + return maxCompilerConcurrency, func() {} } - // Some process parallelism. Set c to min(4, maxprocs). - if c > 4 { - c = 4 + + // Cap compiler parallelism using the pool. + tokensMu.Lock() + defer tokensMu.Unlock() + concurrentProcesses++ + // Set aside tokens so that we don't run out if we were running cfg.BuildP concurrent compiles. + // We'll set aside one token for each of the action goroutines that aren't currently running a compile. + setAside := cfg.BuildP - concurrentProcesses + availableTokens := tokens - setAside + // Grab half the remaining tokens: but with a floor of at least 1 token, and + // a ceiling of the max backend concurrency. + c := max(min(availableTokens/2, maxCompilerConcurrency), 1) + tokens -= c + // Successfully grabbed the tokens. + return c, func() { + tokensMu.Lock() + defer tokensMu.Unlock() + tokens += c } - return c +} + +var maxCompilerConcurrency = runtime.GOMAXPROCS(0) // max value we will use for -c + +var ( + tokensMu sync.Mutex + tokens int // number of available tokens + concurrentProcesses int // number of currently running compiles +) + +// initCompilerConcurrencyPool sets the number of tokens in the pool. It needs +// to be run after init, so that it can use the value of cfg.BuildP. +func initCompilerConcurrencyPool() { + // Size the pool so that the worst case total number of compiles is not more + // than what it was when we capped the concurrency to 4. + oldConcurrencyCap := min(4, maxCompilerConcurrency) + tokens = oldConcurrencyCap * cfg.BuildP } // trimpath returns the -trimpath argument to use diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go index a2954ab91ab..964d6e8363a 100644 --- a/src/cmd/go/internal/work/init.go +++ b/src/cmd/go/internal/work/init.go @@ -60,6 +60,7 @@ func BuildInit(loaderstate *modload.State) { modload.Init(loaderstate) instrumentInit() buildModeInit() + initCompilerConcurrencyPool() cfgChangedEnv = makeCfgChangedEnv() if err := fsys.Init(); err != nil { diff --git a/src/cmd/go/testdata/script/version_goexperiment.txt b/src/cmd/go/testdata/script/version_goexperiment.txt index 4b165eb6055..23fc57d456d 100644 --- a/src/cmd/go/testdata/script/version_goexperiment.txt +++ b/src/cmd/go/testdata/script/version_goexperiment.txt @@ -12,5 +12,13 @@ stderr 'X:fieldtrack$' -- version.go -- package main -import "runtime" -func main() { println(runtime.Version()) } +import ( + "go/version" + "runtime" +) +func main() { + if !version.IsValid(runtime.Version()) { + panic("version not valid: "+runtime.Version()) + } + println(runtime.Version()) +} diff --git a/src/cmd/internal/goobj/builtinlist.go b/src/cmd/internal/goobj/builtinlist.go index b3320808f11..918ade191dd 100644 --- a/src/cmd/internal/goobj/builtinlist.go +++ b/src/cmd/internal/goobj/builtinlist.go @@ -43,6 +43,7 @@ var builtins = [...]struct { {"runtime.printcomplex128", 1}, {"runtime.printcomplex64", 1}, {"runtime.printstring", 1}, + {"runtime.printquoted", 1}, {"runtime.printpointer", 1}, {"runtime.printuintptr", 1}, {"runtime.printiface", 1}, diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index 2eabe9bda8a..2a3ead55ea6 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -429,7 +429,6 @@ const ( AADD AADDD AADDF - AADDU AADDW AAND @@ -468,6 +467,7 @@ const ( ADIVF ADIVU ADIVW + ADIVWU ALL ALLV @@ -494,7 +494,6 @@ const ( AMUL AMULD AMULF - AMULU AMULH AMULHU AMULW @@ -508,7 +507,9 @@ const ( ANOR AOR AREM + AREMW AREMU + AREMWU ARFE @@ -528,7 +529,6 @@ const ( ASUBD ASUBF - ASUBU ASUBW ADBAR ASYSCALL @@ -1159,6 +1159,143 @@ const ( AXVMULWODVWUW AXVMULWODQVUV + AVADDWEVHB + AVADDWEVWH + AVADDWEVVW + AVADDWEVQV + AVSUBWEVHB + AVSUBWEVWH + AVSUBWEVVW + AVSUBWEVQV + AVADDWODHB + AVADDWODWH + AVADDWODVW + AVADDWODQV + AVSUBWODHB + AVSUBWODWH + AVSUBWODVW + AVSUBWODQV + AXVADDWEVHB + AXVADDWEVWH + AXVADDWEVVW + AXVADDWEVQV + AXVSUBWEVHB + AXVSUBWEVWH + AXVSUBWEVVW + AXVSUBWEVQV + AXVADDWODHB + AXVADDWODWH + AXVADDWODVW + AXVADDWODQV + AXVSUBWODHB + AXVSUBWODWH + AXVSUBWODVW + AXVSUBWODQV + AVADDWEVHBU + AVADDWEVWHU + AVADDWEVVWU + AVADDWEVQVU + AVSUBWEVHBU + AVSUBWEVWHU + AVSUBWEVVWU + AVSUBWEVQVU + AVADDWODHBU + AVADDWODWHU + AVADDWODVWU + AVADDWODQVU + AVSUBWODHBU + AVSUBWODWHU + AVSUBWODVWU + AVSUBWODQVU + AXVADDWEVHBU + AXVADDWEVWHU + AXVADDWEVVWU + AXVADDWEVQVU + AXVSUBWEVHBU + AXVSUBWEVWHU + AXVSUBWEVVWU + AXVSUBWEVQVU + AXVADDWODHBU + AXVADDWODWHU + AXVADDWODVWU + AXVADDWODQVU + AXVSUBWODHBU + AXVSUBWODWHU + AXVSUBWODVWU + AXVSUBWODQVU + + AVMADDB + AVMADDH + AVMADDW + AVMADDV + AVMSUBB + AVMSUBH + AVMSUBW + AVMSUBV + + AXVMADDB + AXVMADDH + AXVMADDW + AXVMADDV + AXVMSUBB + AXVMSUBH + AXVMSUBW + AXVMSUBV + + AVMADDWEVHB + AVMADDWEVWH + AVMADDWEVVW + AVMADDWEVQV + AVMADDWODHB + AVMADDWODWH + AVMADDWODVW + AVMADDWODQV + + AVMADDWEVHBU + AVMADDWEVWHU + AVMADDWEVVWU + AVMADDWEVQVU + AVMADDWODHBU + AVMADDWODWHU + AVMADDWODVWU + AVMADDWODQVU + + AVMADDWEVHBUB + AVMADDWEVWHUH + AVMADDWEVVWUW + AVMADDWEVQVUV + AVMADDWODHBUB + AVMADDWODWHUH + AVMADDWODVWUW + AVMADDWODQVUV + + AXVMADDWEVHB + AXVMADDWEVWH + AXVMADDWEVVW + AXVMADDWEVQV + AXVMADDWODHB + AXVMADDWODWH + AXVMADDWODVW + AXVMADDWODQV + + AXVMADDWEVHBU + AXVMADDWEVWHU + AXVMADDWEVVWU + AXVMADDWEVQVU + AXVMADDWODHBU + AXVMADDWODWHU + AXVMADDWODVWU + AXVMADDWODQVU + + AXVMADDWEVHBUB + AXVMADDWEVWHUH + AXVMADDWEVVWUW + AXVMADDWEVQVUV + AXVMADDWODHBUB + AXVMADDWODWHUH + AXVMADDWODVWUW + AXVMADDWODQVUV + AVSHUF4IB AVSHUF4IH AVSHUF4IW diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go index 92e3cab950f..4fe9a35b276 100644 --- a/src/cmd/internal/obj/loong64/anames.go +++ b/src/cmd/internal/obj/loong64/anames.go @@ -10,7 +10,6 @@ var Anames = []string{ "ADD", "ADDD", "ADDF", - "ADDU", "ADDW", "AND", "BEQ", @@ -43,6 +42,7 @@ var Anames = []string{ "DIVF", "DIVU", "DIVW", + "DIVWU", "LL", "LLV", "LUI", @@ -62,7 +62,6 @@ var Anames = []string{ "MUL", "MULD", "MULF", - "MULU", "MULH", "MULHU", "MULW", @@ -74,7 +73,9 @@ var Anames = []string{ "NOR", "OR", "REM", + "REMW", "REMU", + "REMWU", "RFE", "SC", "SCV", @@ -89,7 +90,6 @@ var Anames = []string{ "SUB", "SUBD", "SUBF", - "SUBU", "SUBW", "DBAR", "SYSCALL", @@ -628,6 +628,134 @@ var Anames = []string{ "XVMULWODWHUH", "XVMULWODVWUW", "XVMULWODQVUV", + "VADDWEVHB", + "VADDWEVWH", + "VADDWEVVW", + "VADDWEVQV", + "VSUBWEVHB", + "VSUBWEVWH", + "VSUBWEVVW", + "VSUBWEVQV", + "VADDWODHB", + "VADDWODWH", + "VADDWODVW", + "VADDWODQV", + "VSUBWODHB", + "VSUBWODWH", + "VSUBWODVW", + "VSUBWODQV", + "XVADDWEVHB", + "XVADDWEVWH", + "XVADDWEVVW", + "XVADDWEVQV", + "XVSUBWEVHB", + "XVSUBWEVWH", + "XVSUBWEVVW", + "XVSUBWEVQV", + "XVADDWODHB", + "XVADDWODWH", + "XVADDWODVW", + "XVADDWODQV", + "XVSUBWODHB", + "XVSUBWODWH", + "XVSUBWODVW", + "XVSUBWODQV", + "VADDWEVHBU", + "VADDWEVWHU", + "VADDWEVVWU", + "VADDWEVQVU", + "VSUBWEVHBU", + "VSUBWEVWHU", + "VSUBWEVVWU", + "VSUBWEVQVU", + "VADDWODHBU", + "VADDWODWHU", + "VADDWODVWU", + "VADDWODQVU", + "VSUBWODHBU", + "VSUBWODWHU", + "VSUBWODVWU", + "VSUBWODQVU", + "XVADDWEVHBU", + "XVADDWEVWHU", + "XVADDWEVVWU", + "XVADDWEVQVU", + "XVSUBWEVHBU", + "XVSUBWEVWHU", + "XVSUBWEVVWU", + "XVSUBWEVQVU", + "XVADDWODHBU", + "XVADDWODWHU", + "XVADDWODVWU", + "XVADDWODQVU", + "XVSUBWODHBU", + "XVSUBWODWHU", + "XVSUBWODVWU", + "XVSUBWODQVU", + "VMADDB", + "VMADDH", + "VMADDW", + "VMADDV", + "VMSUBB", + "VMSUBH", + "VMSUBW", + "VMSUBV", + "XVMADDB", + "XVMADDH", + "XVMADDW", + "XVMADDV", + "XVMSUBB", + "XVMSUBH", + "XVMSUBW", + "XVMSUBV", + "VMADDWEVHB", + "VMADDWEVWH", + "VMADDWEVVW", + "VMADDWEVQV", + "VMADDWODHB", + "VMADDWODWH", + "VMADDWODVW", + "VMADDWODQV", + "VMADDWEVHBU", + "VMADDWEVWHU", + "VMADDWEVVWU", + "VMADDWEVQVU", + "VMADDWODHBU", + "VMADDWODWHU", + "VMADDWODVWU", + "VMADDWODQVU", + "VMADDWEVHBUB", + "VMADDWEVWHUH", + "VMADDWEVVWUW", + "VMADDWEVQVUV", + "VMADDWODHBUB", + "VMADDWODWHUH", + "VMADDWODVWUW", + "VMADDWODQVUV", + "XVMADDWEVHB", + "XVMADDWEVWH", + "XVMADDWEVVW", + "XVMADDWEVQV", + "XVMADDWODHB", + "XVMADDWODWH", + "XVMADDWODVW", + "XVMADDWODQV", + "XVMADDWEVHBU", + "XVMADDWEVWHU", + "XVMADDWEVVWU", + "XVMADDWEVQVU", + "XVMADDWODHBU", + "XVMADDWODWHU", + "XVMADDWODVWU", + "XVMADDWODQVU", + "XVMADDWEVHBUB", + "XVMADDWEVWHUH", + "XVMADDWEVVWUW", + "XVMADDWEVQVUV", + "XVMADDWODHBUB", + "XVMADDWODWHUH", + "XVMADDWODVWUW", + "XVMADDWODQVUV", "VSHUF4IB", "VSHUF4IH", "VSHUF4IW", diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 857ef31ca3a..f9925180153 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -1428,9 +1428,9 @@ func buildop(ctxt *obj.Link) { opset(AFTINTRNEVD, r0) case AADD: + opset(AADDW, r0) opset(ASGT, r0) opset(ASGTU, r0) - opset(AADDU, r0) case AADDV: opset(AADDVU, r0) @@ -1512,18 +1512,22 @@ func buildop(ctxt *obj.Link) { opset(ABSTRINSV, r0) case ASUB: - opset(ASUBU, r0) + opset(ASUBW, r0) opset(ANOR, r0) opset(ASUBV, r0) opset(ASUBVU, r0) opset(AMUL, r0) - opset(AMULU, r0) + opset(AMULW, r0) opset(AMULH, r0) opset(AMULHU, r0) opset(AREM, r0) + opset(AREMW, r0) opset(AREMU, r0) + opset(AREMWU, r0) opset(ADIV, r0) + opset(ADIVW, r0) opset(ADIVU, r0) + opset(ADIVWU, r0) opset(AMULV, r0) opset(AMULVU, r0) opset(AMULHV, r0) @@ -1791,6 +1795,70 @@ func buildop(ctxt *obj.Link) { opset(AVSLTHU, r0) opset(AVSLTWU, r0) opset(AVSLTVU, r0) + opset(AVADDWEVHB, r0) + opset(AVADDWEVWH, r0) + opset(AVADDWEVVW, r0) + opset(AVADDWEVQV, r0) + opset(AVSUBWEVHB, r0) + opset(AVSUBWEVWH, r0) + opset(AVSUBWEVVW, r0) + opset(AVSUBWEVQV, r0) + opset(AVADDWODHB, r0) + opset(AVADDWODWH, r0) + opset(AVADDWODVW, r0) + opset(AVADDWODQV, r0) + opset(AVSUBWODHB, r0) + opset(AVSUBWODWH, r0) + opset(AVSUBWODVW, r0) + opset(AVSUBWODQV, r0) + opset(AVADDWEVHBU, r0) + opset(AVADDWEVWHU, r0) + opset(AVADDWEVVWU, r0) + opset(AVADDWEVQVU, r0) + opset(AVSUBWEVHBU, r0) + opset(AVSUBWEVWHU, r0) + opset(AVSUBWEVVWU, r0) + opset(AVSUBWEVQVU, r0) + opset(AVADDWODHBU, r0) + opset(AVADDWODWHU, r0) + opset(AVADDWODVWU, r0) + opset(AVADDWODQVU, r0) + opset(AVSUBWODHBU, r0) + opset(AVSUBWODWHU, r0) + opset(AVSUBWODVWU, r0) + opset(AVSUBWODQVU, r0) + opset(AVMADDB, r0) + opset(AVMADDH, r0) + opset(AVMADDW, r0) + opset(AVMADDV, r0) + opset(AVMSUBB, r0) + opset(AVMSUBH, r0) + opset(AVMSUBW, r0) + opset(AVMSUBV, r0) + opset(AVMADDWEVHB, r0) + opset(AVMADDWEVWH, r0) + opset(AVMADDWEVVW, r0) + opset(AVMADDWEVQV, r0) + opset(AVMADDWODHB, r0) + opset(AVMADDWODWH, r0) + opset(AVMADDWODVW, r0) + opset(AVMADDWODQV, r0) + opset(AVMADDWEVHBU, r0) + opset(AVMADDWEVWHU, r0) + opset(AVMADDWEVVWU, r0) + opset(AVMADDWEVQVU, r0) + opset(AVMADDWODHBU, r0) + opset(AVMADDWODWHU, r0) + opset(AVMADDWODVWU, r0) + opset(AVMADDWODQVU, r0) + opset(AVMADDWEVHBUB, r0) + opset(AVMADDWEVWHUH, r0) + opset(AVMADDWEVVWUW, r0) + opset(AVMADDWEVQVUV, r0) + opset(AVMADDWODHBUB, r0) + opset(AVMADDWODWHUH, r0) + opset(AVMADDWODVWUW, r0) + opset(AVMADDWODQVUV, r0) case AXVSLTB: opset(AXVSLTH, r0) @@ -1800,6 +1868,70 @@ func buildop(ctxt *obj.Link) { opset(AXVSLTHU, r0) opset(AXVSLTWU, r0) opset(AXVSLTVU, r0) + opset(AXVADDWEVHB, r0) + opset(AXVADDWEVWH, r0) + opset(AXVADDWEVVW, r0) + opset(AXVADDWEVQV, r0) + opset(AXVSUBWEVHB, r0) + opset(AXVSUBWEVWH, r0) + opset(AXVSUBWEVVW, r0) + opset(AXVSUBWEVQV, r0) + opset(AXVADDWODHB, r0) + opset(AXVADDWODWH, r0) + opset(AXVADDWODVW, r0) + opset(AXVADDWODQV, r0) + opset(AXVSUBWODHB, r0) + opset(AXVSUBWODWH, r0) + opset(AXVSUBWODVW, r0) + opset(AXVSUBWODQV, r0) + opset(AXVADDWEVHBU, r0) + opset(AXVADDWEVWHU, r0) + opset(AXVADDWEVVWU, r0) + opset(AXVADDWEVQVU, r0) + opset(AXVSUBWEVHBU, r0) + opset(AXVSUBWEVWHU, r0) + opset(AXVSUBWEVVWU, r0) + opset(AXVSUBWEVQVU, r0) + opset(AXVADDWODHBU, r0) + opset(AXVADDWODWHU, r0) + opset(AXVADDWODVWU, r0) + opset(AXVADDWODQVU, r0) + opset(AXVSUBWODHBU, r0) + opset(AXVSUBWODWHU, r0) + opset(AXVSUBWODVWU, r0) + opset(AXVSUBWODQVU, r0) + opset(AXVMADDB, r0) + opset(AXVMADDH, r0) + opset(AXVMADDW, r0) + opset(AXVMADDV, r0) + opset(AXVMSUBB, r0) + opset(AXVMSUBH, r0) + opset(AXVMSUBW, r0) + opset(AXVMSUBV, r0) + opset(AXVMADDWEVHB, r0) + opset(AXVMADDWEVWH, r0) + opset(AXVMADDWEVVW, r0) + opset(AXVMADDWEVQV, r0) + opset(AXVMADDWODHB, r0) + opset(AXVMADDWODWH, r0) + opset(AXVMADDWODVW, r0) + opset(AXVMADDWODQV, r0) + opset(AXVMADDWEVHBU, r0) + opset(AXVMADDWEVWHU, r0) + opset(AXVMADDWEVVWU, r0) + opset(AXVMADDWEVQVU, r0) + opset(AXVMADDWODHBU, r0) + opset(AXVMADDWODWHU, r0) + opset(AXVMADDWODVWU, r0) + opset(AXVMADDWODQVU, r0) + opset(AXVMADDWEVHBUB, r0) + opset(AXVMADDWEVWHUH, r0) + opset(AXVMADDWEVVWUW, r0) + opset(AXVMADDWEVQVUV, r0) + opset(AXVMADDWODHBUB, r0) + opset(AXVMADDWODWHUH, r0) + opset(AXVMADDWODVWUW, r0) + opset(AXVMADDWODQVUV, r0) case AVANDB: opset(AVORB, r0) @@ -2161,8 +2293,7 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { o5 := uint32(0) o6 := uint32(0) - add := AADDU - add = AADDVU + add := AADDVU switch o.type_ { default: @@ -2293,7 +2424,7 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { v := c.regoff(&p.From) a := AOR if v < 0 { - a = AADDU + a = AADD } o1 = OP_12IRR(c.opirr(a), uint32(v), uint32(0), uint32(REGTMP)) r := int(p.Reg) @@ -2306,6 +2437,9 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { v := int32(0) if p.To.Target() != nil { v = int32(p.To.Target().Pc-p.Pc) >> 2 + if v < -1<<25 || v >= 1<<25 { + c.ctxt.Diag("branch too far \n%v", p) + } } o1 = OP_B_BL(c.opirr(p.As), uint32(v)) if p.To.Sym != nil { @@ -2552,7 +2686,7 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { case 34: // mov $con,fr v := c.regoff(&p.From) - a := AADDU + a := AADD if v > 0 { a = AOR } @@ -3180,9 +3314,7 @@ func (c *ctxt0) oprrrr(a obj.As) uint32 { func (c *ctxt0) oprrr(a obj.As) uint32 { switch a { - case AADD: - return 0x20 << 15 - case AADDU: + case AADD, AADDW: return 0x20 << 15 case ASGT: return 0x24 << 15 // SLT @@ -3202,9 +3334,7 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { return 0x2c << 15 // orn case AANDN: return 0x2d << 15 // andn - case ASUB: - return 0x22 << 15 - case ASUBU, ANEGW: + case ASUB, ASUBW, ANEGW: return 0x22 << 15 case ANOR: return 0x28 << 15 @@ -3233,9 +3363,7 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { case ASUBVU, ANEGV: return 0x23 << 15 - case AMUL: - return 0x38 << 15 // mul.w - case AMULU: + case AMUL, AMULW: return 0x38 << 15 // mul.w case AMULH: return 0x39 << 15 // mulh.w @@ -3253,17 +3381,17 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { return 0x3e << 15 // mulw.d.w case AMULWVWU: return 0x3f << 15 // mulw.d.wu - case ADIV: + case ADIV, ADIVW: return 0x40 << 15 // div.w - case ADIVU: + case ADIVU, ADIVWU: return 0x42 << 15 // div.wu case ADIVV: return 0x44 << 15 // div.d case ADIVVU: return 0x46 << 15 // div.du - case AREM: + case AREM, AREMW: return 0x41 << 15 // mod.w - case AREMU: + case AREMU, AREMWU: return 0x43 << 15 // mod.wu case AREMV: return 0x45 << 15 // mod.d @@ -3612,6 +3740,262 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { return 0xe946 << 15 // xvmulwod.d.wu.w case AXVMULWODQVUV: return 0xe947 << 15 // xvmulwod.q.du.d + case AVADDWEVHB: + return 0x0E03C << 15 // vaddwev.h.b + case AVADDWEVWH: + return 0x0E03D << 15 // vaddwev.w.h + case AVADDWEVVW: + return 0x0E03E << 15 // vaddwev.d.w + case AVADDWEVQV: + return 0x0E03F << 15 // vaddwev.q.d + case AVSUBWEVHB: + return 0x0E040 << 15 // vsubwev.h.b + case AVSUBWEVWH: + return 0x0E041 << 15 // vsubwev.w.h + case AVSUBWEVVW: + return 0x0E042 << 15 // vsubwev.d.w + case AVSUBWEVQV: + return 0x0E043 << 15 // vsubwev.q.d + case AVADDWODHB: + return 0x0E044 << 15 // vaddwod.h.b + case AVADDWODWH: + return 0x0E045 << 15 // vaddwod.w.h + case AVADDWODVW: + return 0x0E046 << 15 // vaddwod.d.w + case AVADDWODQV: + return 0x0E047 << 15 // vaddwod.q.d + case AVSUBWODHB: + return 0x0E048 << 15 // vsubwod.h.b + case AVSUBWODWH: + return 0x0E049 << 15 // vsubwod.w.h + case AVSUBWODVW: + return 0x0E04A << 15 // vsubwod.d.w + case AVSUBWODQV: + return 0x0E04B << 15 // vsubwod.q.d + case AXVADDWEVHB: + return 0x0E83C << 15 // xvaddwev.h.b + case AXVADDWEVWH: + return 0x0E83D << 15 // xvaddwev.w.h + case AXVADDWEVVW: + return 0x0E83E << 15 // xvaddwev.d.w + case AXVADDWEVQV: + return 0x0E83F << 15 // xvaddwev.q.d + case AXVSUBWEVHB: + return 0x0E840 << 15 // xvsubwev.h.b + case AXVSUBWEVWH: + return 0x0E841 << 15 // xvsubwev.w.h + case AXVSUBWEVVW: + return 0x0E842 << 15 // xvsubwev.d.w + case AXVSUBWEVQV: + return 0x0E843 << 15 // xvsubwev.q.d + case AXVADDWODHB: + return 0x0E844 << 15 // xvaddwod.h.b + case AXVADDWODWH: + return 0x0E845 << 15 // xvaddwod.w.h + case AXVADDWODVW: + return 0x0E846 << 15 // xvaddwod.d.w + case AXVADDWODQV: + return 0x0E847 << 15 // xvaddwod.q.d + case AXVSUBWODHB: + return 0x0E848 << 15 // xvsubwod.h.b + case AXVSUBWODWH: + return 0x0E849 << 15 // xvsubwod.w.h + case AXVSUBWODVW: + return 0x0E84A << 15 // xvsubwod.d.w + case AXVSUBWODQV: + return 0x0E84B << 15 // xvsubwod.q.d + case AVADDWEVHBU: + return 0x0E05C << 15 // vaddwev.h.bu + case AVADDWEVWHU: + return 0x0E05E << 15 // vaddwev.w.hu + case AVADDWEVVWU: + return 0x0E05E << 15 // vaddwev.d.wu + case AVADDWEVQVU: + return 0x0E05F << 15 // vaddwev.q.du + case AVSUBWEVHBU: + return 0x0E060 << 15 // vsubwev.h.bu + case AVSUBWEVWHU: + return 0x0E061 << 15 // vsubwev.w.hu + case AVSUBWEVVWU: + return 0x0E062 << 15 // vsubwev.d.wu + case AVSUBWEVQVU: + return 0x0E063 << 15 // vsubwev.q.du + case AVADDWODHBU: + return 0x0E064 << 15 // vaddwod.h.bu + case AVADDWODWHU: + return 0x0E065 << 15 // vaddwod.w.hu + case AVADDWODVWU: + return 0x0E066 << 15 // vaddwod.d.wu + case AVADDWODQVU: + return 0x0E067 << 15 // vaddwod.q.du + case AVSUBWODHBU: + return 0x0E068 << 15 // vsubwod.h.bu + case AVSUBWODWHU: + return 0x0E069 << 15 // vsubwod.w.hu + case AVSUBWODVWU: + return 0x0E06A << 15 // vsubwod.d.wu + case AVSUBWODQVU: + return 0x0E06B << 15 // vsubwod.q.du + case AXVADDWEVHBU: + return 0x0E85C << 15 // xvaddwev.h.bu + case AXVADDWEVWHU: + return 0x0E85D << 15 // xvaddwev.w.hu + case AXVADDWEVVWU: + return 0x0E85E << 15 // xvaddwev.d.wu + case AXVADDWEVQVU: + return 0x0E85F << 15 // xvaddwev.q.du + case AXVSUBWEVHBU: + return 0x0E860 << 15 // xvsubwev.h.bu + case AXVSUBWEVWHU: + return 0x0E861 << 15 // xvsubwev.w.hu + case AXVSUBWEVVWU: + return 0x0E862 << 15 // xvsubwev.d.wu + case AXVSUBWEVQVU: + return 0x0E863 << 15 // xvsubwev.q.du + case AXVADDWODHBU: + return 0x0E864 << 15 // xvaddwod.h.bu + case AXVADDWODWHU: + return 0x0E865 << 15 // xvaddwod.w.hu + case AXVADDWODVWU: + return 0x0E866 << 15 // xvaddwod.d.wu + case AXVADDWODQVU: + return 0x0E867 << 15 // xvaddwod.q.du + case AXVSUBWODHBU: + return 0x0E868 << 15 // xvsubwod.h.bu + case AXVSUBWODWHU: + return 0x0E869 << 15 // xvsubwod.w.hu + case AXVSUBWODVWU: + return 0x0E86A << 15 // xvsubwod.d.wu + case AXVSUBWODQVU: + return 0x0E86B << 15 // xvsubwod.q.du + case AVMADDB: + return 0x0E150 << 15 // vmadd.b + case AVMADDH: + return 0x0E151 << 15 // vmadd.h + case AVMADDW: + return 0x0E152 << 15 // vmadd.w + case AVMADDV: + return 0x0E153 << 15 // vmadd.d + case AVMSUBB: + return 0x0E154 << 15 // vmsub.b + case AVMSUBH: + return 0x0E155 << 15 // vmsub.h + case AVMSUBW: + return 0x0E156 << 15 // vmsub.w + case AVMSUBV: + return 0x0E157 << 15 // vmsub.d + case AXVMADDB: + return 0x0E950 << 15 // xvmadd.b + case AXVMADDH: + return 0x0E951 << 15 // xvmadd.h + case AXVMADDW: + return 0x0E952 << 15 // xvmadd.w + case AXVMADDV: + return 0x0E953 << 15 // xvmadd.d + case AXVMSUBB: + return 0x0E954 << 15 // xvmsub.b + case AXVMSUBH: + return 0x0E955 << 15 // xvmsub.h + case AXVMSUBW: + return 0x0E956 << 15 // xvmsub.w + case AXVMSUBV: + return 0x0E957 << 15 // xvmsub.d + case AVMADDWEVHB: + return 0x0E158 << 15 // vmaddwev.h.b + case AVMADDWEVWH: + return 0x0E159 << 15 // vmaddwev.w.h + case AVMADDWEVVW: + return 0x0E15A << 15 // vmaddwev.d.w + case AVMADDWEVQV: + return 0x0E15B << 15 // vmaddwev.q.d + case AVMADDWODHB: + return 0x0E15C << 15 // vmaddwov.h.b + case AVMADDWODWH: + return 0x0E15D << 15 // vmaddwod.w.h + case AVMADDWODVW: + return 0x0E15E << 15 // vmaddwod.d.w + case AVMADDWODQV: + return 0x0E15F << 15 // vmaddwod.q.d + case AVMADDWEVHBU: + return 0x0E168 << 15 // vmaddwev.h.bu + case AVMADDWEVWHU: + return 0x0E169 << 15 // vmaddwev.w.hu + case AVMADDWEVVWU: + return 0x0E16A << 15 // vmaddwev.d.wu + case AVMADDWEVQVU: + return 0x0E16B << 15 // vmaddwev.q.du + case AVMADDWODHBU: + return 0x0E16C << 15 // vmaddwov.h.bu + case AVMADDWODWHU: + return 0x0E16D << 15 // vmaddwod.w.hu + case AVMADDWODVWU: + return 0x0E16E << 15 // vmaddwod.d.wu + case AVMADDWODQVU: + return 0x0E16F << 15 // vmaddwod.q.du + case AVMADDWEVHBUB: + return 0x0E178 << 15 // vmaddwev.h.bu.b + case AVMADDWEVWHUH: + return 0x0E179 << 15 // vmaddwev.w.hu.h + case AVMADDWEVVWUW: + return 0x0E17A << 15 // vmaddwev.d.wu.w + case AVMADDWEVQVUV: + return 0x0E17B << 15 // vmaddwev.q.du.d + case AVMADDWODHBUB: + return 0x0E17C << 15 // vmaddwov.h.bu.b + case AVMADDWODWHUH: + return 0x0E17D << 15 // vmaddwod.w.hu.h + case AVMADDWODVWUW: + return 0x0E17E << 15 // vmaddwod.d.wu.w + case AVMADDWODQVUV: + return 0x0E17F << 15 // vmaddwod.q.du.d + case AXVMADDWEVHB: + return 0x0E958 << 15 // xvmaddwev.h.b + case AXVMADDWEVWH: + return 0x0E959 << 15 // xvmaddwev.w.h + case AXVMADDWEVVW: + return 0x0E95A << 15 // xvmaddwev.d.w + case AXVMADDWEVQV: + return 0x0E95B << 15 // xvmaddwev.q.d + case AXVMADDWODHB: + return 0x0E95C << 15 // xvmaddwov.h.b + case AXVMADDWODWH: + return 0x0E95D << 15 // xvmaddwod.w.h + case AXVMADDWODVW: + return 0x0E95E << 15 // xvmaddwod.d.w + case AXVMADDWODQV: + return 0x0E95F << 15 // xvmaddwod.q.d + case AXVMADDWEVHBU: + return 0x0E968 << 15 // xvmaddwev.h.bu + case AXVMADDWEVWHU: + return 0x0E969 << 15 // xvmaddwev.w.hu + case AXVMADDWEVVWU: + return 0x0E96A << 15 // xvmaddwev.d.wu + case AXVMADDWEVQVU: + return 0x0E96B << 15 // xvmaddwev.q.du + case AXVMADDWODHBU: + return 0x0E96C << 15 // xvmaddwov.h.bu + case AXVMADDWODWHU: + return 0x0E96D << 15 // xvmaddwod.w.hu + case AXVMADDWODVWU: + return 0x0E96E << 15 // xvmaddwod.d.wu + case AXVMADDWODQVU: + return 0x0E96F << 15 // xvmaddwod.q.du + case AXVMADDWEVHBUB: + return 0x0E978 << 15 // xvmaddwev.h.bu.b + case AXVMADDWEVWHUH: + return 0x0E979 << 15 // xvmaddwev.w.hu.h + case AXVMADDWEVVWUW: + return 0x0E97A << 15 // xvmaddwev.d.wu.w + case AXVMADDWEVQVUV: + return 0x0E97B << 15 // xvmaddwev.q.du.d + case AXVMADDWODHBUB: + return 0x0E97C << 15 // xvmaddwov.h.bu.b + case AXVMADDWODWHUH: + return 0x0E97D << 15 // xvmaddwod.w.hu.h + case AXVMADDWODVWUW: + return 0x0E97E << 15 // xvmaddwod.d.wu.w + case AXVMADDWODQVUV: + return 0x0E97F << 15 // xvmaddwod.q.du.d case AVSLLB: return 0xe1d0 << 15 // vsll.b case AVSLLH: @@ -4293,7 +4677,7 @@ func (c *ctxt0) opir(a obj.As) uint32 { func (c *ctxt0) opirr(a obj.As) uint32 { switch a { - case AADD, AADDU: + case AADD, AADDW: return 0x00a << 22 case ASGT: return 0x008 << 22 diff --git a/src/cmd/internal/obj/loong64/obj.go b/src/cmd/internal/obj/loong64/obj.go index a97217d3165..51a28d130c6 100644 --- a/src/cmd/internal/obj/loong64/obj.go +++ b/src/cmd/internal/obj/loong64/obj.go @@ -64,12 +64,6 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { p.As = AADD } - case ASUBU: - if p.From.Type == obj.TYPE_CONST { - p.From.Offset = -p.From.Offset - p.As = AADDU - } - case ASUBV: if p.From.Type == obj.TYPE_CONST { p.From.Offset = -p.From.Offset @@ -453,7 +447,6 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q.Link = q1 case AADD, - AADDU, AADDV, AADDVU: if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.From.Type == obj.TYPE_CONST { diff --git a/src/cmd/internal/obj/riscv/doc.go b/src/cmd/internal/obj/riscv/doc.go index 365bedd2999..898a45b0e6f 100644 --- a/src/cmd/internal/obj/riscv/doc.go +++ b/src/cmd/internal/obj/riscv/doc.go @@ -289,6 +289,33 @@ the constant literal is 0.0, MOVF and MOVD will be encoded as FLW and FLD instructions that load the constant from a location within the program's binary. +# Compressed instructions + +The Go assembler converts 32 bit RISC-V instructions to compressed +instructions when generating machine code. This conversion happens +automatically without the need for any direct involvement from the programmer, +although judicious choice of registers can improve the compression rate for +certain instructions (see the [RISC-V ISA Manual] for more details). This +behaviour is enabled by default for all of the supported RISC-V profiles, i.e., +it is not affected by the value of the GORISCV64 environment variable. + +The use of compressed instructions can be disabled via a debug flag, +compressinstructions: + + - Use -gcflags=all=-d=compressinstructions=0 to disable compressed + instructions in Go code. + - Use -asmflags=all=-d=compressinstructions=0 to disable compressed + instructions in assembly code. + +To completely disable automatic instruction compression in a Go binary both +options must be specified. + +The assembler also permits the use of compressed instructions in hand coded +assembly language, but this should generally be avoided. Note that the +compressinstructions flag only prevents the automatic compression of 32 +bit instructions. It has no effect on compressed instructions that are +hand coded directly into an assembly file. + [RISC-V ISA Manual]: https://github.com/riscv/riscv-isa-manual [rva20u64]: https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#51-rva20u64-profile [rva22u64]: https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#rva22u64-profile diff --git a/src/cmd/link/cgo_test.go b/src/cmd/link/cgo_test.go index 52db70e1ad8..a684edd5f3b 100644 --- a/src/cmd/link/cgo_test.go +++ b/src/cmd/link/cgo_test.go @@ -115,10 +115,10 @@ func testCGOLTO(t *testing.T, cc, cgoCflags string, test int) { t.Fatalf("bad case %d", test) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build") + cmd := goCmd(t, "build") cmd.Dir = dir cgoCflags += " -flto" - cmd.Env = append(cmd.Environ(), "CGO_CFLAGS="+cgoCflags) + cmd.Env = append(cmd.Env, "CGO_CFLAGS="+cgoCflags) t.Logf("CGO_CFLAGS=%q %v", cgoCflags, cmd) out, err := cmd.CombinedOutput() diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index 56a076002a9..d9916ae0799 100644 --- a/src/cmd/link/dwarf_test.go +++ b/src/cmd/link/dwarf_test.go @@ -21,56 +21,6 @@ import ( "testing" ) -// TestMain allows this test binary to run as a -toolexec wrapper for -// the 'go' command. If LINK_TEST_TOOLEXEC is set, TestMain runs the -// binary as if it were cmd/link, and otherwise runs the requested -// tool as a subprocess. -// -// This allows the test to verify the behavior of the current contents of the -// cmd/link package even if the installed cmd/link binary is stale. -func TestMain(m *testing.M) { - // Are we running as a toolexec wrapper? If so then run either - // the correct tool or this executable itself (for the linker). - // Running as toolexec wrapper. - if os.Getenv("LINK_TEST_TOOLEXEC") != "" { - if strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe") == "link" { - // Running as a -toolexec linker, and the tool is cmd/link. - // Substitute this test binary for the linker. - os.Args = os.Args[1:] - main() - os.Exit(0) - } - // Running some other tool. - cmd := exec.Command(os.Args[1], os.Args[2:]...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - os.Exit(1) - } - os.Exit(0) - } - - // Are we being asked to run as the linker (without toolexec)? - // If so then kick off main. - if os.Getenv("LINK_TEST_EXEC_LINKER") != "" { - main() - os.Exit(0) - } - - if testExe, err := os.Executable(); err == nil { - // on wasm, some phones, we expect an error from os.Executable() - testLinker = testExe - } - - // Not running as a -toolexec wrapper or as a linker executable. - // Just run the tests. - os.Exit(m.Run()) -} - -// Path of the test executable being run. -var testLinker string - func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) { testenv.MustHaveCGO(t) testenv.MustHaveGoBuild(t) @@ -106,14 +56,13 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) exe := filepath.Join(tmpDir, prog+".exe") dir := "../../runtime/testdata/" + prog - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-o", exe) + cmd := goCmd(t, "build", "-o", exe) if buildmode != "" { cmd.Args = append(cmd.Args, "-buildmode", buildmode) } cmd.Args = append(cmd.Args, dir) - cmd.Env = append(os.Environ(), env...) + cmd.Env = append(cmd.Env, env...) cmd.Env = append(cmd.Env, "CGO_CFLAGS=") // ensure CGO_CFLAGS does not contain any flags. Issue #35459 - cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) @@ -282,9 +231,8 @@ func TestDWARFLocationList(t *testing.T) { exe := filepath.Join(tmpDir, "issue65405.exe") dir := "./testdata/dwarf/issue65405" - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-gcflags=all=-N -l", "-o", exe, dir) - cmd.Env = append(os.Environ(), "CGO_CFLAGS=") - cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + cmd := goCmd(t, "build", "-gcflags=all=-N -l", "-o", exe, dir) + cmd.Env = append(cmd.Env, "CGO_CFLAGS=") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) @@ -402,7 +350,7 @@ func TestFlagW(t *testing.T) { t.Run(name, func(t *testing.T) { ldflags := "-ldflags=" + test.flag exe := filepath.Join(t.TempDir(), "a.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", ldflags, "-o", exe, src) + cmd := goCmd(t, "build", ldflags, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build failed: %v\n%s", err, out) diff --git a/src/cmd/link/elf_test.go b/src/cmd/link/elf_test.go index dc52c091f65..adf255a7e3d 100644 --- a/src/cmd/link/elf_test.go +++ b/src/cmd/link/elf_test.go @@ -11,6 +11,7 @@ import ( "cmd/internal/hash" "cmd/link/internal/ld" "debug/elf" + "encoding/binary" "fmt" "internal/platform" "internal/testenv" @@ -22,6 +23,7 @@ import ( "sync" "testing" "text/template" + "unsafe" ) func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) { @@ -80,7 +82,8 @@ func TestSectionsWithSameName(t *testing.T) { dir := t.TempDir() gopath := filepath.Join(dir, "GOPATH") - env := append(os.Environ(), "GOPATH="+gopath) + gopathEnv := "GOPATH=" + gopath + env := append(os.Environ(), gopathEnv) if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil { t.Fatal(err) @@ -91,7 +94,6 @@ func TestSectionsWithSameName(t *testing.T) { t.Fatal(err) } - goTool := testenv.GoToolPath(t) cc, cflags := getCCAndCCFLAGS(t, env) asmObj := filepath.Join(dir, "x.o") @@ -119,10 +121,10 @@ func TestSectionsWithSameName(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, goTool, "build") + cmd := goCmd(t, "build") cmd.Dir = dir - cmd.Env = env - t.Logf("%s build", goTool) + cmd.Env = append(cmd.Env, gopathEnv) + t.Logf("%s build", testenv.GoToolPath(t)) if out, err := cmd.CombinedOutput(); err != nil { t.Logf("%s", out) t.Fatal(err) @@ -150,13 +152,13 @@ func TestMinusRSymsWithSameName(t *testing.T) { dir := t.TempDir() gopath := filepath.Join(dir, "GOPATH") - env := append(os.Environ(), "GOPATH="+gopath) + gopathEnv := "GOPATH=" + gopath + env := append(os.Environ(), gopathEnv) if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil { t.Fatal(err) } - goTool := testenv.GoToolPath(t) cc, cflags := getCCAndCCFLAGS(t, env) objs := []string{} @@ -198,10 +200,10 @@ func TestMinusRSymsWithSameName(t *testing.T) { t.Fatal(err) } - t.Logf("%s build", goTool) - cmd := testenv.Command(t, goTool, "build") + t.Logf("%s build", testenv.GoToolPath(t)) + cmd := goCmd(t, "build") cmd.Dir = dir - cmd.Env = env + cmd.Env = append(cmd.Env, gopathEnv) if out, err := cmd.CombinedOutput(); err != nil { t.Logf("%s", out) t.Fatal(err) @@ -243,7 +245,7 @@ func TestGNUBuildID(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { exe := filepath.Join(tmpdir, test.name) - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-buildid="+gobuildid+" "+test.ldflags, "-o", exe, goFile) + cmd := goCmd(t, "build", "-ldflags=-buildid="+gobuildid+" "+test.ldflags, "-o", exe, goFile) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) } @@ -277,11 +279,9 @@ func TestMergeNoteSections(t *testing.T) { t.Fatal(err) } outFile := filepath.Join(t.TempDir(), "notes.exe") - goTool := testenv.GoToolPath(t) // sha1sum of "gopher" id := "0xf4e8cd51ce8bae2996dc3b74639cdeaa1f7fee5f" - cmd := testenv.Command(t, goTool, "build", "-o", outFile, "-ldflags", - "-B "+id, goFile) + cmd := goCmd(t, "build", "-o", outFile, "-ldflags", "-B "+id, goFile) cmd.Dir = t.TempDir() if out, err := cmd.CombinedOutput(); err != nil { t.Logf("%s", out) @@ -383,7 +383,7 @@ func TestPIESize(t *testing.T) { binpie += linkmode build := func(bin, mode string) error { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", bin, "-buildmode="+mode, "-ldflags=-linkmode="+linkmode) + cmd := goCmd(t, "build", "-o", bin, "-buildmode="+mode, "-ldflags=-linkmode="+linkmode) cmd.Args = append(cmd.Args, "pie.go") cmd.Dir = dir t.Logf("%v", cmd.Args) @@ -532,8 +532,7 @@ func TestIssue51939(t *testing.T) { t.Fatal(err) } outFile := filepath.Join(td, "issue51939.exe") - goTool := testenv.GoToolPath(t) - cmd := testenv.Command(t, goTool, "build", "-o", outFile, goFile) + cmd := goCmd(t, "build", "-o", outFile, goFile) if out, err := cmd.CombinedOutput(); err != nil { t.Logf("%s", out) t.Fatal(err) @@ -565,7 +564,7 @@ func TestFlagR(t *testing.T) { } exe := filepath.Join(tmpdir, "x.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-R=0x100000", "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags=-R=0x100000", "-o", exe, src) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("build failed: %v, output:\n%s", err, out) } @@ -610,7 +609,7 @@ func testFlagD(t *testing.T, dataAddr string, roundQuantum string, expectedAddr ldflags += " -R=" + roundQuantum } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+ldflags, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags="+ldflags, "-o", exe, src) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("build failed: %v, output:\n%s", err, out) } @@ -669,7 +668,7 @@ func testFlagDError(t *testing.T, dataAddr string, roundQuantum string, expected ldflags += " -R=" + roundQuantum } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+ldflags, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags="+ldflags, "-o", exe, src) out, err := cmd.CombinedOutput() if err == nil { t.Fatalf("expected build to fail with unaligned data address, but it succeeded") @@ -678,3 +677,129 @@ func testFlagDError(t *testing.T, dataAddr string, roundQuantum string, expected t.Errorf("expected error message to contain %q, got:\n%s", expectedError, out) } } + +func TestELFHeadersSorted(t *testing.T) { + for _, buildmode := range []string{"exe", "pie"} { + t.Run(buildmode, func(t *testing.T) { + testELFHeadersSorted(t, buildmode) + }) + } +} + +func testELFHeadersSorted(t *testing.T, buildmode string) { + testenv.MustHaveGoBuild(t) + + // We can only test this for internal linking mode. + // For external linking the external linker will + // decide how to sort the sections. + testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes) + if buildmode == "pie" { + testenv.MustInternalLinkPIE(t) + } + + t.Parallel() + + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(goSourceWithData), 0o444); err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "x.exe") + cmd := goCmd(t, "build", "-buildmode="+buildmode, "-ldflags=-linkmode=internal", "-o", exe, src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build failed: %v, output:\n%s", err, out) + } + + // Check that the first section header is all zeroes. + f, err := os.Open(exe) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + var ident [elf.EI_NIDENT]byte + if _, err := f.Read(ident[:]); err != nil { + t.Fatal(err) + } + + var bo binary.ByteOrder + switch elf.Data(ident[elf.EI_DATA]) { + case elf.ELFDATA2LSB: + bo = binary.LittleEndian + case elf.ELFDATA2MSB: + bo = binary.BigEndian + default: + t.Fatalf("unrecognized data encoding %d", ident[elf.EI_DATA]) + } + + var shoff int64 + var shsize int + switch elf.Class(ident[elf.EI_CLASS]) { + case elf.ELFCLASS32: + var hdr elf.Header32 + data := make([]byte, unsafe.Sizeof(hdr)) + if _, err := f.ReadAt(data, 0); err != nil { + t.Fatal(err) + } + shoff = int64(bo.Uint32(data[unsafe.Offsetof(hdr.Shoff):])) + shsize = int(unsafe.Sizeof(elf.Section32{})) + + case elf.ELFCLASS64: + var hdr elf.Header64 + data := make([]byte, unsafe.Sizeof(hdr)) + if _, err := f.ReadAt(data, 0); err != nil { + t.Fatal(err) + } + shoff = int64(bo.Uint64(data[unsafe.Offsetof(hdr.Shoff):])) + shsize = int(unsafe.Sizeof(elf.Section64{})) + + default: + t.Fatalf("unrecognized class %d", ident[elf.EI_CLASS]) + } + + if shoff > 0 { + data := make([]byte, shsize) + if _, err := f.ReadAt(data, shoff); err != nil { + t.Fatal(err) + } + for i, c := range data { + if c != 0 { + t.Errorf("section header 0 byte %d is %d, should be zero", i, c) + } + } + } + + ef, err := elf.NewFile(f) + if err != nil { + t.Fatal(err) + } + defer ef.Close() + + // After the first zero section header, + // we should see allocated sections, + // then unallocated sections. + // The allocated sections should be sorted by address. + i := 1 + lastAddr := uint64(0) + for i < len(ef.Sections) { + sec := ef.Sections[i] + if sec.Flags&elf.SHF_ALLOC == 0 { + break + } + if sec.Addr < lastAddr { + t.Errorf("section %d %q address %#x less than previous address %#x", i, sec.Name, sec.Addr, lastAddr) + } + lastAddr = sec.Addr + i++ + } + + firstUnalc := i + for i < len(ef.Sections) { + sec := ef.Sections[i] + if sec.Flags&elf.SHF_ALLOC != 0 { + t.Errorf("allocated section %d %q follows first unallocated section %d %q", i, sec.Name, firstUnalc, ef.Sections[firstUnalc].Name) + } + i++ + } +} diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index e7e202fc1f6..5b6dabb62b5 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1937,6 +1937,26 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { } ldr := ctxt.loader + // SMODULEDATA needs to be writable, but the GC doesn't need to + // look at it. We don't use allocateSingleSymSections because + // the name of the section is not the name of the symbol. + if len(state.data[sym.SMODULEDATA]) > 0 { + if len(state.data[sym.SMODULEDATA]) != 1 { + Errorf("internal error: more than one SMODULEDATA symbol") + } + s := state.data[sym.SMODULEDATA][0] + sect := addsection(ldr, ctxt.Arch, &Segdata, ".go.module", 06) + sect.Align = symalign(ldr, s) + state.datsize = Rnd(state.datsize, int64(sect.Align)) + sect.Vaddr = uint64(state.datsize) + ldr.SetSymSect(s, sect) + state.setSymType(s, sym.SDATA) + ldr.SetSymValue(s, int64(uint64(state.datsize)-sect.Vaddr)) + state.datsize += ldr.SymSize(s) + sect.Length = uint64(state.datsize) - sect.Vaddr + state.checkdatsize(sym.SMODULEDATA) + } + // writable .got (note that for PIE binaries .got goes in relro) if len(state.data[sym.SELFGOT]) > 0 { state.allocateNamedSectionAndAssignSyms(&Segdata, ".got", sym.SELFGOT, sym.SDATA, 06) @@ -2128,6 +2148,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pctab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.functab", 0), sect) + ldr.SetSymSect(ldr.LookupOrCreateSym("go:func.*", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect) setCarrierSize(sym.SPCLNTAB, int64(sect.Length)) if ctxt.HeadType == objabi.Haix { @@ -3066,6 +3087,7 @@ func (ctxt *Link) address() []*sym.Segment { ctxt.defineInternal("runtime.filetab", sym.SRODATA) ctxt.defineInternal("runtime.pctab", sym.SRODATA) ctxt.defineInternal("runtime.functab", sym.SRODATA) + ctxt.defineInternal("go:func.*", sym.SRODATA) ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length)) ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr)) ctxt.xdefine("runtime.enoptrdata", sym.SNOPTRDATAEND, int64(noptr.Vaddr+noptr.Length)) diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 9bab73e7b71..ff0fa5c377d 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -2394,28 +2394,6 @@ func (d *dwctxt) collectUnitLocs(u *sym.CompilationUnit) []loader.Sym { return syms } -// Add DWARF section names to the section header string table, by calling add -// on each name. ELF only. -func dwarfaddshstrings(ctxt *Link, add func(string)) { - if *FlagW { // disable dwarf - return - } - - secs := []string{"abbrev", "frame", "info", "loc", "line", "gdb_scripts"} - if buildcfg.Experiment.Dwarf5 { - secs = append(secs, "addr", "rnglists", "loclists") - } else { - secs = append(secs, "ranges", "loc") - } - - for _, sec := range secs { - add(".debug_" + sec) - if ctxt.IsExternal() { - add(elfRelType + ".debug_" + sec) - } - } -} - func dwarfaddelfsectionsyms(ctxt *Link) { if *FlagW { // disable dwarf return @@ -2428,7 +2406,7 @@ func dwarfaddelfsectionsyms(ctxt *Link) { for _, si := range dwarfp { s := si.secSym() sect := ldr.SymSect(si.secSym()) - putelfsectionsym(ctxt, ctxt.Out, s, sect.Elfsect.(*ElfShdr).shnum) + putelfsectionsym(ctxt, ctxt.Out, s, elfShdrShnum(sect.Elfsect.(*ElfShdr))) } } diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 62736ab94bd..12218feb314 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -10,6 +10,7 @@ import ( "cmd/internal/sys" "cmd/link/internal/loader" "cmd/link/internal/sym" + "cmp" "debug/elf" "encoding/binary" "encoding/hex" @@ -70,10 +71,30 @@ import ( // ElfEhdr is the ELF file header. type ElfEhdr elf.Header64 -// ElfShdr is an ELF section entry, plus the section index. +// ElfShdr is an ELF section table entry. type ElfShdr struct { elf.Section64 + + // nameString is the section name as a string. + // This is not to be confused with Name, + // inherited from elf.Section64, which is an offset. + nameString string + + // The section index, set by elfSortShdrs. + // Don't read this directly, use elfShdrShnum. shnum elf.SectionIndex + + // Because we don't compute the final section number + // until late in the link, when the link and info fields + // hold section indexes, we store pointers, and fetch + // the final section index when we write them out. + link *ElfShdr + info *ElfShdr + + // We compute the section offsets of reloc sections + // after we create the ELF section header. + // This field lets us fetch the section offset and size. + relocSect *sym.Section } // ElfPhdr is the ELF program, or segment, header. @@ -93,7 +114,7 @@ const ( ELF32RELSIZE = 8 ) -var elfstrdat, elfshstrdat []byte +var elfstrdat []byte // ELFRESERVE is the total amount of space to reserve at the // start of the file for Header, PHeaders, SHeaders, and interp. @@ -109,9 +130,10 @@ var ( // target platform uses. elfRelType string - ehdr ElfEhdr - phdr = make([]*ElfPhdr, 0, 8) - shdr = make([]*ElfShdr, 0, 64) + ehdr ElfEhdr + phdr = make([]*ElfPhdr, 0, 8) + shdr = make([]*ElfShdr, 0, 64) + shdrSorted bool interp string ) @@ -141,15 +163,6 @@ type ELFArch struct { DynamicReadOnly bool } -type Elfstring struct { - s string - off int -} - -var elfstr [100]Elfstring - -var nelfstr int - var buildinfo []byte // Elfinit initializes the global ehdr variable that holds the ELF header. @@ -263,15 +276,72 @@ func elf32phdr(out *OutBuf, e *ElfPhdr) { out.Write32(uint32(e.Align)) } +// elfShdrShnum returns the section index of an ElfShdr. +func elfShdrShnum(e *ElfShdr) elf.SectionIndex { + if e.shnum == -1 { + Errorf("internal error: retrieved section index before it is set") + errorexit() + } + return e.shnum +} + +// elfShdrOff returns the section offset for an ElfShdr. +func elfShdrOff(e *ElfShdr) uint64 { + if e.relocSect != nil { + if e.Off != 0 { + Errorf("internal error: ElfShdr relocSect == %p Off == %d", e.relocSect, e.Off) + errorexit() + } + return e.relocSect.Reloff + } + return e.Off +} + +// elfShdrSize returns the section size for an ElfShdr. +func elfShdrSize(e *ElfShdr) uint64 { + if e.relocSect != nil { + if e.Size != 0 { + Errorf("internal error: ElfShdr relocSect == %p Size == %d", e.relocSect, e.Size) + errorexit() + } + return e.relocSect.Rellen + } + return e.Size +} + +// elfShdrLink returns the link value for an ElfShdr. +func elfShdrLink(e *ElfShdr) uint32 { + if e.link != nil { + if e.Link != 0 { + Errorf("internal error: ElfShdr link == %p Link == %d", e.link, e.Link) + errorexit() + } + return uint32(elfShdrShnum(e.link)) + } + return e.Link +} + +// elfShdrInfo returns the info value for an ElfShdr. +func elfShdrInfo(e *ElfShdr) uint32 { + if e.info != nil { + if e.Info != 0 { + Errorf("internal error: ElfShdr info == %p Info == %d", e.info, e.Info) + errorexit() + } + return uint32(elfShdrShnum(e.info)) + } + return e.Info +} + func elf64shdr(out *OutBuf, e *ElfShdr) { out.Write32(e.Name) out.Write32(e.Type) out.Write64(e.Flags) out.Write64(e.Addr) - out.Write64(e.Off) - out.Write64(e.Size) - out.Write32(e.Link) - out.Write32(e.Info) + out.Write64(elfShdrOff(e)) + out.Write64(elfShdrSize(e)) + out.Write32(elfShdrLink(e)) + out.Write32(elfShdrInfo(e)) out.Write64(e.Addralign) out.Write64(e.Entsize) } @@ -281,37 +351,106 @@ func elf32shdr(out *OutBuf, e *ElfShdr) { out.Write32(e.Type) out.Write32(uint32(e.Flags)) out.Write32(uint32(e.Addr)) - out.Write32(uint32(e.Off)) - out.Write32(uint32(e.Size)) - out.Write32(e.Link) - out.Write32(e.Info) + out.Write32(uint32(elfShdrOff(e))) + out.Write32(uint32(elfShdrSize(e))) + out.Write32(elfShdrLink(e)) + out.Write32(elfShdrInfo(e)) out.Write32(uint32(e.Addralign)) out.Write32(uint32(e.Entsize)) } func elfwriteshdrs(out *OutBuf) uint32 { if elf64 { - for i := 0; i < int(ehdr.Shnum); i++ { - elf64shdr(out, shdr[i]) + for _, sh := range shdr { + elf64shdr(out, sh) } - return uint32(ehdr.Shnum) * ELF64SHDRSIZE + return uint32(len(shdr)) * ELF64SHDRSIZE } - for i := 0; i < int(ehdr.Shnum); i++ { - elf32shdr(out, shdr[i]) + for _, sh := range shdr { + elf32shdr(out, sh) } - return uint32(ehdr.Shnum) * ELF32SHDRSIZE + return uint32(len(shdr)) * ELF32SHDRSIZE } -func elfsetstring(ctxt *Link, s loader.Sym, str string, off int) { - if nelfstr >= len(elfstr) { - ctxt.Errorf(s, "too many elf strings") - errorexit() +// elfSortShdrs sorts the section headers so that allocated sections +// are first, in address order. This isn't required for correctness, +// but it makes the ELF file easier for humans to read. +// We only do this for an executable, not an object file. +func elfSortShdrs(ctxt *Link) { + if ctxt.LinkMode != LinkExternal { + // Use [1:] to leave the empty section header zero in place. + slices.SortStableFunc(shdr[1:], func(a, b *ElfShdr) int { + isAllocated := func(h *ElfShdr) bool { + return elf.SectionFlag(h.Flags)&elf.SHF_ALLOC != 0 + } + if isAllocated(a) { + if isAllocated(b) { + if r := cmp.Compare(a.Addr, b.Addr); r != 0 { + return r + } + // With same address, sort smallest + // section first. + return cmp.Compare(a.Size, b.Size) + } + // Allocated before unallocated. + return -1 + } + if isAllocated(b) { + // Allocated before unallocated. + return 1 + } + return 0 + }) + } + for i, h := range shdr { + h.shnum = elf.SectionIndex(i) + } + shdrSorted = true +} + +// elfWriteShstrtab writes out the ELF section string table. +// It also sets the Name field of the section headers. +// It returns the length of the string table. +func elfWriteShstrtab(ctxt *Link) uint32 { + // Map from section name to shstrtab offset. + m := make(map[string]uint32, len(shdr)) + + m[""] = 0 + ctxt.Out.WriteByte(0) + off := uint32(1) + + writeString := func(s string) { + m[s] = off + ctxt.Out.WriteString(s) + ctxt.Out.WriteByte(0) + off += uint32(len(s)) + 1 } - elfstr[nelfstr].s = str - elfstr[nelfstr].off = off - nelfstr++ + // As a minor optimization, do the relocation sections first, + // as they may let us reuse the suffix. + // That is, the offset for ".text" can point into ".rel.text". + // We don't do a full suffix search as the relocation sections + // are likely to be the only match. + for _, sh := range shdr { + if suffix, ok := strings.CutPrefix(sh.nameString, elfRelType); ok { + if _, found := m[suffix]; !found { + m[suffix] = off + uint32(len(elfRelType)) + } + writeString(sh.nameString) + } + } + + for _, sh := range shdr { + if shOff, ok := m[sh.nameString]; ok { + sh.Name = shOff + } else { + sh.Name = off + writeString(sh.nameString) + } + } + + return off } func elfwritephdrs(out *OutBuf) uint32 { @@ -340,12 +479,17 @@ func newElfPhdr() *ElfPhdr { return e } -func newElfShdr(name int64) *ElfShdr { - e := new(ElfShdr) - e.Name = uint32(name) - e.shnum = elf.SectionIndex(ehdr.Shnum) +func newElfShdr(name string) *ElfShdr { + if shdrSorted { + Errorf("internal error: creating a section header after they were sorted") + errorexit() + } + + e := &ElfShdr{ + nameString: name, + shnum: -1, // make invalid for now, set by elfSortShdrs + } shdr = append(shdr, e) - ehdr.Shnum++ return e } @@ -1051,37 +1195,20 @@ func elfphrelro(seg *sym.Segment) { ph.Align = uint64(*FlagRound) } +// elfshname finds or creates a section given its name. func elfshname(name string) *ElfShdr { - for i := 0; i < nelfstr; i++ { - if name != elfstr[i].s { - continue + for _, sh := range shdr { + if sh.nameString == name { + return sh } - off := elfstr[i].off - for i = 0; i < int(ehdr.Shnum); i++ { - sh := shdr[i] - if sh.Name == uint32(off) { - return sh - } - } - return newElfShdr(int64(off)) } - Exitf("cannot find elf name %s", name) - return nil + return newElfShdr(name) } -// Create an ElfShdr for the section with name. -// Create a duplicate if one already exists with that name. +// elfshnamedup creates a new section with a given name. +// If there is an existing section with this name, it creates a duplicate. func elfshnamedup(name string) *ElfShdr { - for i := 0; i < nelfstr; i++ { - if name == elfstr[i].s { - off := elfstr[i].off - return newElfShdr(int64(off)) - } - } - - Errorf("cannot find elf name %s", name) - errorexit() - return nil + return newElfShdr(name) } func elfshalloc(sect *sym.Section) *ElfShdr { @@ -1190,7 +1317,7 @@ func elfshreloc(arch *sys.Arch, sect *sym.Section) *ElfShdr { // its own .rela.text. if sect.Name == ".text" { - if sh.Info != 0 && sh.Info != uint32(sect.Elfsect.(*ElfShdr).shnum) { + if sh.info != nil && sh.info != sect.Elfsect.(*ElfShdr) { sh = elfshnamedup(elfRelType + sect.Name) } } @@ -1200,10 +1327,9 @@ func elfshreloc(arch *sys.Arch, sect *sym.Section) *ElfShdr { if typ == elf.SHT_RELA { sh.Entsize += uint64(arch.RegSize) } - sh.Link = uint32(elfshname(".symtab").shnum) - sh.Info = uint32(sect.Elfsect.(*ElfShdr).shnum) - sh.Off = sect.Reloff - sh.Size = sect.Rellen + sh.link = elfshname(".symtab") + sh.info = sect.Elfsect.(*ElfShdr) + sh.relocSect = sect sh.Addralign = uint64(arch.RegSize) return sh } @@ -1334,140 +1460,11 @@ func addgonote(ctxt *Link, sectionName string, tag uint32, desc []byte) { func (ctxt *Link) doelf() { ldr := ctxt.loader - // Predefine strings we need for section headers. - - addshstr := func(s string) int { - off := len(elfshstrdat) - elfshstrdat = append(elfshstrdat, s...) - elfshstrdat = append(elfshstrdat, 0) - return off - } - - shstrtabAddstring := func(s string) { - off := addshstr(s) - elfsetstring(ctxt, 0, s, off) - } - - shstrtabAddstring("") - shstrtabAddstring(".text") - shstrtabAddstring(".noptrdata") - shstrtabAddstring(".data") - shstrtabAddstring(".bss") - shstrtabAddstring(".noptrbss") - shstrtabAddstring(".go.fuzzcntrs") - shstrtabAddstring(".go.buildinfo") - shstrtabAddstring(".go.fipsinfo") - if ctxt.IsMIPS() { - shstrtabAddstring(".MIPS.abiflags") - shstrtabAddstring(".gnu.attributes") - } - - // generate .tbss section for dynamic internal linker or external - // linking, so that various binutils could correctly calculate - // PT_TLS size. See https://golang.org/issue/5200. - if !*FlagD || ctxt.IsExternal() { - shstrtabAddstring(".tbss") - } - if ctxt.IsNetbsd() { - shstrtabAddstring(".note.netbsd.ident") - if *flagRace { - shstrtabAddstring(".note.netbsd.pax") - } - } - if ctxt.IsOpenbsd() { - shstrtabAddstring(".note.openbsd.ident") - } - if ctxt.IsFreebsd() { - shstrtabAddstring(".note.tag") - } - if len(buildinfo) > 0 { - shstrtabAddstring(".note.gnu.build-id") - } - if *flagBuildid != "" { - shstrtabAddstring(".note.go.buildid") - } - shstrtabAddstring(".elfdata") - shstrtabAddstring(".rodata") - shstrtabAddstring(".gopclntab") - // See the comment about data.rel.ro.FOO section names in data.go. - relro_prefix := "" - if ctxt.UseRelro() { - shstrtabAddstring(".data.rel.ro") - relro_prefix = ".data.rel.ro" - } - shstrtabAddstring(relro_prefix + ".typelink") - shstrtabAddstring(relro_prefix + ".itablink") - if ctxt.IsExternal() { *FlagD = true - - shstrtabAddstring(elfRelType + ".text") - shstrtabAddstring(elfRelType + ".rodata") - shstrtabAddstring(elfRelType + relro_prefix + ".typelink") - shstrtabAddstring(elfRelType + relro_prefix + ".itablink") - shstrtabAddstring(elfRelType + ".noptrdata") - shstrtabAddstring(elfRelType + ".data") - if ctxt.UseRelro() { - shstrtabAddstring(elfRelType + ".data.rel.ro") - } - shstrtabAddstring(elfRelType + ".go.buildinfo") - shstrtabAddstring(elfRelType + ".go.fipsinfo") - if ctxt.IsMIPS() { - shstrtabAddstring(elfRelType + ".MIPS.abiflags") - shstrtabAddstring(elfRelType + ".gnu.attributes") - } - - // add a .note.GNU-stack section to mark the stack as non-executable - shstrtabAddstring(".note.GNU-stack") - - if ctxt.IsShared() { - shstrtabAddstring(".note.go.abihash") - shstrtabAddstring(".note.go.pkg-list") - shstrtabAddstring(".note.go.deps") - } } - hasinitarr := ctxt.linkShared - - // Shared library initializer. - switch ctxt.BuildMode { - case BuildModeCArchive, BuildModeCShared, BuildModeShared, BuildModePlugin: - hasinitarr = true - } - - if hasinitarr { - shstrtabAddstring(".init_array") - shstrtabAddstring(elfRelType + ".init_array") - } - - if !*FlagS { - shstrtabAddstring(".symtab") - shstrtabAddstring(".strtab") - } - if !*FlagW { - dwarfaddshstrings(ctxt, shstrtabAddstring) - } - - shstrtabAddstring(".shstrtab") - if !*FlagD { // -d suppresses dynamic loader format - shstrtabAddstring(".interp") - shstrtabAddstring(".hash") - shstrtabAddstring(".got") - if ctxt.IsPPC64() { - shstrtabAddstring(".glink") - } - shstrtabAddstring(".got.plt") - shstrtabAddstring(".dynamic") - shstrtabAddstring(".dynsym") - shstrtabAddstring(".dynstr") - shstrtabAddstring(elfRelType) - shstrtabAddstring(elfRelType + ".plt") - - shstrtabAddstring(".plt") - shstrtabAddstring(".gnu.version") - shstrtabAddstring(".gnu.version_r") - // dynamic symbol table - first entry all zeros dynsym := ldr.CreateSymForUpdate(".dynsym", 0) @@ -1710,19 +1707,6 @@ func asmbElf(ctxt *Link) { var symo int64 symo = int64(Segdwarf.Fileoff + Segdwarf.Filelen) symo = Rnd(symo, int64(ctxt.Arch.PtrSize)) - ctxt.Out.SeekSet(symo) - if *FlagS { - ctxt.Out.Write(elfshstrdat) - } else { - ctxt.Out.SeekSet(symo) - asmElfSym(ctxt) - ctxt.Out.Write(elfstrdat) - ctxt.Out.Write(elfshstrdat) - if ctxt.IsExternal() { - elfEmitReloc(ctxt) - } - } - ctxt.Out.SeekSet(0) ldr := ctxt.loader eh := getElfEhdr() @@ -1947,9 +1931,9 @@ func asmbElf(ctxt *Link) { sh.Entsize = ELF32SYMSIZE } sh.Addralign = uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".dynstr").shnum) + sh.link = elfshname(".dynstr") - // sh.info is the index of first non-local symbol (number of local symbols) + // sh.Info is the index of first non-local symbol (number of local symbols) s := ldr.Lookup(".dynsym", 0) i := uint32(0) for sub := s; sub != 0; sub = ldr.SubSym(sub) { @@ -1972,7 +1956,7 @@ func asmbElf(ctxt *Link) { sh.Type = uint32(elf.SHT_GNU_VERSYM) sh.Flags = uint64(elf.SHF_ALLOC) sh.Addralign = 2 - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") sh.Entsize = 2 shsym(sh, ldr, ldr.Lookup(".gnu.version", 0)) @@ -1981,7 +1965,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Addralign = uint64(ctxt.Arch.RegSize) sh.Info = uint32(elfverneed) - sh.Link = uint32(elfshname(".dynstr").shnum) + sh.link = elfshname(".dynstr") shsym(sh, ldr, ldr.Lookup(".gnu.version_r", 0)) } @@ -1991,8 +1975,8 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = ELF64RELASIZE sh.Addralign = uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".dynsym").shnum) - sh.Info = uint32(elfshname(".plt").shnum) + sh.link = elfshname(".dynsym") + sh.info = elfshname(".plt") shsym(sh, ldr, ldr.Lookup(".rela.plt", 0)) sh = elfshname(".rela") @@ -2000,7 +1984,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = ELF64RELASIZE sh.Addralign = 8 - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") shsym(sh, ldr, ldr.Lookup(".rela", 0)) } else { sh := elfshname(".rel.plt") @@ -2008,7 +1992,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = ELF32RELSIZE sh.Addralign = 4 - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") shsym(sh, ldr, ldr.Lookup(".rel.plt", 0)) sh = elfshname(".rel") @@ -2016,7 +2000,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = ELF32RELSIZE sh.Addralign = 4 - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") shsym(sh, ldr, ldr.Lookup(".rel", 0)) } @@ -2071,7 +2055,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = 4 sh.Addralign = uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") shsym(sh, ldr, ldr.Lookup(".hash", 0)) // sh and elf.PT_DYNAMIC for .dynamic section @@ -2081,7 +2065,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC + elf.SHF_WRITE) sh.Entsize = 2 * uint64(ctxt.Arch.RegSize) sh.Addralign = uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".dynstr").shnum) + sh.link = elfshname(".dynstr") shsym(sh, ldr, ldr.Lookup(".dynamic", 0)) ph := newElfPhdr() ph.Type = elf.PT_DYNAMIC @@ -2120,11 +2104,8 @@ func asmbElf(ctxt *Link) { } elfobj: - sh := elfshname(".shstrtab") - eh.Shstrndx = uint16(sh.shnum) - if ctxt.IsMIPS() { - sh = elfshname(".MIPS.abiflags") + sh := elfshname(".MIPS.abiflags") sh.Type = uint32(elf.SHT_MIPS_ABIFLAGS) sh.Flags = uint64(elf.SHF_ALLOC) sh.Addralign = 8 @@ -2190,6 +2171,25 @@ elfobj: sh.Flags = 0 } + elfSortShdrs(ctxt) + + sh := elfshname(".shstrtab") + eh.Shstrndx = uint16(elfShdrShnum(sh)) + + var shstrtabLen uint32 + ctxt.Out.SeekSet(symo) + if *FlagS { + shstrtabLen = elfWriteShstrtab(ctxt) + } else { + asmElfSym(ctxt) + ctxt.Out.Write(elfstrdat) + shstrtabLen = elfWriteShstrtab(ctxt) + if ctxt.IsExternal() { + elfEmitReloc(ctxt) + } + } + ctxt.Out.SeekSet(0) + var shstroff uint64 if !*FlagS { sh := elfshname(".symtab") @@ -2198,7 +2198,7 @@ elfobj: sh.Size = uint64(symSize) sh.Addralign = uint64(ctxt.Arch.RegSize) sh.Entsize = 8 + 2*uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".strtab").shnum) + sh.link = elfshname(".strtab") sh.Info = uint32(elfglobalsymndx) sh = elfshname(".strtab") @@ -2214,7 +2214,7 @@ elfobj: sh = elfshname(".shstrtab") sh.Type = uint32(elf.SHT_STRTAB) sh.Off = shstroff - sh.Size = uint64(len(elfshstrdat)) + sh.Size = uint64(shstrtabLen) sh.Addralign = 1 // Main header @@ -2264,6 +2264,11 @@ elfobj: pph.Memsz = pph.Filesz } + if len(shdr) >= 0xffff { + Errorf("too many ELF sections") + } + eh.Shnum = uint16(len(shdr)) + ctxt.Out.SeekSet(0) a := int64(0) a += int64(elfwritehdr(ctxt.Out)) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 2c861129b52..22ee13dff87 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -923,9 +923,7 @@ func (ctxt *Link) linksetup() { mdsb = ctxt.loader.MakeSymbolUpdater(moduledata) ctxt.loader.SetAttrLocal(moduledata, true) } - // In all cases way we mark the moduledata as noptrdata to hide it from - // the GC. - mdsb.SetType(sym.SNOPTRDATA) + mdsb.SetType(sym.SMODULEDATA) ctxt.loader.SetAttrReachable(moduledata, true) ctxt.Moduledata = moduledata diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index 68af94a405a..1eb7112e644 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -10,10 +10,12 @@ import ( "cmd/internal/sys" "cmd/link/internal/loader" "cmd/link/internal/sym" + "cmp" "fmt" "internal/abi" "internal/buildcfg" "path/filepath" + "slices" "strings" ) @@ -36,6 +38,7 @@ type pclntab struct { cutab loader.Sym filetab loader.Sym pctab loader.Sym + funcdata loader.Sym // The number of functions + number of TEXT sections - 1. This is such an // unexpected value because platforms that have more than one TEXT section @@ -183,7 +186,7 @@ func genInlTreeSym(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, arch // signal to the symtab() phase that it needs to be grouped in with // other similar symbols (gcdata, etc); the dodata() phase will // eventually switch the type back to SRODATA. - inlTreeSym.SetType(sym.SGOFUNC) + inlTreeSym.SetType(sym.SPCLNTAB) ldr.SetAttrReachable(its, true) ldr.SetSymAlign(its, 4) // it has 32-bit fields ninl := fi.NumInlTree() @@ -258,7 +261,7 @@ func (state *pclntab) generatePCHeader(ctxt *Link) { // Write header. // Keep in sync with runtime/symtab.go:pcHeader and package debug/gosym. - header.SetUint32(ctxt.Arch, 0, 0xfffffff1) + header.SetUint32(ctxt.Arch, 0, uint32(abi.CurrentPCLnTabMagic)) header.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC)) header.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize)) off := header.SetUint(ctxt.Arch, 8, uint64(state.nfunc)) @@ -518,6 +521,157 @@ func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) { state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab) } +// generateFuncdata writes out the funcdata information. +func (state *pclntab) generateFuncdata(ctxt *Link, funcs []loader.Sym, inlsyms map[loader.Sym]loader.Sym) { + ldr := ctxt.loader + + // Walk the functions and collect the funcdata. + seen := make(map[loader.Sym]struct{}, len(funcs)) + fdSyms := make([]loader.Sym, 0, len(funcs)) + fd := []loader.Sym{} + for _, s := range funcs { + fi := ldr.FuncInfo(s) + if !fi.Valid() { + continue + } + fi.Preload() + fd := funcData(ldr, s, fi, inlsyms[s], fd) + for j, fdSym := range fd { + if ignoreFuncData(ldr, s, j, fdSym) { + continue + } + + if _, ok := seen[fdSym]; !ok { + fdSyms = append(fdSyms, fdSym) + seen[fdSym] = struct{}{} + } + } + } + seen = nil + + // Sort the funcdata in reverse order by alignment + // to minimize alignment gaps. Use a stable sort + // for reproducible results. + var maxAlign int32 + slices.SortStableFunc(fdSyms, func(a, b loader.Sym) int { + aAlign := symalign(ldr, a) + bAlign := symalign(ldr, b) + + // Remember maximum alignment. + maxAlign = max(maxAlign, aAlign, bAlign) + + // Negate to sort by decreasing alignment. + return -cmp.Compare(aAlign, bAlign) + }) + + // We will output the symbols in the order of fdSyms. + // Set the value of each symbol to its offset in the funcdata. + // This way when writeFuncs writes out the funcdata offset, + // it can simply write out the symbol value. + + // Accumulated size of funcdata info. + size := int64(0) + + for _, fdSym := range fdSyms { + datSize := ldr.SymSize(fdSym) + if datSize == 0 { + ctxt.Errorf(fdSym, "zero size funcdata") + continue + } + + size = Rnd(size, int64(symalign(ldr, fdSym))) + ldr.SetSymValue(fdSym, size) + size += datSize + + // We do not put the funcdata symbols in the symbol table. + ldr.SetAttrNotInSymbolTable(fdSym, true) + + // Mark the symbol as special so that it does not get + // adjusted by the section offset. + ldr.SetAttrSpecial(fdSym, true) + } + + // Funcdata symbols are permitted to have R_ADDROFF relocations, + // which the linker can fully resolve. + resolveRelocs := func(ldr *loader.Loader, fdSym loader.Sym, data []byte) { + relocs := ldr.Relocs(fdSym) + for i := 0; i < relocs.Count(); i++ { + r := relocs.At(i) + if r.Type() != objabi.R_ADDROFF { + ctxt.Errorf(fdSym, "unsupported reloc %d (%s) for funcdata symbol", r.Type(), sym.RelocName(ctxt.Target.Arch, r.Type())) + return + } + if r.Siz() != 4 { + ctxt.Errorf(fdSym, "unsupported ADDROFF reloc size %d for funcdata symbol", r.Siz()) + return + } + rs := r.Sym() + if r.Weak() && !ldr.AttrReachable(rs) { + return + } + sect := ldr.SymSect(rs) + if sect == nil { + ctxt.Errorf(fdSym, "missing section for relocation target %s for funcdata symbol", ldr.SymName(rs)) + } + o := ldr.SymValue(rs) + if sect.Name != ".text" { + o -= int64(sect.Vaddr) + } else { + // With multiple .text sections the offset + // is from the start of the first one. + o -= int64(Segtext.Sections[0].Vaddr) + if ctxt.Target.IsWasm() { + if o&(1<<16-1) != 0 { + ctxt.Errorf(fdSym, "textoff relocation does not target function entry for funcdata symbol: %s %#x", ldr.SymName(rs), o) + } + o >>= 16 + } + } + o += r.Add() + if o != int64(int32(o)) && o != int64(uint32(o)) { + ctxt.Errorf(fdSym, "ADDROFF relocation out of range for funcdata symbol: %#x", o) + } + ctxt.Target.Arch.ByteOrder.PutUint32(data[r.Off():], uint32(o)) + } + } + + writeFuncData := func(ctxt *Link, s loader.Sym) { + ldr := ctxt.loader + sb := ldr.MakeSymbolUpdater(s) + for _, fdSym := range fdSyms { + off := ldr.SymValue(fdSym) + fdSymData := ldr.Data(fdSym) + sb.SetBytesAt(off, fdSymData) + // Resolve any R_ADDROFF relocations. + resolveRelocs(ldr, fdSym, sb.Data()[off:off+int64(len(fdSymData))]) + } + } + + state.funcdata = state.addGeneratedSym(ctxt, "go:func.*", size, writeFuncData) + + // Because the funcdata previously was not in pclntab, + // we need to keep the visible symbol so that tools can find it. + ldr.SetAttrNotInSymbolTable(state.funcdata, false) +} + +// ignoreFuncData reports whether we should ignore a funcdata symbol. +// +// cmd/internal/obj optimistically populates ArgsPointerMaps and +// ArgInfo for assembly functions, hoping that the compiler will +// emit appropriate symbols from their Go stub declarations. If +// it didn't though, just ignore it. +// +// TODO(cherryyz): Fix arg map generation (see discussion on CL 523335). +func ignoreFuncData(ldr *loader.Loader, s loader.Sym, j int, fdSym loader.Sym) bool { + if fdSym == 0 { + return true + } + if (j == abi.FUNCDATA_ArgsPointerMaps || j == abi.FUNCDATA_ArgInfo) && ldr.IsFromAssembly(s) && ldr.Data(fdSym) == nil { + return true + } + return false +} + // numPCData returns the number of PCData syms for the FuncInfo. // NB: Preload must be called on valid FuncInfos before calling this function. func numPCData(ldr *loader.Loader, s loader.Sym, fi loader.FuncInfo) uint32 { @@ -656,8 +810,6 @@ func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, sta func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, startLocations, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) { ldr := ctxt.loader deferReturnSym := ldr.Lookup("runtime.deferreturn", abiInternalVer) - gofunc := ldr.Lookup("go:func.*", 0) - gofuncBase := ldr.SymValue(gofunc) textStart := ldr.SymValue(ldr.Lookup("runtime.text", 0)) funcdata := []loader.Sym{} var pcsp, pcfile, pcline, pcinline loader.Sym @@ -755,25 +907,12 @@ func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSym dataoff := off + int64(4*j) fdsym := funcdata[j] - // cmd/internal/obj optimistically populates ArgsPointerMaps and - // ArgInfo for assembly functions, hoping that the compiler will - // emit appropriate symbols from their Go stub declarations. If - // it didn't though, just ignore it. - // - // TODO(cherryyz): Fix arg map generation (see discussion on CL 523335). - if fdsym != 0 && (j == abi.FUNCDATA_ArgsPointerMaps || j == abi.FUNCDATA_ArgInfo) && ldr.IsFromAssembly(s) && ldr.Data(fdsym) == nil { - fdsym = 0 - } - - if fdsym == 0 { + if ignoreFuncData(ldr, s, j, fdsym) { sb.SetUint32(ctxt.Arch, dataoff, ^uint32(0)) // ^0 is a sentinel for "no value" continue } - if outer := ldr.OuterSym(fdsym); outer != gofunc { - panic(fmt.Sprintf("bad carrier sym for symbol %s (funcdata %s#%d), want go:func.* got %s", ldr.SymName(fdsym), ldr.SymName(s), j, ldr.SymName(outer))) - } - sb.SetUint32(ctxt.Arch, dataoff, uint32(ldr.SymValue(fdsym)-gofuncBase)) + sb.SetUint32(ctxt.Arch, dataoff, uint32(ldr.SymValue(fdsym))) } } } @@ -816,6 +955,9 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes] // end PC [thearch.ptrsize bytes] // func structures, pcdata offsets, func data. + // + // runtime.funcdata + // []byte of deduplicated funcdata state, compUnits, funcs := makePclntab(ctxt, container) @@ -831,6 +973,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { state.generatePctab(ctxt, funcs) inlSyms := makeInlSyms(ctxt, funcs, nameOffsets) state.generateFunctab(ctxt, funcs, inlSyms, cuOffsets, nameOffsets) + state.generateFuncdata(ctxt, funcs, inlSyms) return state } @@ -932,7 +1075,7 @@ func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) { } } - state.findfunctab = ctxt.createGeneratorSymbol("runtime.findfunctab", 0, sym.SRODATA, size, writeFindFuncTab) + state.findfunctab = ctxt.createGeneratorSymbol("runtime.findfunctab", 0, sym.SPCLNTAB, size, writeFindFuncTab) ldr.SetAttrReachable(state.findfunctab, true) ldr.SetAttrLocal(state.findfunctab, true) } diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index a0345ca1c7b..f9bc7007eda 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -101,7 +101,7 @@ func putelfsym(ctxt *Link, x loader.Sym, typ elf.SymType, curbind elf.SymBind) { ldr.Errorf(x, "missing ELF section in putelfsym") return } - elfshnum = xosect.Elfsect.(*ElfShdr).shnum + elfshnum = elfShdrShnum(xosect.Elfsect.(*ElfShdr)) } sname := ldr.SymExtname(x) @@ -496,13 +496,13 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { } var ( symgostring = groupSym("go:string.*", sym.SGOSTRING) - symgofunc = groupSym("go:func.*", sym.SGOFUNC) + symgofunc = groupSym("go:funcdesc", sym.SGOFUNC) symgcbits = groupSym("runtime.gcbits.*", sym.SGCBITS) ) symgofuncrel := symgofunc if ctxt.UseRelro() { - symgofuncrel = groupSym("go:funcrel.*", sym.SGOFUNCRELRO) + symgofuncrel = groupSym("go:funcdescrel", sym.SGOFUNCRELRO) } // assign specific types so that they sort together. @@ -548,28 +548,6 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { ldr.SetCarrierSym(s, symgofunc) } - case strings.HasPrefix(name, "gcargs."), - strings.HasPrefix(name, "gclocals."), - strings.HasPrefix(name, "gclocals·"), - ldr.SymType(s) == sym.SGOFUNC && s != symgofunc, // inltree, see pcln.go - strings.HasSuffix(name, ".opendefer"), - strings.HasSuffix(name, ".arginfo0"), - strings.HasSuffix(name, ".arginfo1"), - strings.HasSuffix(name, ".argliveinfo"), - strings.HasSuffix(name, ".wrapinfo"), - strings.HasSuffix(name, ".args_stackmap"), - strings.HasSuffix(name, ".stkobj"): - ldr.SetAttrNotInSymbolTable(s, true) - symGroupType[s] = sym.SGOFUNC - ldr.SetCarrierSym(s, symgofunc) - if ctxt.Debugvlog != 0 { - align := ldr.SymAlign(s) - liveness += (ldr.SymSize(s) + int64(align) - 1) &^ (int64(align) - 1) - } - - // Note: Check for "type:" prefix after checking for .arginfo1 suffix. - // That way symbols like "type:.eq.[2]interface {}.arginfo1" that belong - // in go:func.* end up there. case strings.HasPrefix(name, "type:"): if !ctxt.DynlinkingGo() { ldr.SetAttrNotInSymbolTable(s, true) diff --git a/src/cmd/link/internal/ld/xcoff.go b/src/cmd/link/internal/ld/xcoff.go index 4500a7cb0c9..77ae1236c9c 100644 --- a/src/cmd/link/internal/ld/xcoff.go +++ b/src/cmd/link/internal/ld/xcoff.go @@ -1254,7 +1254,7 @@ func Xcoffadddynrel(target *Target, ldr *loader.Loader, syms *ArchSyms, s loader break } } - } else if t := ldr.SymType(s); t.IsDATA() || t.IsNOPTRDATA() || t == sym.SBUILDINFO || t == sym.SXCOFFTOC { + } else if t := ldr.SymType(s); t.IsDATA() || t.IsNOPTRDATA() || t == sym.SBUILDINFO || t == sym.SXCOFFTOC || t == sym.SMODULEDATA { switch ldr.SymSect(targ).Seg { default: ldr.Errorf(s, "unknown segment for .loader relocation with symbol %s", ldr.SymName(targ)) diff --git a/src/cmd/link/internal/loong64/asm.go b/src/cmd/link/internal/loong64/asm.go index 219cfc7196a..142578bcebf 100644 --- a/src/cmd/link/internal/loong64/asm.go +++ b/src/cmd/link/internal/loong64/asm.go @@ -643,11 +643,14 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { relocs := ldr.Relocs(s) r := relocs.At(ri) switch r.Type() { - case objabi.ElfRelocOffset + objabi.RelocType(elf.R_LARCH_B26): - // Nothing to do. - // The plt symbol has not been added. If we add tramp - // here, plt will not work. - case objabi.R_CALLLOONG64: + case objabi.ElfRelocOffset + objabi.RelocType(elf.R_LARCH_B26), objabi.R_CALLLOONG64: + if ldr.SymType(rs) == sym.SDYNIMPORT { + // Nothing to do. + // The plt symbol has not been added. If we add tramp + // here, plt will not work. + return + } + var t int64 // ldr.SymValue(rs) == 0 indicates a cross-package jump to a function that is not yet // laid out. Conservatively use a trampoline. This should be rare, as we lay out packages diff --git a/src/cmd/link/internal/sym/symkind.go b/src/cmd/link/internal/sym/symkind.go index 8709e7b48f8..746a12efd62 100644 --- a/src/cmd/link/internal/sym/symkind.go +++ b/src/cmd/link/internal/sym/symkind.go @@ -99,9 +99,10 @@ const ( SFIPSINFO // go:fipsinfo aka crypto/internal/fips140/check.Linkinfo (why is this writable)? SELFSECT // .got.plt, .plt, .dynamic where appropriate. SMACHO // Used only for .llvmasm? - SMACHOGOT // Mach-O GOT. SWINDOWS // Windows dynamic symbols. + SMODULEDATA // Linker generated moduledata struct. SELFGOT // Writable ELF GOT section. + SMACHOGOT // Mach-O GOT. SNOPTRDATA // Data with no heap pointers. SNOPTRDATAFIPSSTART // Start of FIPS non-pointer writable data. SNOPTRDATAFIPS // FIPS non-pointer writable data. diff --git a/src/cmd/link/internal/sym/symkind_string.go b/src/cmd/link/internal/sym/symkind_string.go index 019e7c746a6..80da278b332 100644 --- a/src/cmd/link/internal/sym/symkind_string.go +++ b/src/cmd/link/internal/sym/symkind_string.go @@ -45,54 +45,55 @@ func _() { _ = x[SFIPSINFO-34] _ = x[SELFSECT-35] _ = x[SMACHO-36] - _ = x[SMACHOGOT-37] - _ = x[SWINDOWS-38] + _ = x[SWINDOWS-37] + _ = x[SMODULEDATA-38] _ = x[SELFGOT-39] - _ = x[SNOPTRDATA-40] - _ = x[SNOPTRDATAFIPSSTART-41] - _ = x[SNOPTRDATAFIPS-42] - _ = x[SNOPTRDATAFIPSEND-43] - _ = x[SNOPTRDATAEND-44] - _ = x[SINITARR-45] - _ = x[SDATA-46] - _ = x[SDATAFIPSSTART-47] - _ = x[SDATAFIPS-48] - _ = x[SDATAFIPSEND-49] - _ = x[SDATAEND-50] - _ = x[SXCOFFTOC-51] - _ = x[SBSS-52] - _ = x[SNOPTRBSS-53] - _ = x[SLIBFUZZER_8BIT_COUNTER-54] - _ = x[SCOVERAGE_COUNTER-55] - _ = x[SCOVERAGE_AUXVAR-56] - _ = x[STLSBSS-57] - _ = x[SFirstUnallocated-58] - _ = x[SXREF-59] - _ = x[SMACHOSYMSTR-60] - _ = x[SMACHOSYMTAB-61] - _ = x[SMACHOINDIRECTPLT-62] - _ = x[SMACHOINDIRECTGOT-63] - _ = x[SDYNIMPORT-64] - _ = x[SHOSTOBJ-65] - _ = x[SUNDEFEXT-66] - _ = x[SDWARFSECT-67] - _ = x[SDWARFCUINFO-68] - _ = x[SDWARFCONST-69] - _ = x[SDWARFFCN-70] - _ = x[SDWARFABSFCN-71] - _ = x[SDWARFTYPE-72] - _ = x[SDWARFVAR-73] - _ = x[SDWARFRANGE-74] - _ = x[SDWARFLOC-75] - _ = x[SDWARFLINES-76] - _ = x[SDWARFADDR-77] - _ = x[SSEHUNWINDINFO-78] - _ = x[SSEHSECT-79] + _ = x[SMACHOGOT-40] + _ = x[SNOPTRDATA-41] + _ = x[SNOPTRDATAFIPSSTART-42] + _ = x[SNOPTRDATAFIPS-43] + _ = x[SNOPTRDATAFIPSEND-44] + _ = x[SNOPTRDATAEND-45] + _ = x[SINITARR-46] + _ = x[SDATA-47] + _ = x[SDATAFIPSSTART-48] + _ = x[SDATAFIPS-49] + _ = x[SDATAFIPSEND-50] + _ = x[SDATAEND-51] + _ = x[SXCOFFTOC-52] + _ = x[SBSS-53] + _ = x[SNOPTRBSS-54] + _ = x[SLIBFUZZER_8BIT_COUNTER-55] + _ = x[SCOVERAGE_COUNTER-56] + _ = x[SCOVERAGE_AUXVAR-57] + _ = x[STLSBSS-58] + _ = x[SFirstUnallocated-59] + _ = x[SXREF-60] + _ = x[SMACHOSYMSTR-61] + _ = x[SMACHOSYMTAB-62] + _ = x[SMACHOINDIRECTPLT-63] + _ = x[SMACHOINDIRECTGOT-64] + _ = x[SDYNIMPORT-65] + _ = x[SHOSTOBJ-66] + _ = x[SUNDEFEXT-67] + _ = x[SDWARFSECT-68] + _ = x[SDWARFCUINFO-69] + _ = x[SDWARFCONST-70] + _ = x[SDWARFFCN-71] + _ = x[SDWARFABSFCN-72] + _ = x[SDWARFTYPE-73] + _ = x[SDWARFVAR-74] + _ = x[SDWARFRANGE-75] + _ = x[SDWARFLOC-76] + _ = x[SDWARFLINES-77] + _ = x[SDWARFADDR-78] + _ = x[SSEHUNWINDINFO-79] + _ = x[SSEHSECT-80] } -const _SymKind_name = "SxxxSTEXTSTEXTFIPSSTARTSTEXTFIPSSTEXTFIPSENDSTEXTENDSELFRXSECTSMACHOPLTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASRODATAFIPSSTARTSRODATAFIPSSRODATAFIPSENDSRODATAENDSFUNCTABSPCLNTABSELFROSECTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSELFRELROSECTSMACHORELROSECTSTYPELINKSITABLINKSFirstWritableSBUILDINFOSFIPSINFOSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASNOPTRDATAFIPSSTARTSNOPTRDATAFIPSSNOPTRDATAFIPSENDSNOPTRDATAENDSINITARRSDATASDATAFIPSSTARTSDATAFIPSSDATAFIPSENDSDATAENDSXCOFFTOCSBSSSNOPTRBSSSLIBFUZZER_8BIT_COUNTERSCOVERAGE_COUNTERSCOVERAGE_AUXVARSTLSBSSSFirstUnallocatedSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSDYNIMPORTSHOSTOBJSUNDEFEXTSDWARFSECTSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSDWARFADDRSSEHUNWINDINFOSSEHSECT" +const _SymKind_name = "SxxxSTEXTSTEXTFIPSSTARTSTEXTFIPSSTEXTFIPSENDSTEXTENDSELFRXSECTSMACHOPLTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASRODATAFIPSSTARTSRODATAFIPSSRODATAFIPSENDSRODATAENDSFUNCTABSPCLNTABSELFROSECTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSELFRELROSECTSMACHORELROSECTSTYPELINKSITABLINKSFirstWritableSBUILDINFOSFIPSINFOSELFSECTSMACHOSWINDOWSSMODULEDATASELFGOTSMACHOGOTSNOPTRDATASNOPTRDATAFIPSSTARTSNOPTRDATAFIPSSNOPTRDATAFIPSENDSNOPTRDATAENDSINITARRSDATASDATAFIPSSTARTSDATAFIPSSDATAFIPSENDSDATAENDSXCOFFTOCSBSSSNOPTRBSSSLIBFUZZER_8BIT_COUNTERSCOVERAGE_COUNTERSCOVERAGE_AUXVARSTLSBSSSFirstUnallocatedSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSDYNIMPORTSHOSTOBJSUNDEFEXTSDWARFSECTSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSDWARFADDRSSEHUNWINDINFOSSEHSECT" -var _SymKind_index = [...]uint16{0, 4, 9, 23, 32, 44, 52, 62, 71, 76, 83, 92, 99, 106, 113, 129, 140, 154, 164, 172, 180, 190, 200, 212, 226, 238, 250, 262, 275, 288, 303, 312, 321, 335, 345, 354, 362, 368, 377, 385, 392, 402, 421, 435, 452, 465, 473, 478, 492, 501, 513, 521, 530, 534, 543, 566, 583, 599, 606, 623, 628, 640, 652, 669, 686, 696, 704, 713, 723, 735, 746, 755, 767, 777, 786, 797, 806, 817, 827, 841, 849} +var _SymKind_index = [...]uint16{0, 4, 9, 23, 32, 44, 52, 62, 71, 76, 83, 92, 99, 106, 113, 129, 140, 154, 164, 172, 180, 190, 200, 212, 226, 238, 250, 262, 275, 288, 303, 312, 321, 335, 345, 354, 362, 368, 376, 387, 394, 403, 413, 432, 446, 463, 476, 484, 489, 503, 512, 524, 532, 541, 545, 554, 577, 594, 610, 617, 634, 639, 651, 663, 680, 697, 707, 715, 724, 734, 746, 757, 766, 778, 788, 797, 808, 817, 828, 838, 852, 860} func (i SymKind) String() string { if i >= SymKind(len(_SymKind_index)-1) { diff --git a/src/cmd/link/internal/wasm/asm.go b/src/cmd/link/internal/wasm/asm.go index 65f79c80120..947f2da2b5d 100644 --- a/src/cmd/link/internal/wasm/asm.go +++ b/src/cmd/link/internal/wasm/asm.go @@ -127,6 +127,7 @@ func asmb(ctxt *ld.Link, ldr *loader.Loader) { ldr.SymSect(ldr.Lookup("runtime.rodata", 0)), ldr.SymSect(ldr.Lookup("runtime.typelink", 0)), ldr.SymSect(ldr.Lookup("runtime.itablink", 0)), + ldr.SymSect(ldr.Lookup("runtime.firstmoduledata", 0)), ldr.SymSect(ldr.Lookup("runtime.pclntab", 0)), ldr.SymSect(ldr.Lookup("runtime.noptrdata", 0)), ldr.SymSect(ldr.Lookup("runtime.data", 0)), diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 6ab1246c814..0c4cde0399f 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -7,10 +7,14 @@ package main import ( "bufio" "bytes" + "debug/elf" "debug/macho" + "debug/pe" "errors" + "internal/abi" "internal/platform" "internal/testenv" + "internal/xcoff" "os" "os/exec" "path/filepath" @@ -19,12 +23,90 @@ import ( "strconv" "strings" "testing" + "unsafe" imacho "cmd/internal/macho" "cmd/internal/objfile" "cmd/internal/sys" ) +// TestMain allows this test binary to run as a -toolexec wrapper for +// the 'go' command. If LINK_TEST_TOOLEXEC is set, TestMain runs the +// binary as if it were cmd/link, and otherwise runs the requested +// tool as a subprocess. +// +// This allows the test to verify the behavior of the current contents of the +// cmd/link package even if the installed cmd/link binary is stale. +func TestMain(m *testing.M) { + // Are we running as a toolexec wrapper? If so then run either + // the correct tool or this executable itself (for the linker). + // Running as toolexec wrapper. + if os.Getenv("LINK_TEST_TOOLEXEC") != "" { + if strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe") == "link" { + // Running as a -toolexec linker, and the tool is cmd/link. + // Substitute this test binary for the linker. + os.Args = os.Args[1:] + main() + os.Exit(0) + } + // Running some other tool. + cmd := exec.Command(os.Args[1], os.Args[2:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + os.Exit(1) + } + os.Exit(0) + } + + // Are we being asked to run as the linker (without toolexec)? + // If so then kick off main. + if os.Getenv("LINK_TEST_EXEC_LINKER") != "" { + main() + os.Exit(0) + } + + if testExe, err := os.Executable(); err == nil { + // on wasm, some phones, we expect an error from os.Executable() + testLinker = testExe + } + + // Not running as a -toolexec wrapper or as a linker executable. + // Just run the tests. + os.Exit(m.Run()) +} + +// testLinker is the path of the test executable being run. +// This is used by [TestScript]. +var testLinker string + +// goCmd returns a [*exec.Cmd] that runs the go tool using +// the current linker sources rather than the installed linker. +// The first element of the args parameter should be the go subcommand +// to run, such as "build" or "run". It must be a subcommand that +// takes the go command's build flags. +func goCmd(t *testing.T, args ...string) *exec.Cmd { + goArgs := []string{args[0], "-toolexec", testenv.Executable(t)} + args = append(goArgs, args[1:]...) + cmd := testenv.Command(t, testenv.GoToolPath(t), args...) + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + return cmd +} + +// linkCmd returns a [*exec.Cmd] that runs the linker built from +// the current sources. This is like "go tool link", but runs the +// current linker rather than the installed one. +func linkCmd(t *testing.T, args ...string) *exec.Cmd { + // Set up the arguments that TestMain looks for. + args = append([]string{"link"}, args...) + cmd := testenv.Command(t, testenv.Executable(t), args...) + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + return cmd +} + var AuthorPaidByTheColumnInch struct { fog int `text:"London. Michaelmas term lately over, and the Lord Chancellor sitting in Lincoln’s Inn Hall. Implacable November weather. As much mud in the streets as if the waters had but newly retired from the face of the earth, and it would not be wonderful to meet a Megalosaurus, forty feet long or so, waddling like an elephantine lizard up Holborn Hill. Smoke lowering down from chimney-pots, making a soft black drizzle, with flakes of soot in it as big as full-grown snowflakes—gone into mourning, one might imagine, for the death of the sun. Dogs, undistinguishable in mire. Horses, scarcely better; splashed to their very blinkers. Foot passengers, jostling one another’s umbrellas in a general infection of ill temper, and losing their foot-hold at street-corners, where tens of thousands of other foot passengers have been slipping and sliding since the day broke (if this day ever broke), adding new deposits to the crust upon crust of mud, sticking at those points tenaciously to the pavement, and accumulating at compound interest. Fog everywhere. Fog up the river, where it flows among green aits and meadows; fog down the river, where it rolls defiled among the tiers of shipping and the waterside pollutions of a great (and dirty) city. Fog on the Essex marshes, fog on the Kentish heights. Fog creeping into the cabooses of collier-brigs; fog lying out on the yards and hovering in the rigging of great ships; fog drooping on the gunwales of barges and small boats. Fog in the eyes and throats of ancient Greenwich pensioners, wheezing by the firesides of their wards; fog in the stem and bowl of the afternoon pipe of the wrathful skipper, down in his close cabin; fog cruelly pinching the toes and fingers of his shivering little ‘prentice boy on deck. Chance people on the bridges peeping over the parapets into a nether sky of fog, with fog all round them, as if they were up in a balloon and hanging in the misty clouds. Gas looming through the fog in divers places in the streets, much as the sun may, from the spongey fields, be seen to loom by husbandman and ploughboy. Most of the shops lighted two hours before their time—as the gas seems to know, for it has a haggard and unwilling look. The raw afternoon is rawest, and the dense fog is densest, and the muddy streets are muddiest near that leaden-headed old obstruction, appropriate ornament for the threshold of a leaden-headed old corporation, Temple Bar. And hard by Temple Bar, in Lincoln’s Inn Hall, at the very heart of the fog, sits the Lord High Chancellor in his High Court of Chancery."` @@ -74,7 +156,7 @@ func main() {} t.Fatalf("failed to compile main.go: %v, output: %s\n", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgfile, "main.o") + cmd = linkCmd(t, "-importcfg="+importcfgfile, "main.o") cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err != nil { @@ -111,9 +193,6 @@ func TestIssue28429(t *testing.T) { cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { - if len(args) >= 2 && args[1] == "link" && runtime.GOOS == "android" && runtime.GOARCH == "arm64" { - testenv.SkipFlaky(t, 58806) - } t.Fatalf("'go %s' failed: %v, output: %s", strings.Join(args, " "), err, out) } @@ -133,7 +212,15 @@ func TestIssue28429(t *testing.T) { // Verify that the linker does not attempt // to compile the extra section. - runGo("tool", "link", "-importcfg="+importcfgfile, "main.a") + cmd := linkCmd(t, "-importcfg="+importcfgfile, "main.a") + cmd.Dir = tmpdir + out, err := cmd.CombinedOutput() + if err != nil { + if runtime.GOOS == "android" && runtime.GOARCH == "arm64" { + testenv.SkipFlaky(t, 58806) + } + t.Fatalf("linker failed: %v, output %s", err, out) + } } func TestUnresolved(t *testing.T) { @@ -171,9 +258,9 @@ TEXT ·x(SB),0,$0 MOVD ·zero(SB), AX RET `) - cmd := testenv.Command(t, testenv.GoToolPath(t), "build") + cmd := goCmd(t, "build") cmd.Dir = tmpdir - cmd.Env = append(os.Environ(), + cmd.Env = append(cmd.Env, "GOARCH=amd64", "GOOS=linux", "GOPATH="+filepath.Join(tmpdir, "_gopath")) out, err := cmd.CombinedOutput() if err == nil { @@ -260,7 +347,7 @@ void foo() { runGo("tool", "pack", "c", "x.a", "x1.o", "x2.o", "x3.o") // Now attempt to link using the internal linker. - cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgfile, "-linkmode=internal", "x.a") + cmd := linkCmd(t, "-importcfg="+importcfgfile, "-linkmode=internal", "x.a") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err == nil { @@ -306,7 +393,7 @@ func TestBuildForTvOS(t *testing.T) { tmpDir := t.TempDir() ar := filepath.Join(tmpDir, "lib.a") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode=c-archive", "-o", ar, lib) + cmd := goCmd(t, "build", "-buildmode=c-archive", "-o", ar, lib) env := []string{ "CGO_ENABLED=1", "GOOS=ios", @@ -315,7 +402,7 @@ func TestBuildForTvOS(t *testing.T) { "CGO_CFLAGS=", // ensure CGO_CFLAGS does not contain any flags. Issue #35459 "CGO_LDFLAGS=" + strings.Join(CGO_LDFLAGS, " "), } - cmd.Env = append(os.Environ(), env...) + cmd.Env = append(cmd.Env, env...) t.Logf("%q %v", env, cmd) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) @@ -351,7 +438,7 @@ func TestXFlag(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-X=main.X=meow", "-o", filepath.Join(tmpdir, "main"), src) + cmd := goCmd(t, "build", "-ldflags=-X=main.X=meow", "-o", filepath.Join(tmpdir, "main"), src) if out, err := cmd.CombinedOutput(); err != nil { t.Errorf("%v: %v:\n%s", cmd.Args, err, out) } @@ -376,8 +463,8 @@ func TestMachOBuildVersion(t *testing.T) { } exe := filepath.Join(tmpdir, "main") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal", "-o", exe, src) - cmd.Env = append(os.Environ(), + cmd := goCmd(t, "build", "-ldflags=-linkmode=internal", "-o", exe, src) + cmd.Env = append(cmd.Env, "CGO_ENABLED=0", "GOOS=darwin", "GOARCH=amd64", @@ -469,7 +556,7 @@ func TestMachOUUID(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { exe := filepath.Join(tmpdir, test.name) - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+test.ldflags, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags="+test.ldflags, "-o", exe, src) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) } @@ -610,7 +697,7 @@ func TestStrictDup(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-strictdups=1") + cmd := goCmd(t, "build", "-ldflags=-strictdups=1") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { @@ -620,7 +707,7 @@ func TestStrictDup(t *testing.T) { t.Errorf("unexpected output:\n%s", out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-strictdups=2") + cmd = goCmd(t, "build", "-ldflags=-strictdups=2") cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err == nil { @@ -708,7 +795,7 @@ func TestFuncAlign(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "falign") + cmd := goCmd(t, "build", "-o", "falign") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { @@ -758,7 +845,7 @@ func TestFuncAlignOption(t *testing.T) { 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 := goCmd(t, "build", "-ldflags=-funcalign="+strconv.FormatUint(align, 10), "-o", exeName, "falign.go") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { @@ -853,7 +940,7 @@ func TestTrampoline(t *testing.T) { exe := filepath.Join(tmpdir, "hello.exe") for _, mode := range buildmodes { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) + cmd := goCmd(t, "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) @@ -919,7 +1006,7 @@ func TestTrampolineCgo(t *testing.T) { exe := filepath.Join(tmpdir, "hello.exe") for _, mode := range buildmodes { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) + cmd := goCmd(t, "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) @@ -938,7 +1025,7 @@ func TestTrampolineCgo(t *testing.T) { if !testenv.CanInternalLink(true) { continue } - cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src) + cmd = goCmd(t, "build", "-buildmode="+mode, "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src) out, err = cmd.CombinedOutput() if err != nil { t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) @@ -992,7 +1079,7 @@ func TestIndexMismatch(t *testing.T) { if err != nil { t.Fatalf("compiling main.go failed: %v\n%s", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgWithAFile, "-L", tmpdir, "-o", exe, mObj) + cmd = linkCmd(t, "-importcfg="+importcfgWithAFile, "-L", tmpdir, "-o", exe, mObj) t.Log(cmd) out, err = cmd.CombinedOutput() if err != nil { @@ -1010,7 +1097,7 @@ func TestIndexMismatch(t *testing.T) { if err != nil { t.Fatalf("compiling a.go failed: %v\n%s", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgWithAFile, "-L", tmpdir, "-o", exe, mObj) + cmd = linkCmd(t, "-importcfg="+importcfgWithAFile, "-L", tmpdir, "-o", exe, mObj) t.Log(cmd) out, err = cmd.CombinedOutput() if err == nil { @@ -1036,7 +1123,7 @@ func TestPErsrcBinutils(t *testing.T) { pkgdir := filepath.Join("testdata", "pe-binutils") exe := filepath.Join(tmpdir, "a.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe) + cmd := goCmd(t, "build", "-o", exe) cmd.Dir = pkgdir // cmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64") // uncomment if debugging in a cross-compiling environment out, err := cmd.CombinedOutput() @@ -1068,7 +1155,7 @@ func TestPErsrcLLVM(t *testing.T) { pkgdir := filepath.Join("testdata", "pe-llvm") exe := filepath.Join(tmpdir, "a.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe) + cmd := goCmd(t, "build", "-o", exe) cmd.Dir = pkgdir // cmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64") // uncomment if debugging in a cross-compiling environment out, err := cmd.CombinedOutput() @@ -1093,7 +1180,7 @@ func TestContentAddressableSymbols(t *testing.T) { t.Parallel() src := filepath.Join("testdata", "testHashedSyms", "p.go") - cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src) + cmd := goCmd(t, "run", src) out, err := cmd.CombinedOutput() if err != nil { t.Errorf("command %s failed: %v\n%s", cmd, err, out) @@ -1107,7 +1194,7 @@ func TestReadOnly(t *testing.T) { t.Parallel() src := filepath.Join("testdata", "testRO", "x.go") - cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src) + cmd := goCmd(t, "run", src) out, err := cmd.CombinedOutput() if err == nil { t.Errorf("running test program did not fail. output:\n%s", out) @@ -1143,7 +1230,7 @@ func TestIssue38554(t *testing.T) { t.Fatalf("failed to write source file: %v", err) } exe := filepath.Join(tmpdir, "x.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src) + cmd := goCmd(t, "build", "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build failed: %v\n%s", err, out) @@ -1193,7 +1280,7 @@ func TestIssue42396(t *testing.T) { t.Fatalf("failed to write source file: %v", err) } exe := filepath.Join(tmpdir, "main.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-gcflags=-race", "-o", exe, src) + cmd := goCmd(t, "build", "-gcflags=-race", "-o", exe, src) out, err := cmd.CombinedOutput() if err == nil { t.Fatalf("build unexpectedly succeeded") @@ -1263,14 +1350,14 @@ func TestLargeReloc(t *testing.T) { if err != nil { t.Fatalf("failed to write source file: %v", err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src) + cmd := goCmd(t, "run", src) out, err := cmd.CombinedOutput() if err != nil { t.Errorf("build failed: %v. output:\n%s", err, out) } if testenv.HasCGO() { // currently all targets that support cgo can external link - cmd = testenv.Command(t, testenv.GoToolPath(t), "run", "-ldflags=-linkmode=external", src) + cmd = goCmd(t, "run", "-ldflags=-linkmode=external", src) out, err = cmd.CombinedOutput() if err != nil { t.Fatalf("build failed: %v. output:\n%s", err, out) @@ -1314,7 +1401,7 @@ func TestUnlinkableObj(t *testing.T) { if err != nil { t.Fatalf("compile x.go failed: %v. output:\n%s", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgfile, "-o", exe, xObj) + cmd = linkCmd(t, "-importcfg="+importcfgfile, "-o", exe, xObj) out, err = cmd.CombinedOutput() if err == nil { t.Fatalf("link did not fail") @@ -1335,7 +1422,7 @@ func TestUnlinkableObj(t *testing.T) { t.Fatalf("compile failed: %v. output:\n%s", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgfile, "-o", exe, xObj) + cmd = linkCmd(t, "-importcfg="+importcfgfile, "-o", exe, xObj) out, err = cmd.CombinedOutput() if err != nil { t.Errorf("link failed: %v. output:\n%s", err, out) @@ -1380,7 +1467,7 @@ func main() {} ldflags := "-ldflags=-v -linkmode=external -tmpdir=" + linktmp var out0 []byte for i := 0; i < 5; i++ { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", ldflags, "-o", exe, src) + cmd := goCmd(t, "build", ldflags, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build failed: %v, output:\n%s", err, out) @@ -1440,6 +1527,9 @@ func TestResponseFile(t *testing.T) { t.Fatal(err) } + // We don't use goCmd here, as -toolexec doesn't use response files. + // This test is more for the go command than the linker anyhow. + cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "output", "x.go") cmd.Dir = tmpdir @@ -1482,7 +1572,7 @@ func TestDynimportVar(t *testing.T) { src := filepath.Join("testdata", "dynimportvar", "main.go") for _, mode := range []string{"internal", "external"} { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode="+mode, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags=-linkmode="+mode, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build (linkmode=%s) failed: %v\n%s", mode, err, out) @@ -1525,7 +1615,7 @@ func TestFlagS(t *testing.T) { syms := []string{"main.main", "main.X", "main.Y"} for _, mode := range modes { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-s -linkmode="+mode, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags=-s -linkmode="+mode, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build (linkmode=%s) failed: %v\n%s", mode, err, out) @@ -1566,7 +1656,7 @@ func TestRandLayout(t *testing.T) { var syms [2]string for i, seed := range []string{"123", "456"} { exe := filepath.Join(tmpdir, "hello"+seed+".exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-randlayout="+seed, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags=-randlayout="+seed, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("seed=%v: build failed: %v\n%s", seed, err, out) @@ -1627,7 +1717,7 @@ func TestCheckLinkname(t *testing.T) { t.Parallel() src := "./testdata/linkname/" + test.src exe := filepath.Join(tmpdir, test.src+".exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src) + cmd := goCmd(t, "build", "-o", exe, src) out, err := cmd.CombinedOutput() if test.ok && err != nil { t.Errorf("build failed unexpectedly: %v:\n%s", err, out) @@ -1649,7 +1739,7 @@ func TestLinknameBSS(t *testing.T) { src := filepath.Join("testdata", "linkname", "sched.go") exe := filepath.Join(tmpdir, "sched.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src) + cmd := goCmd(t, "build", "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build failed unexpectedly: %v:\n%s", err, out) @@ -1688,3 +1778,436 @@ func TestLinknameBSS(t *testing.T) { t.Errorf("executable failed to run: %v\n%s", err, out) } } + +// setValueFromBytes copies from a []byte to a variable. +// This is used to get correctly aligned values in TestFuncdataPlacement. +func setValueFromBytes[T any](p *T, s []byte) { + copy(unsafe.Slice((*byte)(unsafe.Pointer(p)), unsafe.Sizeof(*p)), s) +} + +// Test that all funcdata values are stored in the .gopclntab section. +// This is pretty ugly as there is no API for accessing this data. +// This test will have to be updated when the data formats change. +func TestFuncdataPlacement(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(trivialSrc), 0o444); err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "x.exe") + cmd := goCmd(t, "build", "-o", exe, src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build failed; %v, output:\n%s", err, out) + } + + // We want to find the funcdata in the executable. + // We look at the section table to find the .gopclntab section, + // which starts with the pcHeader. + // That will give us the table of functions, + // which we can use to find the funcdata. + + ef, _ := elf.Open(exe) + mf, _ := macho.Open(exe) + pf, _ := pe.Open(exe) + xf, _ := xcoff.Open(exe) + // TODO: plan9 + if ef == nil && mf == nil && pf == nil && xf == nil { + t.Skip("unrecognized executable file format") + } + + const moddataSymName = "runtime.firstmoduledata" + const gofuncSymName = "go:func.*" + var ( + pclntab []byte + pclntabAddr uint64 + pclntabEnd uint64 + moddataAddr uint64 + moddataBytes []byte + gofuncAddr uint64 + imageBase uint64 + ) + switch { + case ef != nil: + defer ef.Close() + + syms, err := ef.Symbols() + if err != nil { + t.Fatal(err) + } + for _, sym := range syms { + switch sym.Name { + case moddataSymName: + moddataAddr = sym.Value + case gofuncSymName: + gofuncAddr = sym.Value + } + } + + for _, sec := range ef.Sections { + if sec.Name == ".gopclntab" { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + pclntab = data + pclntabAddr = sec.Addr + pclntabEnd = sec.Addr + sec.Size + } + if sec.Flags&elf.SHF_ALLOC != 0 && moddataAddr >= sec.Addr && moddataAddr < sec.Addr+sec.Size { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + moddataBytes = data[moddataAddr-sec.Addr:] + } + } + + case mf != nil: + defer mf.Close() + + for _, sym := range mf.Symtab.Syms { + switch sym.Name { + case moddataSymName: + moddataAddr = sym.Value + case gofuncSymName: + gofuncAddr = sym.Value + } + } + + for _, sec := range mf.Sections { + if sec.Name == "__gopclntab" { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + pclntab = data + pclntabAddr = sec.Addr + pclntabEnd = sec.Addr + sec.Size + } + if moddataAddr >= sec.Addr && moddataAddr < sec.Addr+sec.Size { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + moddataBytes = data[moddataAddr-sec.Addr:] + } + } + + case pf != nil: + defer pf.Close() + + switch ohdr := pf.OptionalHeader.(type) { + case *pe.OptionalHeader32: + imageBase = uint64(ohdr.ImageBase) + case *pe.OptionalHeader64: + imageBase = ohdr.ImageBase + } + + var moddataSym, gofuncSym, pclntabSym, epclntabSym *pe.Symbol + for _, sym := range pf.Symbols { + switch sym.Name { + case moddataSymName: + moddataSym = sym + case gofuncSymName: + gofuncSym = sym + case "runtime.pclntab": + pclntabSym = sym + case "runtime.epclntab": + epclntabSym = sym + } + } + + if moddataSym == nil { + t.Fatalf("could not find symbol %s", moddataSymName) + } + if gofuncSym == nil { + t.Fatalf("could not find symbol %s", gofuncSymName) + } + if pclntabSym == nil { + t.Fatal("could not find symbol runtime.pclntab") + } + if epclntabSym == nil { + t.Fatal("could not find symbol runtime.epclntab") + } + + sec := pf.Sections[moddataSym.SectionNumber-1] + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + moddataBytes = data[moddataSym.Value:] + moddataAddr = uint64(sec.VirtualAddress + moddataSym.Value) + + sec = pf.Sections[gofuncSym.SectionNumber-1] + gofuncAddr = uint64(sec.VirtualAddress + gofuncSym.Value) + + if pclntabSym.SectionNumber != epclntabSym.SectionNumber { + t.Fatalf("runtime.pclntab section %d != runtime.epclntab section %d", pclntabSym.SectionNumber, epclntabSym.SectionNumber) + } + sec = pf.Sections[pclntabSym.SectionNumber-1] + data, err = sec.Data() + if err != nil { + t.Fatal(err) + } + pclntab = data[pclntabSym.Value:epclntabSym.Value] + pclntabAddr = uint64(sec.VirtualAddress + pclntabSym.Value) + pclntabEnd = uint64(sec.VirtualAddress + epclntabSym.Value) + + case xf != nil: + defer xf.Close() + + for _, sym := range xf.Symbols { + switch sym.Name { + case moddataSymName: + moddataAddr = sym.Value + case gofuncSymName: + gofuncAddr = sym.Value + } + } + + for _, sec := range xf.Sections { + if sec.Name == ".go.pclntab" { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + pclntab = data + pclntabAddr = sec.VirtualAddress + pclntabEnd = sec.VirtualAddress + sec.Size + } + if moddataAddr >= sec.VirtualAddress && moddataAddr < sec.VirtualAddress+sec.Size { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + moddataBytes = data[moddataAddr-sec.VirtualAddress:] + } + } + + default: + panic("can't happen") + } + + if len(pclntab) == 0 { + t.Fatal("could not find pclntab section") + } + if moddataAddr == 0 { + t.Fatalf("could not find %s symbol", moddataSymName) + } + if gofuncAddr == 0 { + t.Fatalf("could not find %s symbol", gofuncSymName) + } + if gofuncAddr < pclntabAddr || gofuncAddr >= pclntabEnd { + t.Fatalf("%s out of range: value %#x not between %#x and %#x", gofuncSymName, gofuncAddr, pclntabAddr, pclntabEnd) + } + if len(moddataBytes) == 0 { + t.Fatal("could not find module data") + } + + // What a slice looks like in the object file. + type moddataSlice struct { + addr uintptr + len int + cap int + } + + // This needs to match the struct defined in runtime/symtab.go, + // and written out by (*Link).symtab. + // This is not the complete moddata struct, only what we need here. + type moddataType struct { + pcHeader uintptr + funcnametab moddataSlice + cutab moddataSlice + filetab moddataSlice + pctab moddataSlice + pclntable moddataSlice + ftab moddataSlice + findfunctab uintptr + minpc, maxpc uintptr + + text, etext uintptr + noptrdata, enoptrdata uintptr + data, edata uintptr + bss, ebss uintptr + noptrbss, enoptrbss uintptr + covctrs, ecovctrs uintptr + end, gcdata, gcbss uintptr + types, etypes uintptr + rodata uintptr + gofunc uintptr + } + + // The executable is on the same system as we are running, + // so the sizes and alignments should match. + // But moddataBytes itself may not be aligned as needed. + // Copy to a variable to ensure alignment. + var moddata moddataType + setValueFromBytes(&moddata, moddataBytes) + + ftabAddr := uint64(moddata.ftab.addr) - imageBase + if ftabAddr < pclntabAddr || ftabAddr >= pclntabEnd { + t.Fatalf("ftab address %#x not between %#x and %#x", ftabAddr, pclntabAddr, pclntabEnd) + } + + // From runtime/symtab.go and the linker function writePCToFunc. + type functab struct { + entryoff uint32 + funcoff uint32 + } + // The ftab slice in moddata has one extra entry used to record + // the final PC. + ftabLen := moddata.ftab.len - 1 + ftab := make([]functab, ftabLen) + copy(ftab, unsafe.Slice((*functab)(unsafe.Pointer(&pclntab[ftabAddr-pclntabAddr])), ftabLen)) + + ftabBase := uint64(moddata.pclntable.addr) - imageBase + + // From runtime/runtime2.go _func and the linker function writeFuncs. + type funcEntry struct { + entryOff uint32 + nameOff int32 + + args int32 + deferreturn uint32 + + pcsp uint32 + pcfile uint32 + pcln uint32 + npcdata uint32 + cuOffset uint32 + startLine int32 + funcID abi.FuncID + flag abi.FuncFlag + _ [1]byte + nfuncdata uint8 + } + + for i, ftabEntry := range ftab { + funcAddr := ftabBase + uint64(ftabEntry.funcoff) + if funcAddr < pclntabAddr || funcAddr >= pclntabEnd { + t.Errorf("ftab entry %d address %#x not between %#x and %#x", i, funcAddr, pclntabAddr, pclntabEnd) + continue + } + + var fe funcEntry + setValueFromBytes(&fe, pclntab[funcAddr-pclntabAddr:]) + + funcdataVals := funcAddr + uint64(unsafe.Sizeof(fe)) + uint64(fe.npcdata*4) + for j := range fe.nfuncdata { + var funcdataVal uint32 + setValueFromBytes(&funcdataVal, pclntab[funcdataVals+uint64(j)*4-pclntabAddr:]) + if funcdataVal == ^uint32(0) { + continue + } + funcdataAddr := gofuncAddr + uint64(funcdataVal) + if funcdataAddr < pclntabAddr || funcdataAddr >= pclntabEnd { + t.Errorf("ftab entry %d funcdata %d address %#x not between %#x and %#x", i, j, funcdataAddr, pclntabAddr, pclntabEnd) + } + } + } + + if uint64(moddata.findfunctab)-imageBase < pclntabAddr || uint64(moddata.findfunctab)-imageBase >= pclntabEnd { + t.Errorf("findfunctab address %#x not between %#x and %#x", moddata.findfunctab, pclntabAddr, pclntabEnd) + } +} + +// Test that moduledata winds up in its own .go.module section. +func TestModuledataPlacement(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(trivialSrc), 0o444); err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "x.exe") + cmd := goCmd(t, "build", "-o", exe, src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build failed; %v, output:\n%s", err, out) + } + + ef, _ := elf.Open(exe) + mf, _ := macho.Open(exe) + pf, _ := pe.Open(exe) + xf, _ := xcoff.Open(exe) + // TODO: plan9 + if ef == nil && mf == nil && pf == nil && xf == nil { + t.Skip("unrecognized executable file format") + } + + const moddataSymName = "runtime.firstmoduledata" + switch { + case ef != nil: + defer ef.Close() + + syms, err := ef.Symbols() + if err != nil { + t.Fatal(err) + } + for _, sym := range syms { + if sym.Name == moddataSymName { + sec := ef.Sections[sym.Section] + if sec.Name != ".go.module" { + t.Errorf("moduledata in section %s, not .go.module", sec.Name) + } + if sym.Value != sec.Addr { + t.Errorf("moduledata address %#x != section start address %#x", sym.Value, sec.Addr) + } + break + } + } + + case mf != nil: + defer mf.Close() + + for _, sym := range mf.Symtab.Syms { + if sym.Name == moddataSymName { + if sym.Sect == 0 { + t.Error("moduledata not in a section") + } else { + sec := mf.Sections[sym.Sect-1] + if sec.Name != "__go_module" { + t.Errorf("moduledata in section %s, not __go.module", sec.Name) + } + if sym.Value != sec.Addr { + t.Errorf("moduledata address %#x != section start address %#x", sym.Value, sec.Addr) + } + } + break + } + } + + case pf != nil: + defer pf.Close() + + // On Windows all the Go specific sections seem to + // get stuffed into a few Windows sections, + // so there is nothing to test here. + + case xf != nil: + defer xf.Close() + + for _, sym := range xf.Symbols { + if sym.Name == moddataSymName { + if sym.SectionNumber == 0 { + t.Errorf("moduledata not in a section") + } else { + sec := xf.Sections[sym.SectionNumber-1] + if sec.Name != ".go.module" { + t.Errorf("moduledata in section %s, not .go.module", sec.Name) + } + if sym.Value != sec.VirtualAddress { + t.Errorf("moduledata address %#x != section start address %#x", sym.Value, sec.VirtualAddress) + } + } + break + } + } + } +} diff --git a/src/cmd/link/linkbig_test.go b/src/cmd/link/linkbig_test.go index ae9a38fa7ba..36c01c3b4af 100644 --- a/src/cmd/link/linkbig_test.go +++ b/src/cmd/link/linkbig_test.go @@ -82,7 +82,7 @@ func TestLargeText(t *testing.T) { } // Build and run with internal linking. - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "bigtext") + cmd := goCmd(t, "build", "-o", "bigtext") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { @@ -96,7 +96,7 @@ func TestLargeText(t *testing.T) { } // Build and run with external linking - cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "bigtext", "-ldflags", "-linkmode=external") + cmd = goCmd(t, "build", "-o", "bigtext", "-ldflags", "-linkmode=external") cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err != nil { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go index 2f5d97e89a6..c5c7cc0e10c 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go @@ -151,11 +151,11 @@ func (d *llvmSymbolizer) readCodeFrames() ([]plugin.Frame, error) { Address string `json:"Address"` ModuleName string `json:"ModuleName"` Symbol []struct { - Line int `json:"Line"` - Column int `json:"Column"` - FunctionName string `json:"FunctionName"` - FileName string `json:"FileName"` - StartLine int `json:"StartLine"` + Line int `json:"Line"` + Column int `json:"Column"` + FunctionName string `json:"FunctionName"` + FileName string `json:"FileName"` + StartLine int `json:"StartLine"` } `json:"Symbol"` } if err := json.Unmarshal([]byte(line), &frame); err != nil { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/config.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/config.go index 184de397ef5..20d169c8f68 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/config.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/config.go @@ -164,7 +164,7 @@ func init() { def := defaultConfig() configFieldMap = map[string]configField{} - t := reflect.TypeOf(config{}) + t := reflect.TypeFor[config]() for i, n := 0, t.NumField(); i < n; i++ { field := t.Field(i) js := strings.Split(field.Tag.Get("json"), ",") diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html index 5405a0be955..4d0f198a695 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html @@ -10,7 +10,7 @@