diff --git a/api/next/73794.txt b/api/next/73794.txt new file mode 100644 index 00000000000000..4018c149ecbecd --- /dev/null +++ b/api/next/73794.txt @@ -0,0 +1 @@ +pkg bytes, method (*Buffer) Peek(int) ([]uint8, error) #73794 diff --git a/doc/next/6-stdlib/99-minor/bytes/73794.md b/doc/next/6-stdlib/99-minor/bytes/73794.md new file mode 100644 index 00000000000000..a44dfc10e693a6 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/bytes/73794.md @@ -0,0 +1,2 @@ +The new [Buffer.Peek] method returns the next n bytes from the buffer without +advancing it. diff --git a/src/bytes/buffer.go b/src/bytes/buffer.go index 9684513942da88..6cb4d6a8f66ebf 100644 --- a/src/bytes/buffer.go +++ b/src/bytes/buffer.go @@ -77,6 +77,18 @@ func (b *Buffer) String() string { return string(b.buf[b.off:]) } +// Peek returns the next n bytes without advancing the buffer. +// If Peek returns fewer than n bytes, it also returns [io.EOF]. +// The slice is only valid until the next call to a read or write method. +// The slice aliases the buffer content at least until the next buffer modification, +// so immediate changes to the slice will affect the result of future reads. +func (b *Buffer) Peek(n int) ([]byte, error) { + if b.Len() < n { + return b.buf[b.off:], io.EOF + } + return b.buf[b.off : b.off+n], nil +} + // empty reports whether the unread portion of the buffer is empty. func (b *Buffer) empty() bool { return len(b.buf) <= b.off } diff --git a/src/bytes/buffer_test.go b/src/bytes/buffer_test.go index b46ba1204eb806..49f601696497e0 100644 --- a/src/bytes/buffer_test.go +++ b/src/bytes/buffer_test.go @@ -531,6 +531,39 @@ func TestReadString(t *testing.T) { } } +var peekTests = []struct { + buffer string + skip int + n int + expected string + err error +}{ + {"", 0, 0, "", nil}, + {"aaa", 0, 3, "aaa", nil}, + {"foobar", 0, 2, "fo", nil}, + {"a", 0, 2, "a", io.EOF}, + {"helloworld", 4, 3, "owo", nil}, + {"helloworld", 5, 5, "world", nil}, + {"helloworld", 5, 6, "world", io.EOF}, +} + +func TestPeek(t *testing.T) { + for _, test := range peekTests { + buf := NewBufferString(test.buffer) + buf.Next(test.skip) + bytes, err := buf.Peek(test.n) + if string(bytes) != test.expected { + t.Errorf("expected %q, got %q", test.expected, bytes) + } + if err != test.err { + t.Errorf("expected error %v, got %v", test.err, err) + } + if buf.Len() != len(test.buffer)-test.skip { + t.Errorf("bad length after peek: %d, want %d", buf.Len(), len(test.buffer)-test.skip) + } + } +} + func BenchmarkReadString(b *testing.B) { const n = 32 << 10 diff --git a/src/bytes/bytes_test.go b/src/bytes/bytes_test.go index f18915c879e097..9547ede312fc0f 100644 --- a/src/bytes/bytes_test.go +++ b/src/bytes/bytes_test.go @@ -1224,7 +1224,7 @@ func TestMap(t *testing.T) { // Run a couple of awful growth/shrinkage tests a := tenRunes('a') - // 1. Grow. This triggers two reallocations in Map. + // 1. Grow. This triggers two reallocations in Map. maxRune := func(r rune) rune { return unicode.MaxRune } m := Map(maxRune, []byte(a)) expect := tenRunes(unicode.MaxRune) diff --git a/src/cmd/asm/internal/asm/testdata/arm64.s b/src/cmd/asm/internal/asm/testdata/arm64.s index 109a3d8316678b..ae10f347bba101 100644 --- a/src/cmd/asm/internal/asm/testdata/arm64.s +++ b/src/cmd/asm/internal/asm/testdata/arm64.s @@ -630,6 +630,8 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 FMOVS F1, 0x44332211(R2) // FMOVS F1, 1144201745(R2) FMOVD F1, 0x1007000(R2) // FMOVD F1, 16805888(R2) FMOVD F1, 0x44332211(R2) // FMOVD F1, 1144201745(R2) + FMOVQ F1, 0x1003000(R2) // FMOVQ F1, 16789504(R2) + FMOVQ F1, 0x44332211(R2) // FMOVQ F1, 1144201745(R2) MOVB 0x1000000(R1), R2 // MOVB 16777216(R1), R2 MOVB 0x44332211(R1), R2 // MOVB 1144201745(R1), R2 @@ -643,6 +645,8 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8 FMOVS 0x44332211(R1), F2 // FMOVS 1144201745(R1), F2 FMOVD 0x1000000(R1), F2 // FMOVD 16777216(R1), F2 FMOVD 0x44332211(R1), F2 // FMOVD 1144201745(R1), F2 + FMOVQ 0x1000000(R1), F2 // FMOVQ 16777216(R1), F2 + FMOVQ 0x44332211(R1), F2 // FMOVQ 1144201745(R1), F2 // shifted or extended register offset. MOVD (R2)(R6.SXTW), R4 // 44c866f8 diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index 6e2a86969d54cb..e0619f8ecddd4c 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -533,12 +533,18 @@ lable2: XVMOVQ X28.V[3], X8 // 88ef0377 XVMOVQ X27.V[0], X9 // 69e30377 - //Move vector element to vector. + // Move vector element to vector. VMOVQ V1.B[3], V9.B16 // 298cf772 VMOVQ V2.H[2], V8.H8 // 48c8f772 VMOVQ V3.W[1], V7.W4 // 67e4f772 VMOVQ V4.V[0], V6.V2 // 86f0f772 + // Move vector register to vector register. + VMOVQ V1, V9 // 29002d73 + VMOVQ V2, V8 // 48002d73 + XVMOVQ X3, X7 // 67002d77 + XVMOVQ X4, X6 // 86002d77 + // Load data from memory and broadcast to each element of a vector register: VMOVQ offset(Rj), . VMOVQ (R4), V0.B16 // 80008030 VMOVQ 1(R4), V0.B16 // 80048030 @@ -1017,6 +1023,12 @@ lable2: XVSHUF4IV $8, X1, X2 // 22209c77 XVSHUF4IV $15, X1, X2 // 223c9c77 + // VPERMIW, XVPERMI{W,V,Q} instructions + VPERMIW $0x1B, V1, V2 // VPERMIW $27, V1, V2 // 226ce473 + XVPERMIW $0x2B, X1, X2 // XVPERMIW $43, X1, X2 // 22ace477 + XVPERMIV $0x3B, X1, X2 // XVPERMIV $59, X1, X2 // 22ece877 + XVPERMIQ $0x4B, X1, X2 // XVPERMIQ $75, X1, X2 // 222ced77 + // [X]VSETEQZ.V, [X]VSETNEZ.V VSETEQV V1, FCC0 // 20989c72 VSETNEV V1, FCC0 // 209c9c72 diff --git a/src/cmd/cgo/doc.go b/src/cmd/cgo/doc.go index ef5272299bbf07..7e8486874ef142 100644 --- a/src/cmd/cgo/doc.go +++ b/src/cmd/cgo/doc.go @@ -127,7 +127,7 @@ environment variable when running the go tool: set it to 1 to enable the use of cgo, and to 0 to disable it. The go tool will set the build constraint "cgo" if cgo is enabled. The special import "C" implies the "cgo" build constraint, as though the file also said -"//go:build cgo". Therefore, if cgo is disabled, files that import +"//go:build cgo". Therefore, if cgo is disabled, files that import "C" will not be built by the go tool. (For more about build constraints see https://golang.org/pkg/go/build/#hdr-Build_Constraints). diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 886ddf2d461b65..d1b629057ab4a8 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -1056,7 +1056,7 @@ func (p *Package) rewriteCall(f *File, call *Call) (string, bool) { func (p *Package) needsPointerCheck(f *File, t ast.Expr, arg ast.Expr) bool { // An untyped nil does not need a pointer check, and when // _cgoCheckPointer returns the untyped nil the type assertion we - // are going to insert will fail. Easier to just skip nil arguments. + // are going to insert will fail. Easier to just skip nil arguments. // TODO: Note that this fails if nil is shadowed. if id, ok := arg.(*ast.Ident); ok && id.Name == "nil" { return false @@ -3010,7 +3010,7 @@ func (c *typeConv) FuncType(dtype *dwarf.FuncType, pos token.Pos) *FuncType { for i, f := range dtype.ParamType { // gcc's DWARF generator outputs a single DotDotDotType parameter for // function pointers that specify no parameters (e.g. void - // (*__cgo_0)()). Treat this special case as void. This case is + // (*__cgo_0)()). Treat this special case as void. This case is // invalid according to ISO C anyway (i.e. void (*__cgo_1)(...) is not // legal). if _, ok := f.(*dwarf.DotDotDotType); ok && i == 0 { @@ -3081,7 +3081,7 @@ func (c *typeConv) Struct(dt *dwarf.StructType, pos token.Pos) (expr *ast.Struct off := int64(0) // Rename struct fields that happen to be named Go keywords into - // _{keyword}. Create a map from C ident -> Go ident. The Go ident will + // _{keyword}. Create a map from C ident -> Go ident. The Go ident will // be mangled. Any existing identifier that already has the same name on // the C-side will cause the Go-mangled version to be prefixed with _. // (e.g. in a struct with fields '_type' and 'type', the latter would be @@ -3309,7 +3309,7 @@ func godefsFields(fld []*ast.Field) { // fieldPrefix returns the prefix that should be removed from all the // field names when generating the C or Go code. For generated // C, we leave the names as is (tv_sec, tv_usec), since that's what -// people are used to seeing in C. For generated Go code, such as +// people are used to seeing in C. For generated Go code, such as // package syscall's data structures, we drop a common prefix // (so sec, usec, which will get turned into Sec, Usec for exporting). func fieldPrefix(fld []*ast.Field) string { @@ -3456,7 +3456,7 @@ func (c *typeConv) badCFType(dt *dwarf.TypedefType) bool { // Tagged pointer support // Low-bit set means tagged object, next 3 bits (currently) // define the tagged object class, next 4 bits are for type -// information for the specific tagged object class. Thus, +// information for the specific tagged object class. Thus, // the low byte is for type info, and the rest of a pointer // (32 or 64-bit) is for payload, whatever the tagged class. // diff --git a/src/cmd/cgo/internal/test/buildid_linux.go b/src/cmd/cgo/internal/test/buildid_linux.go index 84d3edb664eb25..7e0fd0fd126a02 100644 --- a/src/cmd/cgo/internal/test/buildid_linux.go +++ b/src/cmd/cgo/internal/test/buildid_linux.go @@ -4,9 +4,9 @@ package cgotest -// Test that we have no more than one build ID. In the past we used +// Test that we have no more than one build ID. In the past we used // to generate a separate build ID for each package using cgo, and the -// linker concatenated them all. We don't want that--we only want +// linker concatenated them all. We don't want that--we only want // one. import ( @@ -42,7 +42,7 @@ sections: for len(d) > 0 { // ELF standards differ as to the sizes in - // note sections. Both the GNU linker and + // note sections. Both the GNU linker and // gold always generate 32-bit sizes, so that // is what we assume here. diff --git a/src/cmd/cgo/internal/test/callback.go b/src/cmd/cgo/internal/test/callback.go index 478bf8294af3a5..8f8dd8fded6f15 100644 --- a/src/cmd/cgo/internal/test/callback.go +++ b/src/cmd/cgo/internal/test/callback.go @@ -40,7 +40,7 @@ func nestedCall(f func()) { callbackMutex.Unlock() // Pass the address of i because the C function was written to - // take a pointer. We could pass an int if we felt like + // take a pointer. We could pass an int if we felt like // rewriting the C code. C.callback(unsafe.Pointer(&i)) diff --git a/src/cmd/cgo/internal/test/gcc68255/a.go b/src/cmd/cgo/internal/test/gcc68255/a.go index e106dee3ec023d..cc4804b90bd122 100644 --- a/src/cmd/cgo/internal/test/gcc68255/a.go +++ b/src/cmd/cgo/internal/test/gcc68255/a.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Test that it's OK to have C code that does nothing other than -// initialize a global variable. This used to fail with gccgo. +// initialize a global variable. This used to fail with gccgo. package gcc68255 diff --git a/src/cmd/cgo/internal/teststdio/testdata/fib.go b/src/cmd/cgo/internal/teststdio/testdata/fib.go index 96173683353151..69147880c20df2 100644 --- a/src/cmd/cgo/internal/teststdio/testdata/fib.go +++ b/src/cmd/cgo/internal/teststdio/testdata/fib.go @@ -5,7 +5,7 @@ //go:build test_run // Compute Fibonacci numbers with two goroutines -// that pass integers back and forth. No actual +// that pass integers back and forth. No actual // concurrency, just threads and synchronization // and foreign code on multiple pthreads. diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go index 5e08427daf9cc2..955d64b9569406 100644 --- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -72,8 +72,8 @@ type File struct { ExpFunc []*ExpFunc // exported functions for this file Name map[string]*Name // map from Go name to Name NamePos map[*Name]token.Pos // map from Name to position of the first reference - NoCallbacks map[string]bool // C function names that with #cgo nocallback directive - NoEscapes map[string]bool // C function names that with #cgo noescape directive + NoCallbacks map[string]bool // C function names with #cgo nocallback directive + NoEscapes map[string]bool // C function names with #cgo noescape directive Edit *edit.Buffer debugs []*debug // debug data from iterations of gccDebug. Initialized by File.loadDebug. diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index a2bcdf89c5ad44..394e766d4e5328 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -1144,6 +1144,10 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) { if !p.hasPointer(nil, atype, false) { return } + + // Use the export'ed file/line in error messages. + pos := fset.Position(exp.Func.Pos()) + fmt.Fprintf(fgo2, "//line %s:%d\n", pos.Filename, pos.Line) fmt.Fprintf(fgo2, "\t_cgoCheckResult(a.r%d)\n", i) }) } diff --git a/src/cmd/compile/internal/ssagen/intrinsics.go b/src/cmd/compile/internal/ssagen/intrinsics.go index bf9e71c1701d08..190c4840ce9aad 100644 --- a/src/cmd/compile/internal/ssagen/intrinsics.go +++ b/src/cmd/compile/internal/ssagen/intrinsics.go @@ -1603,10 +1603,10 @@ func initIntrinsics(cfg *intrinsicBuildConfig) { }, sys.AMD64) - /******** crypto/subtle ********/ - // We implement a superset of the ConstantTimeSelect promise: - // ConstantTimeSelect returns x if v != 0 and y if v == 0. - add("crypto/subtle", "ConstantTimeSelect", + /******** crypto/internal/constanttime ********/ + // We implement a superset of the Select promise: + // Select returns x if v != 0 and y if v == 0. + add("crypto/internal/constanttime", "Select", func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { v, x, y := args[0], args[1], args[2] @@ -1627,7 +1627,7 @@ func initIntrinsics(cfg *intrinsicBuildConfig) { return s.newValue3(ssa.OpCondSelect, types.Types[types.TINT], x, y, check) }, sys.ArchAMD64, sys.ArchARM64, sys.ArchLoong64, sys.ArchPPC64, sys.ArchPPC64LE, sys.ArchWasm) // all with CMOV support. - add("crypto/subtle", "constantTimeBoolToUint8", + add("crypto/internal/constanttime", "boolToUint8", func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { return s.newValue1(ssa.OpCvtBoolToUint8, types.Types[types.TUINT8], args[0]) }, diff --git a/src/cmd/compile/internal/ssagen/intrinsics_test.go b/src/cmd/compile/internal/ssagen/intrinsics_test.go index 9311f843454c36..782426215c9fff 100644 --- a/src/cmd/compile/internal/ssagen/intrinsics_test.go +++ b/src/cmd/compile/internal/ssagen/intrinsics_test.go @@ -42,7 +42,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"386", "math/bits", "TrailingZeros8"}: struct{}{}, {"386", "runtime", "KeepAlive"}: struct{}{}, {"386", "runtime", "slicebytetostringtmp"}: struct{}{}, - {"386", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"386", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"amd64", "internal/runtime/atomic", "And"}: struct{}{}, {"amd64", "internal/runtime/atomic", "And32"}: struct{}{}, {"amd64", "internal/runtime/atomic", "And64"}: struct{}{}, @@ -189,8 +189,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"amd64", "sync/atomic", "SwapUint32"}: struct{}{}, {"amd64", "sync/atomic", "SwapUint64"}: struct{}{}, {"amd64", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"amd64", "crypto/subtle", "ConstantTimeSelect"}: struct{}{}, - {"amd64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"amd64", "crypto/internal/constanttime", "Select"}: struct{}{}, + {"amd64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"arm", "internal/runtime/sys", "Bswap32"}: struct{}{}, {"arm", "internal/runtime/sys", "Bswap64"}: struct{}{}, {"arm", "internal/runtime/sys", "GetCallerPC"}: struct{}{}, @@ -219,7 +219,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"arm", "math/bits", "TrailingZeros8"}: struct{}{}, {"arm", "runtime", "KeepAlive"}: struct{}{}, {"arm", "runtime", "slicebytetostringtmp"}: struct{}{}, - {"arm", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"arm", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"arm64", "internal/runtime/atomic", "And"}: struct{}{}, {"arm64", "internal/runtime/atomic", "And32"}: struct{}{}, {"arm64", "internal/runtime/atomic", "And64"}: struct{}{}, @@ -364,8 +364,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"arm64", "sync/atomic", "SwapUint32"}: struct{}{}, {"arm64", "sync/atomic", "SwapUint64"}: struct{}{}, {"arm64", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"arm64", "crypto/subtle", "ConstantTimeSelect"}: struct{}{}, - {"arm64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"arm64", "crypto/internal/constanttime", "Select"}: struct{}{}, + {"arm64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"loong64", "internal/runtime/atomic", "And"}: struct{}{}, {"loong64", "internal/runtime/atomic", "And32"}: struct{}{}, {"loong64", "internal/runtime/atomic", "And64"}: struct{}{}, @@ -512,8 +512,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"loong64", "sync/atomic", "SwapUint32"}: struct{}{}, {"loong64", "sync/atomic", "SwapUint64"}: struct{}{}, {"loong64", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"loong64", "crypto/subtle", "ConstantTimeSelect"}: struct{}{}, - {"loong64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"loong64", "crypto/internal/constanttime", "Select"}: struct{}{}, + {"loong64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"mips", "internal/runtime/atomic", "And"}: struct{}{}, {"mips", "internal/runtime/atomic", "And8"}: struct{}{}, {"mips", "internal/runtime/atomic", "Cas"}: struct{}{}, @@ -585,7 +585,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"mips", "sync/atomic", "SwapInt32"}: struct{}{}, {"mips", "sync/atomic", "SwapUint32"}: struct{}{}, {"mips", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"mips", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"mips", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"mips64", "internal/runtime/atomic", "And"}: struct{}{}, {"mips64", "internal/runtime/atomic", "And8"}: struct{}{}, {"mips64", "internal/runtime/atomic", "Cas"}: struct{}{}, @@ -674,7 +674,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"mips64", "sync/atomic", "SwapUint32"}: struct{}{}, {"mips64", "sync/atomic", "SwapUint64"}: struct{}{}, {"mips64", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"mips64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"mips64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"mips64le", "internal/runtime/atomic", "And"}: struct{}{}, {"mips64le", "internal/runtime/atomic", "And8"}: struct{}{}, {"mips64le", "internal/runtime/atomic", "Cas"}: struct{}{}, @@ -763,7 +763,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"mips64le", "sync/atomic", "SwapUint32"}: struct{}{}, {"mips64le", "sync/atomic", "SwapUint64"}: struct{}{}, {"mips64le", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"mips64le", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"mips64le", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"mipsle", "internal/runtime/atomic", "And"}: struct{}{}, {"mipsle", "internal/runtime/atomic", "And8"}: struct{}{}, {"mipsle", "internal/runtime/atomic", "Cas"}: struct{}{}, @@ -835,7 +835,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"mipsle", "sync/atomic", "SwapInt32"}: struct{}{}, {"mipsle", "sync/atomic", "SwapUint32"}: struct{}{}, {"mipsle", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"mipsle", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"mipsle", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"ppc64", "internal/runtime/atomic", "And"}: struct{}{}, {"ppc64", "internal/runtime/atomic", "And8"}: struct{}{}, {"ppc64", "internal/runtime/atomic", "Cas"}: struct{}{}, @@ -960,8 +960,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"ppc64", "sync/atomic", "SwapUint32"}: struct{}{}, {"ppc64", "sync/atomic", "SwapUint64"}: struct{}{}, {"ppc64", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"ppc64", "crypto/subtle", "ConstantTimeSelect"}: struct{}{}, - {"ppc64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"ppc64", "crypto/internal/constanttime", "Select"}: struct{}{}, + {"ppc64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"ppc64le", "internal/runtime/atomic", "And"}: struct{}{}, {"ppc64le", "internal/runtime/atomic", "And8"}: struct{}{}, {"ppc64le", "internal/runtime/atomic", "Cas"}: struct{}{}, @@ -1086,8 +1086,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"ppc64le", "sync/atomic", "SwapUint32"}: struct{}{}, {"ppc64le", "sync/atomic", "SwapUint64"}: struct{}{}, {"ppc64le", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"ppc64le", "crypto/subtle", "ConstantTimeSelect"}: struct{}{}, - {"ppc64le", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"ppc64le", "crypto/internal/constanttime", "Select"}: struct{}{}, + {"ppc64le", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"riscv64", "internal/runtime/atomic", "And"}: struct{}{}, {"riscv64", "internal/runtime/atomic", "And8"}: struct{}{}, {"riscv64", "internal/runtime/atomic", "Cas"}: struct{}{}, @@ -1208,7 +1208,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"riscv64", "sync/atomic", "SwapUint32"}: struct{}{}, {"riscv64", "sync/atomic", "SwapUint64"}: struct{}{}, {"riscv64", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"riscv64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"riscv64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"s390x", "internal/runtime/atomic", "And"}: struct{}{}, {"s390x", "internal/runtime/atomic", "And8"}: struct{}{}, {"s390x", "internal/runtime/atomic", "Cas"}: struct{}{}, @@ -1327,7 +1327,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"s390x", "sync/atomic", "SwapUint32"}: struct{}{}, {"s390x", "sync/atomic", "SwapUint64"}: struct{}{}, {"s390x", "sync/atomic", "SwapUintptr"}: struct{}{}, - {"s390x", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"s390x", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, {"wasm", "internal/runtime/sys", "GetCallerPC"}: struct{}{}, {"wasm", "internal/runtime/sys", "GetCallerSP"}: struct{}{}, {"wasm", "internal/runtime/sys", "GetClosurePtr"}: struct{}{}, @@ -1363,8 +1363,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"wasm", "math/bits", "TrailingZeros8"}: struct{}{}, {"wasm", "runtime", "KeepAlive"}: struct{}{}, {"wasm", "runtime", "slicebytetostringtmp"}: struct{}{}, - {"wasm", "crypto/subtle", "ConstantTimeSelect"}: struct{}{}, - {"wasm", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{}, + {"wasm", "crypto/internal/constanttime", "Select"}: struct{}{}, + {"wasm", "crypto/internal/constanttime", "boolToUint8"}: struct{}{}, } func TestIntrinsics(t *testing.T) { diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index ae7d57566f7e0d..db2ffb5752f1ce 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -7797,7 +7797,7 @@ func callTargetLSym(callee *ir.Name) *obj.LSym { } // deferStructFnField is the field index of _defer.fn. -const deferStructFnField = 4 +const deferStructFnField = 3 var deferType *types.Type @@ -7817,7 +7817,6 @@ func deferstruct() *types.Type { makefield("heap", types.Types[types.TBOOL]), makefield("rangefunc", types.Types[types.TBOOL]), makefield("sp", types.Types[types.TUINTPTR]), - makefield("pc", types.Types[types.TUINTPTR]), // Note: the types here don't really matter. Defer structures // are always scanned explicitly during stack copying and GC, // so we make them uintptr type even though they are real pointers. diff --git a/src/cmd/go/internal/clean/clean.go b/src/cmd/go/internal/clean/clean.go index ae744e13bc79b5..51581c27e16148 100644 --- a/src/cmd/go/internal/clean/clean.go +++ b/src/cmd/go/internal/clean/clean.go @@ -121,7 +121,7 @@ func init() { func runClean(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if len(args) > 0 { cacheFlag := "" switch { @@ -143,7 +143,7 @@ func runClean(ctx context.Context, cmd *base.Command, args []string) { // either the flags and arguments explicitly imply a package, // or no other target (such as a cache) was requested to be cleaned. cleanPkg := len(args) > 0 || cleanI || cleanR - if (!modload.Enabled(moduleLoaderState) || modload.HasModRoot(moduleLoaderState)) && + if (!moduleLoaderState.Enabled() || moduleLoaderState.HasModRoot()) && !cleanCache && !cleanModcache && !cleanTestcache && !cleanFuzzcache { cleanPkg = true } diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index f600a354727ae1..d345a36863232e 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -192,12 +192,12 @@ func findEnv(env []cfg.EnvVar, name string) string { func ExtraEnvVars(loaderstate *modload.State) []cfg.EnvVar { gomod := "" modload.Init(loaderstate) - if modload.HasModRoot(loaderstate) { - gomod = modload.ModFilePath(loaderstate) - } else if modload.Enabled(loaderstate) { + if loaderstate.HasModRoot() { + gomod = loaderstate.ModFilePath() + } else if loaderstate.Enabled() { gomod = os.DevNull } - modload.InitWorkfile(loaderstate) + loaderstate.InitWorkfile() gowork := modload.WorkFilePath(loaderstate) // As a special case, if a user set off explicitly, report that in GOWORK. if cfg.Getenv("GOWORK") == "off" { diff --git a/src/cmd/go/internal/fmtcmd/fmt.go b/src/cmd/go/internal/fmtcmd/fmt.go index cd689b510deb19..fe356bdc081456 100644 --- a/src/cmd/go/internal/fmtcmd/fmt.go +++ b/src/cmd/go/internal/fmtcmd/fmt.go @@ -61,7 +61,7 @@ func runFmt(ctx context.Context, cmd *base.Command, args []string) { baseGofmtArgLen := gofmtArgLen for _, pkg := range load.PackagesAndErrors(moduleLoaderState, ctx, load.PackageOpts{}, args) { - if modload.Enabled(moduleLoaderState) && pkg.Module != nil && !pkg.Module.Main { + if moduleLoaderState.Enabled() && pkg.Module != nil && !pkg.Module.Main { if !printed { fmt.Fprintf(os.Stderr, "go: not formatting packages in dependency modules\n") printed = true diff --git a/src/cmd/go/internal/generate/generate.go b/src/cmd/go/internal/generate/generate.go index 2a5a5a6764af95..59142859c1f445 100644 --- a/src/cmd/go/internal/generate/generate.go +++ b/src/cmd/go/internal/generate/generate.go @@ -183,7 +183,7 @@ func init() { func runGenerate(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if generateRunFlag != "" { var err error @@ -206,7 +206,7 @@ func runGenerate(ctx context.Context, cmd *base.Command, args []string) { printed := false pkgOpts := load.PackageOpts{IgnoreImports: true} for _, pkg := range load.PackagesAndErrors(moduleLoaderState, ctx, pkgOpts, args) { - if modload.Enabled(moduleLoaderState) && pkg.Module != nil && !pkg.Module.Main { + if moduleLoaderState.Enabled() && pkg.Module != nil && !pkg.Module.Main { if !printed { fmt.Fprintf(os.Stderr, "go: not generating in packages in dependency modules\n") printed = true diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go index 086a8c2ca390cb..81ac4ebaf9cf68 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -420,7 +420,7 @@ var nl = []byte{'\n'} func runList(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if *listFmt != "" && listJson { base.Fatalf("go list -f cannot be used with -json") @@ -428,7 +428,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { if *listReuse != "" && !*listM { base.Fatalf("go list -reuse cannot be used without -m") } - if *listReuse != "" && modload.HasModRoot(moduleLoaderState) { + if *listReuse != "" && moduleLoaderState.HasModRoot() { base.Fatalf("go list -reuse cannot be used inside a module") } @@ -502,7 +502,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { if cfg.BuildMod == "vendor" { base.Fatalf("go list -retracted cannot be used when vendoring is enabled") } - if !modload.Enabled(moduleLoaderState) { + if !moduleLoaderState.Enabled() { base.Fatalf("go list -retracted can only be used in module-aware mode") } } @@ -526,7 +526,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { base.Fatalf("go list -test cannot be used with -m") } - if modload.Init(moduleLoaderState); !modload.Enabled(moduleLoaderState) { + if modload.Init(moduleLoaderState); !moduleLoaderState.Enabled() { base.Fatalf("go: list -m cannot be used with GO111MODULE=off") } diff --git a/src/cmd/go/internal/load/search.go b/src/cmd/go/internal/load/search.go index 732dc2a5ae4d2f..749a00e8485f4d 100644 --- a/src/cmd/go/internal/load/search.go +++ b/src/cmd/go/internal/load/search.go @@ -57,9 +57,9 @@ func MatchPackage(pattern, cwd string) func(*modload.State, *Package) bool { default: return func(s *modload.State, p *Package) bool { switch { - case pattern == "tool" && modload.Enabled(s): + case pattern == "tool" && s.Enabled(): return s.MainModules.Tools()[p.ImportPath] - case pattern == "work" && modload.Enabled(s): + case pattern == "work" && s.Enabled(): return p.Module != nil && s.MainModules.Contains(p.Module.Path) default: matchPath := pkgpattern.MatchPattern(pattern) diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go index 7544e221d58f81..150d0c88607122 100644 --- a/src/cmd/go/internal/modcmd/download.go +++ b/src/cmd/go/internal/modcmd/download.go @@ -110,14 +110,14 @@ type ModuleJSON struct { func runDownload(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() // Check whether modules are enabled and whether we're in a module. moduleLoaderState.ForceUseModules = true modload.ExplicitWriteGoMod = true haveExplicitArgs := len(args) > 0 - if modload.HasModRoot(moduleLoaderState) || modload.WorkFilePath(moduleLoaderState) != "" { + if moduleLoaderState.HasModRoot() || modload.WorkFilePath(moduleLoaderState) != "" { modload.LoadModFile(moduleLoaderState, ctx) // to fill MainModules if haveExplicitArgs { @@ -170,7 +170,7 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { } if len(args) == 0 { - if modload.HasModRoot(moduleLoaderState) { + if moduleLoaderState.HasModRoot() { os.Stderr.WriteString("go: no module dependencies to download\n") } else { base.Errorf("go: no modules specified (see 'go help mod download')") @@ -178,7 +178,7 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { base.Exit() } - if *downloadReuse != "" && modload.HasModRoot(moduleLoaderState) { + if *downloadReuse != "" && moduleLoaderState.HasModRoot() { base.Fatalf("go mod download -reuse cannot be used inside a module") } diff --git a/src/cmd/go/internal/modcmd/edit.go b/src/cmd/go/internal/modcmd/edit.go index 2e0d1a6dd8cd02..4cd8d875012c3c 100644 --- a/src/cmd/go/internal/modcmd/edit.go +++ b/src/cmd/go/internal/modcmd/edit.go @@ -233,7 +233,7 @@ func runEdit(ctx context.Context, cmd *base.Command, args []string) { if len(args) == 1 { gomod = args[0] } else { - gomod = modload.ModFilePath(moduleLoaderState) + gomod = moduleLoaderState.ModFilePath() } if *editModule != "" { diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go index 467da99b22961a..307c6ee4b56f15 100644 --- a/src/cmd/go/internal/modcmd/graph.go +++ b/src/cmd/go/internal/modcmd/graph.go @@ -53,7 +53,7 @@ func init() { func runGraph(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if len(args) > 0 { base.Fatalf("go: 'go mod graph' accepts no arguments") diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go index ef44ce41c04c7f..5782f4e79448c6 100644 --- a/src/cmd/go/internal/modcmd/vendor.go +++ b/src/cmd/go/internal/modcmd/vendor.go @@ -67,7 +67,7 @@ func init() { func runVendor(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if modload.WorkFilePath(moduleLoaderState) != "" { base.Fatalf("go: 'go mod vendor' cannot be run in workspace mode. Run 'go work vendor' to vendor the workspace or set 'GOWORK=off' to exit workspace mode.") } @@ -118,7 +118,7 @@ func RunVendor(loaderstate *modload.State, ctx context.Context, vendorE bool, ve includeGoVersions := false isExplicit := map[module.Version]bool{} gv := loaderstate.MainModules.GoVersion(loaderstate) - if gover.Compare(gv, "1.14") >= 0 && (modload.FindGoWork(loaderstate, base.Cwd()) != "" || modload.ModFile(loaderstate).Go != nil) { + if gover.Compare(gv, "1.14") >= 0 && (loaderstate.FindGoWork(base.Cwd()) != "" || modload.ModFile(loaderstate).Go != nil) { // If the Go version is at least 1.14, annotate all explicit 'require' and // 'replace' targets found in the go.mod file so that we can perform a // stronger consistency check when -mod=vendor is set. diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go index e40a05ed531648..d654ba26a4b57c 100644 --- a/src/cmd/go/internal/modcmd/verify.go +++ b/src/cmd/go/internal/modcmd/verify.go @@ -45,7 +45,7 @@ func init() { func runVerify(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if len(args) != 0 { // NOTE(rsc): Could take a module pattern. diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go index 407a19d5c21040..b52b9354c29c72 100644 --- a/src/cmd/go/internal/modcmd/why.go +++ b/src/cmd/go/internal/modcmd/why.go @@ -64,7 +64,7 @@ func init() { func runWhy(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() moduleLoaderState.ForceUseModules = true moduleLoaderState.RootMode = modload.NeedRoot modload.ExplicitWriteGoMod = true // don't write go.mod in ListModules diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index 329fbaec040fc9..c8dc6e29bf69c1 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -308,14 +308,14 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // Allow looking up modules for import paths when outside of a module. // 'go get' is expected to do this, unlike other commands. - modload.AllowMissingModuleImports(moduleLoaderState) + moduleLoaderState.AllowMissingModuleImports() // 'go get' no longer builds or installs packages, so there's nothing to do // if there's no go.mod file. // TODO(#40775): make modload.Init return ErrNoModRoot instead of exiting. // We could handle that here by printing a different message. modload.Init(moduleLoaderState) - if !modload.HasModRoot(moduleLoaderState) { + if !moduleLoaderState.HasModRoot() { base.Fatalf("go: go.mod file not found in current directory or any parent directory.\n" + "\t'go get' is no longer supported outside a module.\n" + "\tTo build and install a command, use 'go install' with a version,\n" + @@ -425,7 +425,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { newReqs := reqsFromGoMod(modload.ModFile(moduleLoaderState)) r.reportChanges(oldReqs, newReqs) - if gowork := modload.FindGoWork(moduleLoaderState, base.Cwd()); gowork != "" { + if gowork := moduleLoaderState.FindGoWork(base.Cwd()); gowork != "" { wf, err := modload.ReadWorkFile(gowork) if err == nil && modload.UpdateWorkGoVersion(wf, moduleLoaderState.MainModules.GoVersion(moduleLoaderState)) { modload.WriteWorkFile(gowork, wf) @@ -575,7 +575,7 @@ func newResolver(loaderstate *modload.State, ctx context.Context, queries []*que buildListVersion: initialVersion, initialVersion: initialVersion, nonesByPath: map[string]*query{}, - workspace: loadWorkspace(modload.FindGoWork(loaderstate, base.Cwd())), + workspace: loadWorkspace(loaderstate.FindGoWork(base.Cwd())), } for _, q := range queries { @@ -722,7 +722,7 @@ func (r *resolver) queryNone(loaderstate *modload.State, ctx context.Context, q if !q.isWildcard() { q.pathOnce(q.pattern, func() pathSet { - hasModRoot := modload.HasModRoot(loaderstate) + hasModRoot := loaderstate.HasModRoot() if hasModRoot && loaderstate.MainModules.Contains(q.pattern) { v := module.Version{Path: q.pattern} // The user has explicitly requested to downgrade their own module to @@ -752,7 +752,7 @@ func (r *resolver) queryNone(loaderstate *modload.State, ctx context.Context, q continue } q.pathOnce(curM.Path, func() pathSet { - if modload.HasModRoot(loaderstate) && curM.Version == "" && loaderstate.MainModules.Contains(curM.Path) { + if loaderstate.HasModRoot() && curM.Version == "" && loaderstate.MainModules.Contains(curM.Path) { return errSet(&modload.QueryMatchesMainModulesError{ MainModules: []module.Version{curM}, Pattern: q.pattern, @@ -779,7 +779,7 @@ func (r *resolver) performLocalQueries(loaderstate *modload.State, ctx context.C // restricted to matching packages in the main module. pkgPattern, mainModule := loaderstate.MainModules.DirImportPath(loaderstate, ctx, q.pattern) if pkgPattern == "." { - modload.MustHaveModRoot(loaderstate) + loaderstate.MustHaveModRoot() versions := loaderstate.MainModules.Versions() modRoots := make([]string, 0, len(versions)) for _, m := range versions { @@ -802,7 +802,7 @@ func (r *resolver) performLocalQueries(loaderstate *modload.State, ctx context.C return errSet(fmt.Errorf("no package to get in current directory")) } if !q.isWildcard() { - modload.MustHaveModRoot(loaderstate) + loaderstate.MustHaveModRoot() return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, loaderstate.MainModules.ModRoot(mainModule))) } search.WarnUnmatched([]*search.Match{match}) diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go index 75d32dc633a420..3086dbc1ad61b0 100644 --- a/src/cmd/go/internal/modget/query.go +++ b/src/cmd/go/internal/modget/query.go @@ -184,7 +184,7 @@ func (q *query) validate(loaderstate *modload.State) error { if q.pattern == "all" { // If there is no main module, "all" is not meaningful. - if !modload.HasModRoot(loaderstate) { + if !loaderstate.HasModRoot() { return fmt.Errorf(`cannot match "all": %v`, modload.NewNoMainModulesError(loaderstate)) } if !versionOkForMainModule(q.version) { diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index 7299452670c13f..f6ba8d43b779f5 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -52,7 +52,7 @@ func findStandardImportPath(path string) string { // standard library or if the package was not successfully loaded with // LoadPackages or ImportFromFiles, nil is returned. func PackageModuleInfo(loaderstate *State, ctx context.Context, pkgpath string) *modinfo.ModulePublic { - if isStandardImportPath(pkgpath) || !Enabled(loaderstate) { + if isStandardImportPath(pkgpath) || !loaderstate.Enabled() { return nil } m, ok := findModule(loaded, pkgpath) @@ -69,7 +69,7 @@ func PackageModuleInfo(loaderstate *State, ctx context.Context, pkgpath string) // standard library or if the package was not successfully loaded with // LoadPackages or ImportFromFiles, the empty string is returned. func PackageModRoot(loaderstate *State, ctx context.Context, pkgpath string) string { - if isStandardImportPath(pkgpath) || !Enabled(loaderstate) || cfg.BuildMod == "vendor" { + if isStandardImportPath(pkgpath) || !loaderstate.Enabled() || cfg.BuildMod == "vendor" { return "" } m, ok := findModule(loaded, pkgpath) @@ -84,7 +84,7 @@ func PackageModRoot(loaderstate *State, ctx context.Context, pkgpath string) str } func ModuleInfo(loaderstate *State, ctx context.Context, path string) *modinfo.ModulePublic { - if !Enabled(loaderstate) { + if !loaderstate.Enabled() { return nil } diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go index cb64bec9c81d12..37c2a6c759f0db 100644 --- a/src/cmd/go/internal/modload/buildlist.go +++ b/src/cmd/go/internal/modload/buildlist.go @@ -165,7 +165,7 @@ func (rs *Requirements) String() string { func (rs *Requirements) initVendor(loaderstate *State, vendorList []module.Version) { rs.graphOnce.Do(func() { roots := loaderstate.MainModules.Versions() - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { // Use rs.rootModules to pull in the go and toolchain roots // from the go.work file and preserve the invariant that all // of rs.rootModules are in mg.g. @@ -208,7 +208,7 @@ func (rs *Requirements) initVendor(loaderstate *State, vendorList []module.Versi // graph, but still distinguishes between direct and indirect // dependencies. vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""} - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { for _, m := range loaderstate.MainModules.Versions() { reqs, _ := rootsFromModFile(loaderstate, m, loaderstate.MainModules.ModFile(m), omitToolchainRoot) mg.g.Require(m, append(reqs, vendorMod)) @@ -333,7 +333,7 @@ func readModGraph(loaderstate *State, ctx context.Context, pruning modPruning, r } var graphRoots []module.Version - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { graphRoots = roots } else { graphRoots = loaderstate.MainModules.Versions() @@ -347,7 +347,7 @@ func readModGraph(loaderstate *State, ctx context.Context, pruning modPruning, r ) if pruning != workspace { - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { panic("pruning is not workspace in workspace mode") } mg.g.Require(loaderstate.MainModules.mustGetSingleMainModule(loaderstate), roots) @@ -529,7 +529,7 @@ func (mg *ModuleGraph) findError() error { func (mg *ModuleGraph) allRootsSelected(loaderstate *State) bool { var roots []module.Version - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { roots = loaderstate.MainModules.Versions() } else { roots, _ = mg.g.RequiredBy(loaderstate.MainModules.mustGetSingleMainModule(loaderstate)) diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go index c6b56c35d4b129..3998ce11726fe0 100644 --- a/src/cmd/go/internal/modload/import.go +++ b/src/cmd/go/internal/modload/import.go @@ -29,10 +29,11 @@ import ( ) type ImportMissingError struct { - Path string - Module module.Version - QueryErr error - modContainingCWD module.Version + Path string + Module module.Version + QueryErr error + modContainingCWD module.Version + allowMissingModuleImports bool // modRoot is dependent on the value of ImportingMainModule and should be // kept in sync. @@ -70,7 +71,7 @@ func (e *ImportMissingError) Error() string { if e.QueryErr != nil && !errors.Is(e.QueryErr, ErrNoModRoot) { return fmt.Sprintf("cannot find module providing package %s: %v", e.Path, e.QueryErr) } - if cfg.BuildMod == "mod" || (cfg.BuildMod == "readonly" && allowMissingModuleImports) { + if cfg.BuildMod == "mod" || (cfg.BuildMod == "readonly" && e.allowMissingModuleImports) { return "cannot find module providing package " + e.Path } @@ -340,7 +341,7 @@ func importFromModules(loaderstate *State, ctx context.Context, path string, rs } } - if HasModRoot(loaderstate) { + if loaderstate.HasModRoot() { vendorDir := VendorDir(loaderstate) dir, inVendorDir, _ := dirInModule(path, "", vendorDir, false) if inVendorDir { @@ -355,7 +356,7 @@ func importFromModules(loaderstate *State, ctx context.Context, path string, rs roots = append(roots, vendorDir) } else { subCommand := "mod" - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { subCommand = "work" } fmt.Fprintf(os.Stderr, "go: ignoring package %s which exists in the vendor directory but is missing from vendor/modules.txt. To sync the vendor directory run go %s vendor.\n", path, subCommand) @@ -373,8 +374,9 @@ func importFromModules(loaderstate *State, ctx context.Context, path string, rs if len(mods) == 0 { return module.Version{}, "", "", nil, &ImportMissingError{ - Path: path, - modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + Path: path, + modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + allowMissingModuleImports: loaderstate.allowMissingModuleImports, } } @@ -490,14 +492,15 @@ func importFromModules(loaderstate *State, ctx context.Context, path string, rs // We checked the full module graph and still didn't find the // requested package. var queryErr error - if !HasModRoot(loaderstate) { + if !loaderstate.HasModRoot() { queryErr = NewNoMainModulesError(loaderstate) } return module.Version{}, "", "", nil, &ImportMissingError{ - Path: path, - QueryErr: queryErr, - isStd: pathIsStd, - modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + Path: path, + QueryErr: queryErr, + isStd: pathIsStd, + modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + allowMissingModuleImports: loaderstate.allowMissingModuleImports, } } @@ -571,9 +574,10 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi } else if ok { if cfg.BuildMod == "readonly" { return module.Version{}, &ImportMissingError{ - Path: path, - replaced: m, - modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + Path: path, + replaced: m, + modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + allowMissingModuleImports: loaderstate.allowMissingModuleImports, } } return m, nil @@ -601,13 +605,14 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi // // Instead of trying QueryPattern, report an ImportMissingError immediately. return module.Version{}, &ImportMissingError{ - Path: path, - isStd: true, - modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + Path: path, + isStd: true, + modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + allowMissingModuleImports: loaderstate.allowMissingModuleImports, } } - if (cfg.BuildMod == "readonly" || cfg.BuildMod == "vendor") && !allowMissingModuleImports { + if (cfg.BuildMod == "readonly" || cfg.BuildMod == "vendor") && !loaderstate.allowMissingModuleImports { // In readonly mode, we can't write go.mod, so we shouldn't try to look up // the module. If readonly mode was enabled explicitly, include that in // the error message. @@ -620,9 +625,10 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason) } return module.Version{}, &ImportMissingError{ - Path: path, - QueryErr: queryErr, - modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + Path: path, + QueryErr: queryErr, + modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + allowMissingModuleImports: loaderstate.allowMissingModuleImports, } } @@ -642,9 +648,10 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi // Return "cannot find module providing package […]" instead of whatever // low-level error QueryPattern produced. return module.Version{}, &ImportMissingError{ - Path: path, - QueryErr: err, - modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + Path: path, + QueryErr: err, + modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + allowMissingModuleImports: loaderstate.allowMissingModuleImports, } } else { return module.Version{}, err @@ -670,10 +677,11 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi return c.Mod, nil } return module.Version{}, &ImportMissingError{ - Path: path, - Module: candidates[0].Mod, - newMissingVersion: candidate0MissingVersion, - modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + Path: path, + Module: candidates[0].Mod, + newMissingVersion: candidate0MissingVersion, + modContainingCWD: loaderstate.MainModules.ModContainingCWD(), + allowMissingModuleImports: loaderstate.allowMissingModuleImports, } } @@ -820,7 +828,7 @@ func fetch(loaderstate *State, ctx context.Context, mod module.Version) (dir str // mustHaveSums reports whether we require that all checksums // needed to load or build packages are already present in the go.sum file. func mustHaveSums(loaderstate *State) bool { - return HasModRoot(loaderstate) && cfg.BuildMod == "readonly" && !inWorkspaceMode(loaderstate) + return loaderstate.HasModRoot() && cfg.BuildMod == "readonly" && !loaderstate.inWorkspaceMode() } type sumMissingError struct { diff --git a/src/cmd/go/internal/modload/import_test.go b/src/cmd/go/internal/modload/import_test.go index 0716675a91d2bd..820fb87b5928f2 100644 --- a/src/cmd/go/internal/modload/import_test.go +++ b/src/cmd/go/internal/modload/import_test.go @@ -58,16 +58,11 @@ var importTests = []struct { func TestQueryImport(t *testing.T) { loaderstate := NewState() loaderstate.RootMode = NoRoot + loaderstate.AllowMissingModuleImports() testenv.MustHaveExternalNetwork(t) testenv.MustHaveExecPath(t, "git") - oldAllowMissingModuleImports := allowMissingModuleImports - defer func() { - allowMissingModuleImports = oldAllowMissingModuleImports - }() - allowMissingModuleImports = true - ctx := context.Background() rs := LoadModFile(loaderstate, ctx) diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index 9abfac971573b3..3d6f9a4a65abc3 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -38,8 +38,6 @@ import ( // // TODO(#40775): See if these can be plumbed as explicit parameters. var ( - allowMissingModuleImports bool - // ExplicitWriteGoMod prevents LoadPackages, ListModules, and other functions // from updating go.mod and go.sum or reporting errors when updates are // needed. A package should set this if it would cause go.mod to be written @@ -85,7 +83,7 @@ func EnterWorkspace(loaderstate *State, ctx context.Context) (exit func(), err e loaderstate.ForceUseModules = true // Load in workspace mode. - InitWorkfile(loaderstate) + loaderstate.InitWorkfile() LoadModFile(loaderstate, ctx) // Update the content of the previous main module, and recompute the requirements. @@ -195,7 +193,7 @@ func (mms *MainModuleSet) getSingleMainModule(loaderstate *State) (module.Versio return module.Version{}, errors.New("internal error: mustGetSingleMainModule called in context with no main modules") } if len(mms.versions) != 1 { - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { return module.Version{}, errors.New("internal error: mustGetSingleMainModule called in workspace mode") } else { return module.Version{}, errors.New("internal error: multiple main modules present outside of workspace mode") @@ -255,7 +253,7 @@ func (mms *MainModuleSet) HighestReplaced() map[string]string { // GoVersion returns the go version set on the single module, in module mode, // or the go.work file in workspace mode. func (mms *MainModuleSet) GoVersion(loaderstate *State) string { - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { return gover.FromGoWork(mms.workFile) } if mms != nil && len(mms.versions) == 1 { @@ -275,7 +273,7 @@ func (mms *MainModuleSet) GoVersion(loaderstate *State) string { // or on the go.work file in workspace mode. // The caller must not modify the result. func (mms *MainModuleSet) Godebugs(loaderstate *State) []*modfile.Godebug { - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { if mms.workFile != nil { return mms.workFile.Godebug } @@ -345,13 +343,13 @@ func BinDir(loaderstate *State) string { // InitWorkfile initializes the workFilePath variable for commands that // operate in workspace mode. It should not be called by other commands, // for example 'go mod tidy', that don't operate in workspace mode. -func InitWorkfile(loaderstate *State) { +func (loaderstate *State) InitWorkfile() { // Initialize fsys early because we need overlay to read go.work file. fips140.Init() if err := fsys.Init(); err != nil { base.Fatal(err) } - loaderstate.workFilePath = FindGoWork(loaderstate, base.Cwd()) + loaderstate.workFilePath = loaderstate.FindGoWork(base.Cwd()) } // FindGoWork returns the name of the go.work file for this command, @@ -359,7 +357,7 @@ func InitWorkfile(loaderstate *State) { // Most code should use Init and Enabled rather than use this directly. // It is exported mainly for Go toolchain switching, which must process // the go.work very early at startup. -func FindGoWork(loaderstate *State, wd string) string { +func (loaderstate *State) FindGoWork(wd string) string { if loaderstate.RootMode == NoRoot { return "" } @@ -415,7 +413,8 @@ func (s *State) setState(new State) State { } type State struct { - initialized bool + initialized bool + allowMissingModuleImports bool // ForceUseModules may be set to force modules to be enabled when // GO111MODULE=auto or to report an error when GO111MODULE=off. @@ -576,7 +575,7 @@ func Init(loaderstate *State) { // of 'go get', but Init reads the -modfile flag in 'go get', so it shouldn't // be called until the command is installed and flags are parsed. Instead of // calling Init and Enabled, the main package can call this function. -func WillBeEnabled(loaderstate *State) bool { +func (loaderstate *State) WillBeEnabled() bool { if loaderstate.modRoots != nil || cfg.ModulesEnabled { // Already enabled. return true @@ -628,13 +627,13 @@ func FindGoMod(wd string) string { // If modules are enabled but there is no main module, Enabled returns true // and then the first use of module information will call die // (usually through MustModRoot). -func Enabled(loaderstate *State) bool { +func (loaderstate *State) Enabled() bool { Init(loaderstate) return loaderstate.modRoots != nil || cfg.ModulesEnabled } func (s *State) vendorDir() (string, error) { - if inWorkspaceMode(s) { + if s.inWorkspaceMode() { return filepath.Join(filepath.Dir(WorkFilePath(s)), "vendor"), nil } mainModule, err := s.MainModules.getSingleMainModule(s) @@ -667,11 +666,11 @@ func VendorDir(loaderstate *State) string { return dir } -func inWorkspaceMode(loaderstate *State) bool { +func (loaderstate *State) inWorkspaceMode() bool { if !loaderstate.initialized { panic("inWorkspaceMode called before modload.Init called") } - if !Enabled(loaderstate) { + if !loaderstate.Enabled() { return false } return loaderstate.workFilePath != "" @@ -680,16 +679,16 @@ func inWorkspaceMode(loaderstate *State) bool { // HasModRoot reports whether a main module or main modules are present. // HasModRoot may return false even if Enabled returns true: for example, 'get' // does not require a main module. -func HasModRoot(loaderstate *State) bool { +func (loaderstate *State) HasModRoot() bool { Init(loaderstate) return loaderstate.modRoots != nil } // MustHaveModRoot checks that a main module or main modules are present, // and calls base.Fatalf if there are no main modules. -func MustHaveModRoot(loaderstate *State) { +func (loaderstate *State) MustHaveModRoot() { Init(loaderstate) - if !HasModRoot(loaderstate) { + if !loaderstate.HasModRoot() { die(loaderstate) } } @@ -697,8 +696,8 @@ func MustHaveModRoot(loaderstate *State) { // ModFilePath returns the path that would be used for the go.mod // file, if in module mode. ModFilePath calls base.Fatalf if there is no main // module, even if -modfile is set. -func ModFilePath(loaderstate *State) string { - MustHaveModRoot(loaderstate) +func (loaderstate *State) ModFilePath() string { + loaderstate.MustHaveModRoot() return modFilePath(findModuleRoot(base.Cwd())) } @@ -716,7 +715,7 @@ func die(loaderstate *State) { if cfg.Getenv("GO111MODULE") == "off" { base.Fatalf("go: modules disabled by GO111MODULE=off; see 'go help modules'") } - if !inWorkspaceMode(loaderstate) { + if !loaderstate.inWorkspaceMode() { if dir, name := findAltConfig(base.Cwd()); dir != "" { rel, err := filepath.Rel(base.Cwd(), dir) if err != nil { @@ -753,7 +752,7 @@ func (e noMainModulesError) Unwrap() error { func NewNoMainModulesError(s *State) noMainModulesError { return noMainModulesError{ - inWorkspaceMode: inWorkspaceMode(s), + inWorkspaceMode: s.inWorkspaceMode(), } } @@ -921,7 +920,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R Init(loaderstate) var workFile *modfile.WorkFile - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { var err error workFile, loaderstate.modRoots, err = LoadWorkFile(loaderstate.workFilePath) if err != nil { @@ -965,7 +964,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R roots []module.Version direct = map[string]bool{"go": true} ) - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { // Since we are in a workspace, the Go version for the synthetic // "command-line-arguments" module must not exceed the Go version // for the workspace. @@ -1004,7 +1003,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R var fixed bool data, f, err := ReadModFile(gomod, fixVersion(loaderstate, ctx, &fixed)) if err != nil { - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { if tooNew, ok := err.(*gover.TooNewError); ok && !strings.HasPrefix(cfg.CmdName, "work ") { // Switching to a newer toolchain won't help - the go.work has the wrong version. // Report this more specific error, unless we are a command like 'go work use' @@ -1019,7 +1018,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R errs = append(errs, err) continue } - if inWorkspaceMode(loaderstate) && !strings.HasPrefix(cfg.CmdName, "work ") { + if loaderstate.inWorkspaceMode() && !strings.HasPrefix(cfg.CmdName, "work ") { // Refuse to use workspace if its go version is too old. // Disable this check if we are a workspace command like work use or work sync, // which will fix the problem. @@ -1031,7 +1030,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R } } - if !inWorkspaceMode(loaderstate) { + if !loaderstate.inWorkspaceMode() { ok := true for _, g := range f.Godebug { if err := CheckGodebug("godebug", g.Key, g.Value); err != nil { @@ -1079,7 +1078,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R rs.initVendor(loaderstate, vendorList) } - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { // We don't need to update the mod file so return early. loaderstate.requirements = rs return rs, nil @@ -1292,11 +1291,11 @@ func fixVersion(loaderstate *State, ctx context.Context, fixed *bool) modfile.Ve // // This function affects the default cfg.BuildMod when outside of a module, // so it can only be called prior to Init. -func AllowMissingModuleImports(loaderstate *State) { - if loaderstate.initialized { +func (s *State) AllowMissingModuleImports() { + if s.initialized { panic("AllowMissingModuleImports after Init") } - allowMissingModuleImports = true + s.allowMissingModuleImports = true } // makeMainModules creates a MainModuleSet and associated variables according to @@ -1422,7 +1421,7 @@ func requirementsFromModFiles(loaderstate *State, ctx context.Context, workFile var roots []module.Version direct := map[string]bool{} var pruning modPruning - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { pruning = workspace roots = make([]module.Version, len(loaderstate.MainModules.Versions()), 2+len(loaderstate.MainModules.Versions())) copy(roots, loaderstate.MainModules.Versions()) @@ -1517,7 +1516,7 @@ func appendGoAndToolchainRoots(roots []module.Version, goVersion, toolchain stri // wasn't provided. setDefaultBuildMod may be called multiple times. func setDefaultBuildMod(loaderstate *State) { if cfg.BuildModExplicit { - if inWorkspaceMode(loaderstate) && cfg.BuildMod != "readonly" && cfg.BuildMod != "vendor" { + if loaderstate.inWorkspaceMode() && cfg.BuildMod != "readonly" && cfg.BuildMod != "vendor" { switch cfg.CmdName { case "work sync", "mod graph", "mod verify", "mod why": // These commands run with BuildMod set to mod, but they don't take the @@ -1553,7 +1552,7 @@ func setDefaultBuildMod(loaderstate *State) { return } if loaderstate.modRoots == nil { - if allowMissingModuleImports { + if loaderstate.allowMissingModuleImports { cfg.BuildMod = "mod" } else { cfg.BuildMod = "readonly" @@ -1564,7 +1563,7 @@ func setDefaultBuildMod(loaderstate *State) { if len(loaderstate.modRoots) >= 1 { var goVersion string var versionSource string - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { versionSource = "go.work" if wfg := loaderstate.MainModules.WorkFile().Go; wfg != nil { goVersion = wfg.Version @@ -1652,7 +1651,7 @@ func modulesTextIsForWorkspace(vendorDir string) (bool, error) { } func mustHaveCompleteRequirements(loaderstate *State) bool { - return cfg.BuildMod != "mod" && !inWorkspaceMode(loaderstate) + return cfg.BuildMod != "mod" && !loaderstate.inWorkspaceMode() } // addGoStmt adds a go directive to the go.mod file if it does not already @@ -1956,7 +1955,7 @@ func UpdateGoModFromReqs(loaderstate *State, ctx context.Context, opts WriteOpts // // In workspace mode, commitRequirements only writes changes to go.work.sum. func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) (err error) { - if inWorkspaceMode(loaderstate) { + 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)) @@ -2243,9 +2242,12 @@ func CheckGodebug(verb, k, v string) error { } return nil } - for _, info := range godebugs.All { - if k == info.Name { - return nil + if godebugs.Lookup(k) != nil { + return nil + } + for _, info := range godebugs.Removed { + if info.Name == k { + return fmt.Errorf("use of removed %s %q, see https://go.dev/doc/godebug#go-1%v", verb, k, info.Removed) } } return fmt.Errorf("unknown %s %q", verb, k) diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go index 6a4d788824caa9..316fda4003be03 100644 --- a/src/cmd/go/internal/modload/list.go +++ b/src/cmd/go/internal/modload/list.go @@ -145,7 +145,7 @@ func listModules(loaderstate *State, ctx context.Context, rs *Requirements, args } if arg == "all" || strings.Contains(arg, "...") { needFullGraph = true - if !HasModRoot(loaderstate) { + if !loaderstate.HasModRoot() { base.Fatalf("go: cannot match %q: %v", arg, NewNoMainModulesError(loaderstate)) } continue @@ -154,7 +154,7 @@ func listModules(loaderstate *State, ctx context.Context, rs *Requirements, args if vers == "upgrade" || vers == "patch" { if _, ok := rs.rootSelected(loaderstate, path); !ok || rs.pruning == unpruned { needFullGraph = true - if !HasModRoot(loaderstate) { + if !loaderstate.HasModRoot() { base.Fatalf("go: cannot match %q: %v", arg, NewNoMainModulesError(loaderstate)) } } @@ -163,7 +163,7 @@ func listModules(loaderstate *State, ctx context.Context, rs *Requirements, args } if _, ok := rs.rootSelected(loaderstate, arg); !ok || rs.pruning == unpruned { needFullGraph = true - if mode&ListVersions == 0 && !HasModRoot(loaderstate) { + if mode&ListVersions == 0 && !loaderstate.HasModRoot() { base.Fatalf("go: cannot match %q without -versions or an explicit version: %v", arg, NewNoMainModulesError(loaderstate)) } } diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index 065d3a78163a21..b4d128fe9a15f6 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -294,7 +294,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat // If we're outside of a module, ensure that the failure mode // indicates that. - if !HasModRoot(loaderstate) { + if !loaderstate.HasModRoot() { die(loaderstate) } @@ -546,7 +546,7 @@ func matchLocalDirs(loaderstate *State, ctx context.Context, modRoots []string, if !slices.Contains(modRoots, modRoot) && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(loaderstate, ctx, absDir, rs) == "" { m.Dirs = []string{} scope := "main module or its selected dependencies" - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { scope = "modules listed in go.work or their selected dependencies" } m.AddError(fmt.Errorf("directory prefix %s does not contain %s", base.ShortPath(absDir), scope)) @@ -674,7 +674,7 @@ func resolveLocalPackage(loaderstate *State, ctx context.Context, dir string, rs if dirstr == "directory ." { dirstr = "current directory" } - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { if mr := findModuleRoot(absDir); mr != "" { return "", fmt.Errorf("%s is contained in a module that is not one of the workspace modules listed in go.work. You can add the module to the workspace using:\n\tgo work use %s", dirstr, base.ShortPath(mr)) } @@ -800,7 +800,7 @@ func ImportFromFiles(loaderstate *State, ctx context.Context, gofiles []string) // DirImportPath returns the effective import path for dir, // provided it is within a main module, or else returns ".". func (mms *MainModuleSet) DirImportPath(loaderstate *State, ctx context.Context, dir string) (path string, m module.Version) { - if !HasModRoot(loaderstate) { + if !loaderstate.HasModRoot() { return ".", module.Version{} } LoadModFile(loaderstate, ctx) // Sets targetPrefix. @@ -1184,7 +1184,7 @@ func loadFromRoots(loaderstate *State, ctx context.Context, params loaderParams) continue } - if !ld.ResolveMissingImports || (!HasModRoot(loaderstate) && !allowMissingModuleImports) { + if !ld.ResolveMissingImports || (!loaderstate.HasModRoot() && !loaderstate.allowMissingModuleImports) { // We've loaded as much as we can without resolving missing imports. break } @@ -1399,7 +1399,7 @@ func (ld *loader) updateRequirements(loaderstate *State, ctx context.Context) (c continue } - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { // In workspace mode / workspace pruning mode, the roots are the main modules // rather than the main module's direct dependencies. The check below on the selected // roots does not apply. diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index be0f2a5c1166fc..7191833a0dcce9 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -574,7 +574,7 @@ type retraction struct { // // The caller must not modify the returned summary. func goModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) { - if m.Version == "" && !inWorkspaceMode(loaderstate) && loaderstate.MainModules.Contains(m.Path) { + if m.Version == "" && !loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) { panic("internal error: goModSummary called on a main module") } if gover.IsToolchain(m.Path) { @@ -686,7 +686,7 @@ func rawGoModSummary(loaderstate *State, m module.Version) (*modFileSummary, err } return &modFileSummary{module: m}, nil } - if m.Version == "" && !inWorkspaceMode(loaderstate) && loaderstate.MainModules.Contains(m.Path) { + if m.Version == "" && !loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) { // Calling rawGoModSummary implies that we are treating m as a module whose // requirements aren't the roots of the module graph and can't be modified. // @@ -694,12 +694,12 @@ func rawGoModSummary(loaderstate *State, m module.Version) (*modFileSummary, err // are the roots of the module graph and we expect them to be kept consistent. panic("internal error: rawGoModSummary called on a main module") } - if m.Version == "" && inWorkspaceMode(loaderstate) && m.Path == "command-line-arguments" { + if m.Version == "" && loaderstate.inWorkspaceMode() && m.Path == "command-line-arguments" { // "go work sync" calls LoadModGraph to make sure the module graph is valid. // If there are no modules in the workspace, we synthesize an empty // command-line-arguments module, which rawGoModData cannot read a go.mod for. return &modFileSummary{module: m}, nil - } else if m.Version == "" && inWorkspaceMode(loaderstate) && loaderstate.MainModules.Contains(m.Path) { + } else if m.Version == "" && loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) { // When go get uses EnterWorkspace to check that the workspace loads properly, // it will update the contents of the workspace module's modfile in memory. To use the updated // contents of the modfile when doing the load, don't read from disk and instead @@ -785,7 +785,7 @@ func rawGoModData(loaderstate *State, m module.Version) (name string, data []byt if m.Version == "" { dir := m.Path if !filepath.IsAbs(dir) { - if inWorkspaceMode(loaderstate) && loaderstate.MainModules.Contains(m.Path) { + if loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) { dir = loaderstate.MainModules.ModRoot(m) } else { // m is a replacement module with only a file path. diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go index 1bb4e3f911e04f..c45808635dbe69 100644 --- a/src/cmd/go/internal/modload/search.go +++ b/src/cmd/go/internal/modload/search.go @@ -176,7 +176,7 @@ func matchPackages(loaderstate *State, ctx context.Context, m *search.Match, tag walkPkgs(modRoot, loaderstate.MainModules.PathPrefix(mod), pruneGoMod|pruneVendor) } } - if HasModRoot(loaderstate) { + if loaderstate.HasModRoot() { walkPkgs(VendorDir(loaderstate), "", pruneVendor) } return diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go index 1fc20ad398b363..9956bcdb127290 100644 --- a/src/cmd/go/internal/modload/vendor.go +++ b/src/cmd/go/internal/modload/vendor.go @@ -154,7 +154,7 @@ func checkVendorConsistency(loaderstate *State, indexes []*modFileIndex, modFile } pre114 := false - if !inWorkspaceMode(loaderstate) { // workspace mode was added after Go 1.14 + if !loaderstate.inWorkspaceMode() { // workspace mode was added after Go 1.14 if len(indexes) != 1 { panic(fmt.Errorf("not in workspace mode but number of indexes is %v, not 1", len(indexes))) } @@ -252,7 +252,7 @@ func checkVendorConsistency(loaderstate *State, indexes []*modFileIndex, modFile } if !foundRequire { article := "" - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { article = "a " } vendErrorf(mod, "is marked as explicit in vendor/modules.txt, but not explicitly required in %vgo.mod", article) @@ -264,7 +264,7 @@ func checkVendorConsistency(loaderstate *State, indexes []*modFileIndex, modFile for _, mod := range vendorReplaced { r := Replacement(loaderstate, mod) replacementSource := "go.mod" - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { replacementSource = "the workspace" } if r == (module.Version{}) { @@ -276,7 +276,7 @@ func checkVendorConsistency(loaderstate *State, indexes []*modFileIndex, modFile if vendErrors.Len() > 0 { subcmd := "mod" - if inWorkspaceMode(loaderstate) { + if loaderstate.inWorkspaceMode() { subcmd = "work" } base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo %s vendor", filepath.Dir(VendorDir(loaderstate)), vendErrors, subcmd) diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go index f821b37f292bfd..ebd99ccfb21f19 100644 --- a/src/cmd/go/internal/run/run.go +++ b/src/cmd/go/internal/run/run.go @@ -79,10 +79,10 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { // for -race and -msan. moduleLoaderState.ForceUseModules = true moduleLoaderState.RootMode = modload.NoRoot - modload.AllowMissingModuleImports(moduleLoaderState) + moduleLoaderState.AllowMissingModuleImports() modload.Init(moduleLoaderState) } else { - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() } work.BuildInit(moduleLoaderState) diff --git a/src/cmd/go/internal/telemetrystats/telemetrystats.go b/src/cmd/go/internal/telemetrystats/telemetrystats.go index 84b4ae2e841567..81a6e1e1758461 100644 --- a/src/cmd/go/internal/telemetrystats/telemetrystats.go +++ b/src/cmd/go/internal/telemetrystats/telemetrystats.go @@ -25,13 +25,20 @@ func incrementConfig() { // TODO(jitsu): Telemetry for the go/mode counters should eventually be // moved to modload.Init() s := modload.NewState() - if !modload.WillBeEnabled(s) { + if !s.WillBeEnabled() { counter.Inc("go/mode:gopath") - } else if workfile := modload.FindGoWork(s, base.Cwd()); workfile != "" { + } else if workfile := s.FindGoWork(base.Cwd()); workfile != "" { counter.Inc("go/mode:workspace") } else { counter.Inc("go/mode:module") } + + if cfg.BuildContext.CgoEnabled { + counter.Inc("go/cgo:enabled") + } else { + counter.Inc("go/cgo:disabled") + } + counter.Inc("go/platform/target/goos:" + cfg.Goos) counter.Inc("go/platform/target/goarch:" + cfg.Goarch) switch cfg.Goarch { diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index 77fb9488ac03fe..44ee98feaaf576 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -684,7 +684,7 @@ var defaultVetFlags = []string{ func runTest(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() pkgArgs, testArgs = testFlags(args) - modload.InitWorkfile(moduleLoaderState) // The test command does custom flag processing; initialize workspaces after that. + moduleLoaderState.InitWorkfile() // The test command does custom flag processing; initialize workspaces after that. if cfg.DebugTrace != "" { var close func() error @@ -742,7 +742,7 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) { if !mainMods.Contains(m.Path) { base.Fatalf("cannot use -fuzz flag on package outside the main module") } - } else if pkgs[0].Standard && modload.Enabled(moduleLoaderState) { + } else if pkgs[0].Standard && moduleLoaderState.Enabled() { // Because packages in 'std' and 'cmd' are part of the standard library, // they are only treated as part of a module in 'go mod' subcommands and // 'go get'. However, we still don't want to accidentally corrupt their diff --git a/src/cmd/go/internal/tool/tool.go b/src/cmd/go/internal/tool/tool.go index e283c7354f5a34..92e8a803105f8d 100644 --- a/src/cmd/go/internal/tool/tool.go +++ b/src/cmd/go/internal/tool/tool.go @@ -162,7 +162,7 @@ func listTools(loaderstate *modload.State, ctx context.Context) { fmt.Println(name) } - modload.InitWorkfile(loaderstate) + loaderstate.InitWorkfile() modload.LoadModFile(loaderstate, ctx) modTools := slices.Sorted(maps.Keys(loaderstate.MainModules.Tools())) for _, tool := range modTools { @@ -253,7 +253,7 @@ func loadBuiltinTool(toolName string) string { } func loadModTool(loaderstate *modload.State, ctx context.Context, name string) string { - modload.InitWorkfile(loaderstate) + loaderstate.InitWorkfile() modload.LoadModFile(loaderstate, ctx) matches := []string{} diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go index e7201e2f5fbb27..4c7e7a5e576ba5 100644 --- a/src/cmd/go/internal/toolchain/select.go +++ b/src/cmd/go/internal/toolchain/select.go @@ -99,7 +99,7 @@ func Select() { log.SetPrefix("go: ") defer log.SetPrefix("") - if !modload.WillBeEnabled(moduleLoaderState) { + if !moduleLoaderState.WillBeEnabled() { return } @@ -525,7 +525,7 @@ func raceSafeCopy(old, new string) error { // The toolchain line overrides the version line func modGoToolchain(loaderstate *modload.State) (file, goVers, toolchain string) { wd := base.UncachedCwd() - file = modload.FindGoWork(loaderstate, wd) + file = loaderstate.FindGoWork(wd) // $GOWORK can be set to a file that does not yet exist, if we are running 'go work init'. // Do not try to load the file in that case if _, err := os.Stat(file); err != nil { diff --git a/src/cmd/go/internal/vet/vet.go b/src/cmd/go/internal/vet/vet.go index a429cbff65934e..9055446325af6e 100644 --- a/src/cmd/go/internal/vet/vet.go +++ b/src/cmd/go/internal/vet/vet.go @@ -126,7 +126,7 @@ func run(ctx context.Context, cmd *base.Command, args []string) { // The vet/fix commands do custom flag processing; // initialize workspaces after that. - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if cfg.DebugTrace != "" { var close func() error diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index 7ca95cbe3f9286..c483c19c65b30c 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -460,7 +460,7 @@ var pkgsFilter = func(pkgs []*load.Package) []*load.Package { return pkgs } func runBuild(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() BuildInit(moduleLoaderState) b := NewBuilder("", moduleLoaderState.VendorDirOrEmpty) defer func() { @@ -696,10 +696,10 @@ func runInstall(ctx context.Context, cmd *base.Command, args []string) { } } - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() BuildInit(moduleLoaderState) pkgs := load.PackagesAndErrors(moduleLoaderState, ctx, load.PackageOpts{AutoVCS: true}, args) - if cfg.ModulesEnabled && !modload.HasModRoot(moduleLoaderState) { + if cfg.ModulesEnabled && !moduleLoaderState.HasModRoot() { haveErrors := false allMissingErrors := true for _, pkg := range pkgs { @@ -863,7 +863,7 @@ func InstallPackages(loaderstate *modload.State, ctx context.Context, patterns [ func installOutsideModule(loaderstate *modload.State, ctx context.Context, args []string) { loaderstate.ForceUseModules = true loaderstate.RootMode = modload.NoRoot - modload.AllowMissingModuleImports(loaderstate) + loaderstate.AllowMissingModuleImports() modload.Init(loaderstate) BuildInit(loaderstate) diff --git a/src/cmd/go/internal/workcmd/edit.go b/src/cmd/go/internal/workcmd/edit.go index 2b9f658f861c85..b18098ba5d7f71 100644 --- a/src/cmd/go/internal/workcmd/edit.go +++ b/src/cmd/go/internal/workcmd/edit.go @@ -144,7 +144,7 @@ func runEditwork(ctx context.Context, cmd *base.Command, args []string) { if len(args) == 1 { gowork = args[0] } else { - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() gowork = modload.WorkFilePath(moduleLoaderState) } if gowork == "" { diff --git a/src/cmd/go/internal/workcmd/init.go b/src/cmd/go/internal/workcmd/init.go index 9ba9e4dec02c5f..896740f0803502 100644 --- a/src/cmd/go/internal/workcmd/init.go +++ b/src/cmd/go/internal/workcmd/init.go @@ -45,7 +45,7 @@ func init() { func runInit(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() moduleLoaderState.ForceUseModules = true diff --git a/src/cmd/go/internal/workcmd/sync.go b/src/cmd/go/internal/workcmd/sync.go index ae4fd9c5f34ce1..13ce1e5f4249ee 100644 --- a/src/cmd/go/internal/workcmd/sync.go +++ b/src/cmd/go/internal/workcmd/sync.go @@ -50,7 +50,7 @@ func init() { func runSync(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() moduleLoaderState.ForceUseModules = true - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if modload.WorkFilePath(moduleLoaderState) == "" { base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") } diff --git a/src/cmd/go/internal/workcmd/use.go b/src/cmd/go/internal/workcmd/use.go index eae9688b52413f..041aa069e2d6bd 100644 --- a/src/cmd/go/internal/workcmd/use.go +++ b/src/cmd/go/internal/workcmd/use.go @@ -63,7 +63,7 @@ func init() { func runUse(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() moduleLoaderState.ForceUseModules = true - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() gowork := modload.WorkFilePath(moduleLoaderState) if gowork == "" { base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") diff --git a/src/cmd/go/internal/workcmd/vendor.go b/src/cmd/go/internal/workcmd/vendor.go index 8852d965fa3b94..26715c8d3be3c6 100644 --- a/src/cmd/go/internal/workcmd/vendor.go +++ b/src/cmd/go/internal/workcmd/vendor.go @@ -47,7 +47,7 @@ func init() { func runVendor(ctx context.Context, cmd *base.Command, args []string) { moduleLoaderState := modload.NewState() - modload.InitWorkfile(moduleLoaderState) + moduleLoaderState.InitWorkfile() if modload.WorkFilePath(moduleLoaderState) == "" { base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") } diff --git a/src/cmd/go/testdata/script/list_empty_importpath.txt b/src/cmd/go/testdata/script/list_empty_importpath.txt index 4ddf6b36d26881..fe4210322bb4f1 100644 --- a/src/cmd/go/testdata/script/list_empty_importpath.txt +++ b/src/cmd/go/testdata/script/list_empty_importpath.txt @@ -1,12 +1,15 @@ ! go list all ! stderr 'panic' -[!GOOS:windows] [!GOOS:solaris] stderr 'invalid import path' -# #73976: Allow 'no errors' on Windows and Solaris until issue +[!GOOS:windows] [!GOOS:solaris] [!GOOS:freebsd] [!GOOS:openbsd] [!GOOS:netbsd] stderr 'invalid import path' +# #73976: Allow 'no errors' on Windows, Solaris, and BSD until issue # is resolved to prevent flakes. 'no errors' is printed by # empty scanner.ErrorList errors so that's probably where the # message is coming from, though we don't know how. [GOOS:windows] stderr 'invalid import path|no errors' [GOOS:solaris] stderr 'invalid import path|no errors' +[GOOS:freebsd] stderr 'invalid import path|no errors' +[GOOS:openbsd] stderr 'invalid import path|no errors' +[GOOS:netbsd] stderr 'invalid import path|no errors' # go list produces a package for 'p' but not for '' go list -e all diff --git a/src/cmd/go/testdata/script/mod_removed_godebug.txt b/src/cmd/go/testdata/script/mod_removed_godebug.txt new file mode 100644 index 00000000000000..bd1f61c9d26cc9 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_removed_godebug.txt @@ -0,0 +1,11 @@ +# Test case that makes sure we print a nice error message +# instead of the generic "unknown godebug" error message +# for removed GODEBUGs. + +! go list +stderr '^go.mod:3: use of removed godebug "x509sha1", see https://go.dev/doc/godebug#go-124$' + +-- go.mod -- +module example.com/bar + +godebug x509sha1=1 diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go index befd1bee13d66f..7e7f028bfb3d2b 100644 --- a/src/cmd/internal/obj/arm64/asm7.go +++ b/src/cmd/internal/obj/arm64/asm7.go @@ -7276,6 +7276,8 @@ func (c *ctxt7) opldrr(p *obj.Prog, a obj.As, rt, rn, rm int16, extension bool) op = OptionS<<10 | 0x3<<21 | 0x17<<27 | 1<<26 case AFMOVD: op = OptionS<<10 | 0x3<<21 | 0x1f<<27 | 1<<26 + case AFMOVQ: + op = OptionS<<10 | 0x7<<21 | 0x07<<27 | 1<<26 default: c.ctxt.Diag("bad opldrr %v\n%v", a, p) return 0 @@ -7308,6 +7310,8 @@ func (c *ctxt7) opstrr(p *obj.Prog, a obj.As, rt, rn, rm int16, extension bool) op = OptionS<<10 | 0x1<<21 | 0x17<<27 | 1<<26 case AFMOVD: op = OptionS<<10 | 0x1<<21 | 0x1f<<27 | 1<<26 + case AFMOVQ: + op = OptionS<<10 | 0x5<<21 | 0x07<<27 | 1<<26 default: c.ctxt.Diag("bad opstrr %v\n%v", a, p) return 0 diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index 3a676db922ca71..762dc338e3e149 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -1115,6 +1115,11 @@ const ( AXVSHUF4IW AXVSHUF4IV + AVPERMIW + AXVPERMIW + AXVPERMIV + AXVPERMIQ + AVSETEQV AVSETNEV AVSETANYEQB diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go index 422ccbd9b0bc0a..607e6063110a3c 100644 --- a/src/cmd/internal/obj/loong64/anames.go +++ b/src/cmd/internal/obj/loong64/anames.go @@ -586,6 +586,10 @@ var Anames = []string{ "XVSHUF4IH", "XVSHUF4IW", "XVSHUF4IV", + "VPERMIW", + "XVPERMIW", + "XVPERMIV", + "XVPERMIQ", "VSETEQV", "VSETNEV", "VSETANYEQB", diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 7eb5668d82e231..87691838861c3d 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -58,6 +58,8 @@ var optab = []Optab{ {AMOVW, C_REG, C_NONE, C_NONE, C_REG, C_NONE, 1, 4, 0, 0}, {AMOVV, C_REG, C_NONE, C_NONE, C_REG, C_NONE, 1, 4, 0, 0}, + {AVMOVQ, C_VREG, C_NONE, C_NONE, C_VREG, C_NONE, 1, 4, 0, 0}, + {AXVMOVQ, C_XREG, C_NONE, C_NONE, C_XREG, C_NONE, 1, 4, 0, 0}, {AMOVB, C_REG, C_NONE, C_NONE, C_REG, C_NONE, 12, 4, 0, 0}, {AMOVBU, C_REG, C_NONE, C_NONE, C_REG, C_NONE, 12, 4, 0, 0}, {AMOVWU, C_REG, C_NONE, C_NONE, C_REG, C_NONE, 12, 4, 0, 0}, @@ -1778,6 +1780,7 @@ func buildop(ctxt *obj.Link) { opset(AVSHUF4IH, r0) opset(AVSHUF4IW, r0) opset(AVSHUF4IV, r0) + opset(AVPERMIW, r0) case AXVANDB: opset(AXVORB, r0) @@ -1787,6 +1790,9 @@ func buildop(ctxt *obj.Link) { opset(AXVSHUF4IH, r0) opset(AXVSHUF4IW, r0) opset(AXVSHUF4IV, r0) + opset(AXVPERMIW, r0) + opset(AXVPERMIV, r0) + opset(AXVPERMIQ, r0) case AVANDV: opset(AVORV, r0) @@ -2097,12 +2103,19 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { case 0: // pseudo ops break - case 1: // mov r1,r2 ==> OR r1,r0,r2 - a := AOR - if p.As == AMOVW { - a = ASLL + case 1: // mov rj, rd + switch p.As { + case AMOVW: + o1 = OP_RRR(c.oprrr(ASLL), uint32(REGZERO), uint32(p.From.Reg), uint32(p.To.Reg)) + case AMOVV: + o1 = OP_RRR(c.oprrr(AOR), uint32(REGZERO), uint32(p.From.Reg), uint32(p.To.Reg)) + case AVMOVQ: + o1 = OP_6IRR(c.opirr(AVSLLV), uint32(0), uint32(p.From.Reg), uint32(p.To.Reg)) + case AXVMOVQ: + o1 = OP_6IRR(c.opirr(AXVSLLV), uint32(0), uint32(p.From.Reg), uint32(p.To.Reg)) + default: + c.ctxt.Diag("unexpected encoding\n%v", p) } - o1 = OP_RRR(c.oprrr(a), uint32(REGZERO), uint32(p.From.Reg), uint32(p.To.Reg)) case 2: // add/sub r1,[r2],r3 r := int(p.Reg) @@ -4362,6 +4375,14 @@ func (c *ctxt0) opirr(a obj.As) uint32 { return 0x1de6 << 18 // xvshuf4i.w case AXVSHUF4IV: return 0x1de7 << 18 // xvshuf4i.d + case AVPERMIW: + return 0x1cf9 << 18 // vpermi.w + case AXVPERMIW: + return 0x1df9 << 18 // xvpermi.w + case AXVPERMIV: + return 0x1dfa << 18 // xvpermi.d + case AXVPERMIQ: + return 0x1dfb << 18 // xvpermi.q case AVBITCLRB: return 0x1CC4<<18 | 0x1<<13 // vbitclri.b case AVBITCLRH: diff --git a/src/cmd/internal/obj/loong64/doc.go b/src/cmd/internal/obj/loong64/doc.go index f7e5a4fb4279ea..c96501ea81b990 100644 --- a/src/cmd/internal/obj/loong64/doc.go +++ b/src/cmd/internal/obj/loong64/doc.go @@ -203,6 +203,15 @@ Note: In the following sections 3.1 to 3.6, "ui4" (4-bit unsigned int immediate) VMOVQ Vj.W[index], Vd.W4 | vreplvei.w vd, vj, ui2 | for i in range(4) : VR[vd].w[i] = VR[vj].w[ui2] VMOVQ Vj.V[index], Vd.V2 | vreplvei.d vd, vj, ui1 | for i in range(2) : VR[vd].d[i] = VR[vj].d[ui1] +3.7 Move vector register to vector register. + Instruction format: + VMOVQ Vj, Vd + + Mapping between Go and platform assembly: + Go assembly | platform assembly | semantics + VMOVQ Vj, Vd | vslli.d vd, vj, 0x0 | for i in range(2) : VR[vd].D[i] = SLL(VR[vj].D[i], 0) + VXMOVQ Xj, Xd | xvslli.d xd, xj, 0x0 | for i in range(4) : XR[xd].D[i] = SLL(XR[xj].D[i], 0) + 3.7 Load data from memory and broadcast to each element of a vector register. Instruction format: @@ -229,6 +238,23 @@ Note: In the following sections 3.1 to 3.6, "ui4" (4-bit unsigned int immediate) VMOVQ 8(R4), V5.W4 | vldrepl.w v5, r4, $2 VMOVQ 8(R4), V5.V2 | vldrepl.d v5, r4, $1 +3.8 Vector permutation instruction + Instruction format: + VPERMIW ui8, Vj, Vd + + Mapping between Go and platform assembly: + Go assembly | platform assembly | semantics + VPERMIW ui8, Vj, Vd | vpermi.w vd, vj, ui8 | VR[vd].W[0] = VR[vj].W[ui8[1:0]], VR[vd].W[1] = VR[vj].W[ui8[3:2]], + | | VR[vd].W[2] = VR[vd].W[ui8[5:4]], VR[vd].W[3] = VR[vd].W[ui8[7:6]] + XVPERMIW ui8, Xj, Xd | xvpermi.w xd, xj, ui8 | XR[xd].W[0] = XR[xj].W[ui8[1:0]], XR[xd].W[1] = XR[xj].W[ui8[3:2]], + | | XR[xd].W[3] = XR[xd].W[ui8[7:6]], XR[xd].W[2] = XR[xd].W[ui8[5:4]], + | | XR[xd].W[4] = XR[xj].W[ui8[1:0]+4], XR[xd].W[5] = XR[xj].W[ui8[3:2]+4], + | | XR[xd].W[6] = XR[xd].W[ui8[5:4]+4], XR[xd].W[7] = XR[xd].W[ui8[7:6]+4] + XVPERMIV ui8, Xj, Xd | xvpermi.d xd, xj, ui8 | XR[xd].D[0] = XR[xj].D[ui8[1:0]], XR[xd].D[1] = XR[xj].D[ui8[3:2]], + | | XR[xd].D[2] = XR[xj].D[ui8[5:4]], XR[xd].D[3] = XR[xj].D[ui8[7:6]] + XVPERMIQ ui8, Xj, Xd | xvpermi.q xd, xj, ui8 | vec = {XR[xd], XR[xj]}, XR[xd].Q[0] = vec.Q[ui8[1:0]], XR[xd].Q[1] = vec.Q[ui8[5:4]] + + # Special instruction encoding definition and description on LoongArch 1. DBAR hint encoding for LA664(Loongson 3A6000) and later micro-architectures, paraphrased diff --git a/src/crypto/internal/constanttime/constant_time.go b/src/crypto/internal/constanttime/constant_time.go new file mode 100644 index 00000000000000..5525307195661b --- /dev/null +++ b/src/crypto/internal/constanttime/constant_time.go @@ -0,0 +1,42 @@ +// 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 constanttime + +// The functions in this package are compiler intrinsics for constant-time +// operations. They are exposed by crypto/subtle and used directly by the +// FIPS 140-3 module. + +// Select returns x if v == 1 and y if v == 0. +// Its behavior is undefined if v takes any other value. +func Select(v, x, y int) int { + // This is intrinsicified on arches with CMOV. + // It implements the following superset behavior: + // ConstantTimeSelect returns x if v != 0 and y if v == 0. + // Do the same here to avoid non portable UB. + v = int(boolToUint8(v != 0)) + return ^(v-1)&x | (v-1)&y +} + +// ByteEq returns 1 if x == y and 0 otherwise. +func ByteEq(x, y uint8) int { + return int(boolToUint8(x == y)) +} + +// Eq returns 1 if x == y and 0 otherwise. +func Eq(x, y int32) int { + return int(boolToUint8(x == y)) +} + +// LessOrEq returns 1 if x <= y and 0 otherwise. +// Its behavior is undefined if x or y are negative or > 2**31 - 1. +func LessOrEq(x, y int) int { + return int(boolToUint8(x <= y)) +} + +// boolToUint8 is a compiler intrinsic. +// It returns 1 for true and 0 for false. +func boolToUint8(b bool) uint8 { + panic("unreachable; must be intrinsicified") +} diff --git a/src/crypto/internal/fips140/edwards25519/tables.go b/src/crypto/internal/fips140/edwards25519/tables.go index 801b76771d1ea3..7da3f7b15bca63 100644 --- a/src/crypto/internal/fips140/edwards25519/tables.go +++ b/src/crypto/internal/fips140/edwards25519/tables.go @@ -4,9 +4,7 @@ package edwards25519 -import ( - "crypto/internal/fips140/subtle" -) +import "crypto/internal/constanttime" // A dynamic lookup table for variable-base, constant-time scalar muls. type projLookupTable struct { @@ -95,7 +93,7 @@ func (v *projLookupTable) SelectInto(dest *projCached, x int8) { dest.Zero() for j := 1; j <= 8; j++ { // Set dest = j*Q if |x| = j - cond := subtle.ConstantTimeByteEq(xabs, uint8(j)) + cond := constanttime.ByteEq(xabs, uint8(j)) dest.Select(&v.points[j-1], dest, cond) } // Now dest = |x|*Q, conditionally negate to get x*Q @@ -111,7 +109,7 @@ func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) { dest.Zero() for j := 1; j <= 8; j++ { // Set dest = j*Q if |x| = j - cond := subtle.ConstantTimeByteEq(xabs, uint8(j)) + cond := constanttime.ByteEq(xabs, uint8(j)) dest.Select(&v.points[j-1], dest, cond) } // Now dest = |x|*Q, conditionally negate to get x*Q diff --git a/src/crypto/internal/fips140/nistec/generate.go b/src/crypto/internal/fips140/nistec/generate.go index 7786dc556f5260..75b1ac60f0b6ec 100644 --- a/src/crypto/internal/fips140/nistec/generate.go +++ b/src/crypto/internal/fips140/nistec/generate.go @@ -140,8 +140,8 @@ const tmplNISTEC = `// Copyright 2022 The Go Authors. All rights reserved. package nistec import ( + "crypto/internal/constanttime" "crypto/internal/fips140/nistec/fiat" - "crypto/internal/fips140/subtle" "errors" "sync" ) @@ -467,7 +467,7 @@ func (table *{{.p}}Table) Select(p *{{.P}}Point, n uint8) { } p.Set(New{{.P}}Point()) for i := uint8(1); i < 16; i++ { - cond := subtle.ConstantTimeByteEq(i, n) + cond := constanttime.ByteEq(i, n) p.Select(table[i-1], p, cond) } } diff --git a/src/crypto/internal/fips140/nistec/p224.go b/src/crypto/internal/fips140/nistec/p224.go index 82bced251fe0ac..7965b186891b0b 100644 --- a/src/crypto/internal/fips140/nistec/p224.go +++ b/src/crypto/internal/fips140/nistec/p224.go @@ -7,8 +7,8 @@ package nistec import ( + "crypto/internal/constanttime" "crypto/internal/fips140/nistec/fiat" - "crypto/internal/fips140/subtle" "errors" "sync" ) @@ -333,7 +333,7 @@ func (table *p224Table) Select(p *P224Point, n uint8) { } p.Set(NewP224Point()) for i := uint8(1); i < 16; i++ { - cond := subtle.ConstantTimeByteEq(i, n) + cond := constanttime.ByteEq(i, n) p.Select(table[i-1], p, cond) } } diff --git a/src/crypto/internal/fips140/nistec/p256.go b/src/crypto/internal/fips140/nistec/p256.go index c957c5424737b0..650bde4e73e0a7 100644 --- a/src/crypto/internal/fips140/nistec/p256.go +++ b/src/crypto/internal/fips140/nistec/p256.go @@ -7,8 +7,8 @@ package nistec import ( + "crypto/internal/constanttime" "crypto/internal/fips140/nistec/fiat" - "crypto/internal/fips140/subtle" "crypto/internal/fips140deps/byteorder" "crypto/internal/fips140deps/cpu" "errors" @@ -458,7 +458,7 @@ func (table *p256Table) Select(p *P256Point, n uint8) { } p.Set(NewP256Point()) for i := uint8(1); i <= 16; i++ { - cond := subtle.ConstantTimeByteEq(i, n) + cond := constanttime.ByteEq(i, n) p.Select(&table[i-1], p, cond) } } @@ -553,7 +553,7 @@ func (table *p256AffineTable) Select(p *p256AffinePoint, n uint8) { panic("nistec: internal error: p256AffineTable.Select called with out-of-bounds value") } for i := uint8(1); i <= 32; i++ { - cond := subtle.ConstantTimeByteEq(i, n) + cond := constanttime.ByteEq(i, n) p.x.Select(&table[i-1].x, &p.x, cond) p.y.Select(&table[i-1].y, &p.y, cond) } @@ -618,7 +618,7 @@ func (p *P256Point) ScalarBaseMult(scalar []byte) (*P256Point, error) { // the point at infinity (because infinity can't be represented in affine // coordinates). Here we conditionally set p to the infinity if sel is zero. // In the loop, that's handled by AddAffine. - selIsZero := subtle.ConstantTimeByteEq(sel, 0) + selIsZero := constanttime.ByteEq(sel, 0) p.Select(NewP256Point(), t.Projective(), selIsZero) for index >= 5 { @@ -636,7 +636,7 @@ func (p *P256Point) ScalarBaseMult(scalar []byte) (*P256Point, error) { table := &p256GeneratorTables[(index+1)/6] table.Select(t, sel) t.Negate(sign) - selIsZero := subtle.ConstantTimeByteEq(sel, 0) + selIsZero := constanttime.ByteEq(sel, 0) p.AddAffine(p, t, selIsZero) } diff --git a/src/crypto/internal/fips140/nistec/p384.go b/src/crypto/internal/fips140/nistec/p384.go index 318c08a97972f7..352f1a806e8ee4 100644 --- a/src/crypto/internal/fips140/nistec/p384.go +++ b/src/crypto/internal/fips140/nistec/p384.go @@ -7,8 +7,8 @@ package nistec import ( + "crypto/internal/constanttime" "crypto/internal/fips140/nistec/fiat" - "crypto/internal/fips140/subtle" "errors" "sync" ) @@ -333,7 +333,7 @@ func (table *p384Table) Select(p *P384Point, n uint8) { } p.Set(NewP384Point()) for i := uint8(1); i < 16; i++ { - cond := subtle.ConstantTimeByteEq(i, n) + cond := constanttime.ByteEq(i, n) p.Select(table[i-1], p, cond) } } diff --git a/src/crypto/internal/fips140/nistec/p521.go b/src/crypto/internal/fips140/nistec/p521.go index 8ade8a33040b7a..429f6379934904 100644 --- a/src/crypto/internal/fips140/nistec/p521.go +++ b/src/crypto/internal/fips140/nistec/p521.go @@ -7,8 +7,8 @@ package nistec import ( + "crypto/internal/constanttime" "crypto/internal/fips140/nistec/fiat" - "crypto/internal/fips140/subtle" "errors" "sync" ) @@ -333,7 +333,7 @@ func (table *p521Table) Select(p *P521Point, n uint8) { } p.Set(NewP521Point()) for i := uint8(1); i < 16; i++ { - cond := subtle.ConstantTimeByteEq(i, n) + cond := constanttime.ByteEq(i, n) p.Select(table[i-1], p, cond) } } diff --git a/src/crypto/internal/fips140/rsa/pkcs1v22.go b/src/crypto/internal/fips140/rsa/pkcs1v22.go index 94e7345996a46f..29c47069a3e0ee 100644 --- a/src/crypto/internal/fips140/rsa/pkcs1v22.go +++ b/src/crypto/internal/fips140/rsa/pkcs1v22.go @@ -9,6 +9,7 @@ package rsa import ( "bytes" + "crypto/internal/constanttime" "crypto/internal/fips140" "crypto/internal/fips140/drbg" "crypto/internal/fips140/sha256" @@ -432,7 +433,7 @@ func DecryptOAEP(hash, mgfHash hash.Hash, priv *PrivateKey, ciphertext []byte, l hash.Write(label) lHash := hash.Sum(nil) - firstByteIsZero := subtle.ConstantTimeByteEq(em[0], 0) + firstByteIsZero := constanttime.ByteEq(em[0], 0) seed := em[1 : hash.Size()+1] db := em[hash.Size()+1:] @@ -458,11 +459,11 @@ func DecryptOAEP(hash, mgfHash hash.Hash, priv *PrivateKey, ciphertext []byte, l rest := db[hash.Size():] for i := 0; i < len(rest); i++ { - equals0 := subtle.ConstantTimeByteEq(rest[i], 0) - equals1 := subtle.ConstantTimeByteEq(rest[i], 1) - index = subtle.ConstantTimeSelect(lookingForIndex&equals1, i, index) - lookingForIndex = subtle.ConstantTimeSelect(equals1, 0, lookingForIndex) - invalid = subtle.ConstantTimeSelect(lookingForIndex&^equals0, 1, invalid) + equals0 := constanttime.ByteEq(rest[i], 0) + equals1 := constanttime.ByteEq(rest[i], 1) + index = constanttime.Select(lookingForIndex&equals1, i, index) + lookingForIndex = constanttime.Select(equals1, 0, lookingForIndex) + invalid = constanttime.Select(lookingForIndex&^equals0, 1, invalid) } if firstByteIsZero&lHash2Good&^invalid&^lookingForIndex != 1 { diff --git a/src/crypto/internal/fips140/subtle/constant_time.go b/src/crypto/internal/fips140/subtle/constant_time.go index fa7a002d3fa456..fc1e3079855e94 100644 --- a/src/crypto/internal/fips140/subtle/constant_time.go +++ b/src/crypto/internal/fips140/subtle/constant_time.go @@ -5,6 +5,7 @@ package subtle import ( + "crypto/internal/constanttime" "crypto/internal/fips140deps/byteorder" "math/bits" ) @@ -24,7 +25,7 @@ func ConstantTimeCompare(x, y []byte) int { v |= x[i] ^ y[i] } - return ConstantTimeByteEq(v, 0) + return constanttime.ByteEq(v, 0) } // ConstantTimeLessOrEqBytes returns 1 if x <= y and 0 otherwise. The comparison @@ -58,20 +59,6 @@ func ConstantTimeLessOrEqBytes(x, y []byte) int { return int(b ^ 1) } -// ConstantTimeSelect returns x if v == 1 and y if v == 0. -// Its behavior is undefined if v takes any other value. -func ConstantTimeSelect(v, x, y int) int { return ^(v-1)&x | (v-1)&y } - -// ConstantTimeByteEq returns 1 if x == y and 0 otherwise. -func ConstantTimeByteEq(x, y uint8) int { - return int((uint32(x^y) - 1) >> 31) -} - -// ConstantTimeEq returns 1 if x == y and 0 otherwise. -func ConstantTimeEq(x, y int32) int { - return int((uint64(uint32(x^y)) - 1) >> 63) -} - // ConstantTimeCopy copies the contents of y into x (a slice of equal length) // if v == 1. If v == 0, x is left unchanged. Its behavior is undefined if v // takes any other value. @@ -86,11 +73,3 @@ func ConstantTimeCopy(v int, x, y []byte) { x[i] = x[i]&xmask | y[i]&ymask } } - -// ConstantTimeLessOrEq returns 1 if x <= y and 0 otherwise. -// Its behavior is undefined if x or y are negative or > 2**31 - 1. -func ConstantTimeLessOrEq(x, y int) int { - x32 := int32(x) - y32 := int32(y) - return int(((x32 - y32 - 1) >> 31) & 1) -} diff --git a/src/crypto/internal/fips140deps/fipsdeps_test.go b/src/crypto/internal/fips140deps/fipsdeps_test.go index 3eaae1830d0e18..29a56047c3c5fd 100644 --- a/src/crypto/internal/fips140deps/fipsdeps_test.go +++ b/src/crypto/internal/fips140deps/fipsdeps_test.go @@ -28,6 +28,9 @@ var AllowedInternalPackages = map[string]bool{ // randutil.MaybeReadByte is used in non-FIPS mode by GenerateKey functions. "crypto/internal/randutil": true, + + // constanttime are the constant-time intrinsics. + "crypto/internal/constanttime": true, } func TestImports(t *testing.T) { diff --git a/src/crypto/subtle/constant_time.go b/src/crypto/subtle/constant_time.go index 8eeff3b629befb..14c911101b0fb8 100644 --- a/src/crypto/subtle/constant_time.go +++ b/src/crypto/subtle/constant_time.go @@ -6,63 +6,47 @@ // code but require careful thought to use correctly. package subtle -import "crypto/internal/fips140/subtle" +import ( + "crypto/internal/constanttime" + "crypto/internal/fips140/subtle" +) + +// These functions are forwarded to crypto/internal/constanttime for intrinsified +// operations, and to crypto/internal/fips140/subtle for byte slice operations. // ConstantTimeCompare returns 1 if the two slices, x and y, have equal contents // and 0 otherwise. The time taken is a function of the length of the slices and // is independent of the contents. If the lengths of x and y do not match it // returns 0 immediately. func ConstantTimeCompare(x, y []byte) int { - if len(x) != len(y) { - return 0 - } - - var v byte - - for i := 0; i < len(x); i++ { - v |= x[i] ^ y[i] - } - - return ConstantTimeByteEq(v, 0) + return subtle.ConstantTimeCompare(x, y) } // ConstantTimeSelect returns x if v == 1 and y if v == 0. // Its behavior is undefined if v takes any other value. func ConstantTimeSelect(v, x, y int) int { - // This is intrinsicified on arches with CMOV. - // It implements the following superset behavior: - // ConstantTimeSelect returns x if v != 0 and y if v == 0. - // Do the same here to avoid non portable UB. - v = int(constantTimeBoolToUint8(v != 0)) - return ^(v-1)&x | (v-1)&y + return constanttime.Select(v, x, y) } // ConstantTimeByteEq returns 1 if x == y and 0 otherwise. func ConstantTimeByteEq(x, y uint8) int { - return int(constantTimeBoolToUint8(x == y)) + return constanttime.ByteEq(x, y) } // ConstantTimeEq returns 1 if x == y and 0 otherwise. func ConstantTimeEq(x, y int32) int { - return int(constantTimeBoolToUint8(x == y)) + return constanttime.Eq(x, y) } // ConstantTimeCopy copies the contents of y into x (a slice of equal length) // if v == 1. If v == 0, x is left unchanged. Its behavior is undefined if v // takes any other value. func ConstantTimeCopy(v int, x, y []byte) { - // Forward this one since it gains nothing from compiler intrinsics. subtle.ConstantTimeCopy(v, x, y) } // ConstantTimeLessOrEq returns 1 if x <= y and 0 otherwise. // Its behavior is undefined if x or y are negative or > 2**31 - 1. func ConstantTimeLessOrEq(x, y int) int { - return int(constantTimeBoolToUint8(x <= y)) -} - -// constantTimeBoolToUint8 is a compiler intrinsic. -// It returns 1 for true and 0 for false. -func constantTimeBoolToUint8(b bool) uint8 { - panic("unreachable; must be intrinsicified") + return constanttime.LessOrEq(x, y) } diff --git a/src/crypto/tls/bettertls_test.go b/src/crypto/tls/bettertls_test.go new file mode 100644 index 00000000000000..d1b06109288e5c --- /dev/null +++ b/src/crypto/tls/bettertls_test.go @@ -0,0 +1,230 @@ +// 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. + +// This test uses Netflix's BetterTLS test suite to test the crypto/x509 +// path building and name constraint validation. +// +// The test data in JSON form is around 31MB, so we fetch the BetterTLS +// go module and use it to generate the JSON data on-the-fly in a tmp dir. +// +// For more information, see: +// https://github.com/netflix/bettertls +// https://netflixtechblog.com/bettertls-c9915cd255c0 + +package tls_test + +import ( + "crypto/internal/cryptotest" + "crypto/x509" + "encoding/base64" + "encoding/json" + "internal/testenv" + "os" + "path/filepath" + "testing" +) + +// TestBetterTLS runs the "pathbuilding" and "nameconstraints" suites of +// BetterTLS. +// +// The test cases in the pathbuilding suite are designed to test edge-cases +// for path building and validation. In particular, the ["chain of pain"][0] +// scenario where a validator treats path building as an operation with +// a single possible outcome, instead of many. +// +// The test cases in the nameconstraints suite are designed to test edge-cases +// for name constraint parsing and validation. +// +// [0]: https://medium.com/@sleevi_/path-building-vs-path-verifying-the-chain-of-pain-9fbab861d7d6 +func TestBetterTLS(t *testing.T) { + testenv.SkipIfShortAndSlow(t) + + data, roots := testData(t) + + for _, suite := range []string{"pathbuilding", "nameconstraints"} { + t.Run(suite, func(t *testing.T) { + runTestSuite(t, suite, &data, roots) + }) + } +} + +func runTestSuite(t *testing.T, suiteName string, data *betterTLS, roots *x509.CertPool) { + suite, exists := data.Suites[suiteName] + if !exists { + t.Fatalf("missing %s suite", suiteName) + } + + t.Logf( + "running %s test suite with %d test cases", + suiteName, len(suite.TestCases)) + + for _, tc := range suite.TestCases { + t.Logf("testing %s test case %d", suiteName, tc.ID) + + certsDER, err := tc.Certs() + if err != nil { + t.Fatalf( + "failed to decode certificates for test case %d: %v", + tc.ID, err) + } + + if len(certsDER) == 0 { + t.Fatalf("test case %d has no certificates", tc.ID) + } + + eeCert, err := x509.ParseCertificate(certsDER[0]) + if err != nil { + // Several constraint test cases contain invalid end-entity + // certificate extensions that we reject ahead of verification + // time. We consider this a pass and skip further processing. + // + // For example, a SAN with a uniformResourceIdentifier general name + // containing the value `"http://foo.bar, DNS:test.localhost"`, or + // an iPAddress general name of the wrong length. + if suiteName == "nameconstraints" && tc.Expected == expectedReject { + t.Logf( + "skipping expected reject test case %d "+ + "- end entity certificate parse error: %v", + tc.ID, err) + continue + } + t.Fatalf( + "failed to parse end entity certificate for test case %d: %v", + tc.ID, err) + } + + intermediates := x509.NewCertPool() + for i, certDER := range certsDER[1:] { + cert, err := x509.ParseCertificate(certDER) + if err != nil { + t.Fatalf( + "failed to parse intermediate certificate %d for test case %d: %v", + i+1, tc.ID, err) + } + intermediates.AddCert(cert) + } + + _, err = eeCert.Verify(x509.VerifyOptions{ + Roots: roots, + Intermediates: intermediates, + DNSName: tc.Hostname, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + + switch tc.Expected { + case expectedAccept: + if err != nil { + t.Errorf( + "test case %d failed: expected success, got error: %v", + tc.ID, err) + } + case expectedReject: + if err == nil { + t.Errorf( + "test case %d failed: expected failure, but verification succeeded", + tc.ID) + } + default: + t.Fatalf( + "test case %d failed: unknown expected result: %s", + tc.ID, tc.Expected) + } + } +} + +func testData(t *testing.T) (betterTLS, *x509.CertPool) { + const ( + bettertlsModule = "github.com/Netflix/bettertls" + bettertlsVersion = "v0.0.0-20250909192348-e1e99e353074" + ) + + bettertlsDir := cryptotest.FetchModule(t, bettertlsModule, bettertlsVersion) + + tempDir := t.TempDir() + testsJSONPath := filepath.Join(tempDir, "tests.json") + + cmd := testenv.Command(t, testenv.GoToolPath(t), + "run", "./test-suites/cmd/bettertls", + "export-tests", + "--out", testsJSONPath) + cmd.Dir = bettertlsDir + + t.Log("running bettertls export-tests command") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf( + "failed to run bettertls export-tests: %v\nOutput: %s", + err, output) + } + + jsonData, err := os.ReadFile(testsJSONPath) + if err != nil { + t.Fatalf("failed to read exported tests.json: %v", err) + } + + t.Logf("successfully loaded tests.json at %s", testsJSONPath) + + var data betterTLS + if err := json.Unmarshal(jsonData, &data); err != nil { + t.Fatalf("failed to unmarshal JSON data: %v", err) + } + + t.Logf("testing betterTLS revision: %s", data.Revision) + t.Logf("number of test suites: %d", len(data.Suites)) + + rootDER, err := data.RootCert() + if err != nil { + t.Fatalf("failed to decode trust root: %v", err) + } + + rootCert, err := x509.ParseCertificate(rootDER) + if err != nil { + t.Fatalf("failed to parse trust root certificate: %v", err) + } + + roots := x509.NewCertPool() + roots.AddCert(rootCert) + + return data, roots +} + +type betterTLS struct { + Revision string `json:"betterTlsRevision"` + Root string `json:"trustRoot"` + Suites map[string]betterTLSSuite `json:"suites"` +} + +func (b *betterTLS) RootCert() ([]byte, error) { + return base64.StdEncoding.DecodeString(b.Root) +} + +type betterTLSSuite struct { + TestCases []betterTLSTest `json:"testCases"` +} + +type betterTLSTest struct { + ID uint32 `json:"id"` + Certificates []string `json:"certificates"` + Hostname string `json:"hostname"` + Expected expectedResult `json:"expected"` +} + +func (test *betterTLSTest) Certs() ([][]byte, error) { + certs := make([][]byte, len(test.Certificates)) + for i, cert := range test.Certificates { + decoded, err := base64.StdEncoding.DecodeString(cert) + if err != nil { + return nil, err + } + certs[i] = decoded + } + return certs, nil +} + +type expectedResult string + +const ( + expectedAccept expectedResult = "ACCEPT" + expectedReject expectedResult = "REJECT" +) diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index bc7eae69def15f..48a9f3e75bb7b5 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -479,6 +479,8 @@ var depsRules = ` io, math/rand/v2 < crypto/internal/randutil; + NONE < crypto/internal/constanttime; + STR < crypto/internal/impl; OS < crypto/internal/sysrand @@ -496,6 +498,7 @@ var depsRules = ` crypto/internal/impl, crypto/internal/entropy, crypto/internal/randutil, + crypto/internal/constanttime, crypto/internal/entropy/v1.0.0, crypto/internal/fips140deps/byteorder, crypto/internal/fips140deps/cpu, diff --git a/src/internal/godebugs/godebugs_test.go b/src/internal/godebugs/godebugs_test.go index 168acc134aa753..e242f58c5536c8 100644 --- a/src/internal/godebugs/godebugs_test.go +++ b/src/internal/godebugs/godebugs_test.go @@ -93,3 +93,11 @@ func incNonDefaults(t *testing.T) map[string]bool { } return seen } + +func TestRemoved(t *testing.T) { + for _, info := range godebugs.Removed { + if godebugs.Lookup(info.Name) != nil { + t.Fatalf("GODEBUG: %v exists in both Removed and All", info.Name) + } + } +} diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 852305e8553aab..271c58648dc2cd 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -78,6 +78,16 @@ var All = []Info{ {Name: "zipinsecurepath", Package: "archive/zip"}, } +type RemovedInfo struct { + Name string // name of the removed GODEBUG setting. + Removed int // minor version of Go, when the removal happened +} + +// Removed contains all GODEBUGs that we have removed. +var Removed = []RemovedInfo{ + {Name: "x509sha1", Removed: 24}, +} + // Lookup returns the Info with the given name. func Lookup(name string) *Info { // binary search, avoiding import of sort. diff --git a/src/internal/profile/proto.go b/src/internal/profile/proto.go index 58ff0ad2e07789..ad6f621f883c7d 100644 --- a/src/internal/profile/proto.go +++ b/src/internal/profile/proto.go @@ -24,6 +24,7 @@ package profile import ( "errors" "fmt" + "slices" ) type buffer struct { @@ -175,6 +176,16 @@ func le32(p []byte) uint32 { return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24 } +func peekNumVarints(data []byte) (numVarints int) { + for ; len(data) > 0; numVarints++ { + var err error + if _, data, err = decodeVarint(data); err != nil { + break + } + } + return numVarints +} + func decodeVarint(data []byte) (uint64, []byte, error) { var i int var u uint64 @@ -275,6 +286,9 @@ func decodeInt64(b *buffer, x *int64) error { func decodeInt64s(b *buffer, x *[]int64) error { if b.typ == 2 { // Packed encoding + dataLen := peekNumVarints(b.data) + *x = slices.Grow(*x, dataLen) + data := b.data for len(data) > 0 { var u uint64 @@ -305,8 +319,11 @@ func decodeUint64(b *buffer, x *uint64) error { func decodeUint64s(b *buffer, x *[]uint64) error { if b.typ == 2 { - data := b.data // Packed encoding + dataLen := peekNumVarints(b.data) + *x = slices.Grow(*x, dataLen) + + data := b.data for len(data) > 0 { var u uint64 var err error diff --git a/src/internal/runtime/cgobench/bench_test.go b/src/internal/runtime/cgobench/bench_test.go index b4d8efec5efcf6..3b8f9a8ca3aee2 100644 --- a/src/internal/runtime/cgobench/bench_test.go +++ b/src/internal/runtime/cgobench/bench_test.go @@ -24,3 +24,17 @@ func BenchmarkCgoCallParallel(b *testing.B) { } }) } + +func BenchmarkCgoCallWithCallback(b *testing.B) { + for b.Loop() { + cgobench.Callback() + } +} + +func BenchmarkCgoCallParallelWithCallback(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + cgobench.Callback() + } + }) +} diff --git a/src/internal/runtime/cgobench/funcs.go b/src/internal/runtime/cgobench/funcs.go index db685180a1b594..91efa5127893e5 100644 --- a/src/internal/runtime/cgobench/funcs.go +++ b/src/internal/runtime/cgobench/funcs.go @@ -9,9 +9,24 @@ package cgobench /* static void empty() { } + +void go_empty_callback(); + +static void callback() { + go_empty_callback(); +} + */ import "C" func Empty() { C.empty() } + +func Callback() { + C.callback() +} + +//export go_empty_callback +func go_empty_callback() { +} diff --git a/src/internal/strconv/atofeisel.go b/src/internal/strconv/atofeisel.go index 10b8c96bba94e2..5fa92908b49ced 100644 --- a/src/internal/strconv/atofeisel.go +++ b/src/internal/strconv/atofeisel.go @@ -40,7 +40,7 @@ func eiselLemire64(man uint64, exp10 int, neg bool) (f float64, ok bool) { // Normalization. clz := bits.LeadingZeros64(man) man <<= uint(clz) - retExp2 := uint64(exp2+64-float64Bias) - uint64(clz) + retExp2 := uint64(exp2+63-float64Bias) - uint64(clz) // Multiplication. xHi, xLo := bits.Mul64(man, pow.Hi) @@ -115,7 +115,7 @@ func eiselLemire32(man uint64, exp10 int, neg bool) (f float32, ok bool) { // Normalization. clz := bits.LeadingZeros64(man) man <<= uint(clz) - retExp2 := uint64(exp2+64-float32Bias) - uint64(clz) + retExp2 := uint64(exp2+63-float32Bias) - uint64(clz) // Multiplication. xHi, xLo := bits.Mul64(man, pow.Hi) diff --git a/src/internal/strconv/atoi.go b/src/internal/strconv/atoi.go index 5bc259e7e55e83..4bbcb4f5da7aeb 100644 --- a/src/internal/strconv/atoi.go +++ b/src/internal/strconv/atoi.go @@ -41,8 +41,6 @@ const intSize = 32 << (^uint(0) >> 63) // IntSize is the size in bits of an int or uint value. const IntSize = intSize -const maxUint64 = 1<<64 - 1 - // ParseUint is like [ParseInt] but for unsigned numbers. // // A sign prefix is not permitted. diff --git a/src/internal/strconv/export_test.go b/src/internal/strconv/export_test.go index bea741e6fbe1bf..c879f24480a450 100644 --- a/src/internal/strconv/export_test.go +++ b/src/internal/strconv/export_test.go @@ -6,6 +6,11 @@ package strconv type Uint128 = uint128 +const ( + Pow10Min = pow10Min + Pow10Max = pow10Max +) + var ( MulLog10_2 = mulLog10_2 MulLog2_10 = mulLog2_10 @@ -13,6 +18,9 @@ var ( Pow10 = pow10 Umul128 = umul128 Umul192 = umul192 + Div5Tab = div5Tab + DivisiblePow5 = divisiblePow5 + TrimZeros = trimZeros ) func NewDecimal(i uint64) *decimal { diff --git a/src/internal/strconv/fp_test.go b/src/internal/strconv/fp_test.go index 042328c7d4e4c8..ba739941cc8a92 100644 --- a/src/internal/strconv/fp_test.go +++ b/src/internal/strconv/fp_test.go @@ -99,12 +99,14 @@ func TestFp(t *testing.T) { s := bufio.NewScanner(strings.NewReader(testfp)) for lineno := 1; s.Scan(); lineno++ { line := s.Text() - if len(line) == 0 || line[0] == '#' { + line, _, _ = strings.Cut(line, "#") + line = strings.TrimSpace(line) + if line == "" { continue } a := strings.Split(line, " ") if len(a) != 4 { - t.Error("testdata/testfp.txt:", lineno, ": wrong field count") + t.Errorf("testdata/testfp.txt:%d: wrong field count", lineno) continue } var s string @@ -114,22 +116,21 @@ func TestFp(t *testing.T) { var ok bool v, ok = myatof64(a[2]) if !ok { - t.Error("testdata/testfp.txt:", lineno, ": cannot atof64 ", a[2]) + t.Errorf("testdata/testfp.txt:%d: cannot atof64 %s", lineno, a[2]) continue } s = fmt.Sprintf(a[1], v) case "float32": v1, ok := myatof32(a[2]) if !ok { - t.Error("testdata/testfp.txt:", lineno, ": cannot atof32 ", a[2]) + t.Errorf("testdata/testfp.txt:%d: cannot atof32 %s", lineno, a[2]) continue } s = fmt.Sprintf(a[1], v1) v = float64(v1) } if s != a[3] { - t.Error("testdata/testfp.txt:", lineno, ": ", a[0], " ", a[1], " ", a[2], " (", v, ") ", - "want ", a[3], " got ", s) + t.Errorf("testdata/testfp.txt:%d: %s %s %s %s: have %s want %s", lineno, a[0], a[1], a[2], a[3], s, a[3]) } } if s.Err() != nil { diff --git a/src/internal/strconv/ftoa.go b/src/internal/strconv/ftoa.go index 1aec5447ece8b8..fd30f28289ae9d 100644 --- a/src/internal/strconv/ftoa.go +++ b/src/internal/strconv/ftoa.go @@ -123,16 +123,17 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { return bigFtoa(dst, prec, fmt, neg, mant, exp, flt) } - var digs decimalSlice - ok := false // Negative precision means "only as much as needed to be exact." shortest := prec < 0 + var digs decimalSlice + if mant == 0 { + return formatDigits(dst, shortest, neg, digs, prec, fmt) + } if shortest { // Use Ryu algorithm. var buf [32]byte digs.d = buf[:] ryuFtoaShortest(&digs, mant, exp-int(flt.mantbits), flt) - ok = true // Precision for shortest representation mode. switch fmt { case 'e', 'E': @@ -142,7 +143,11 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { case 'g', 'G': prec = digs.nd } - } else if fmt != 'f' { + return formatDigits(dst, shortest, neg, digs, prec, fmt) + } + + // TODO figure out when we can use fast code for f + if fmt != 'f' { // Fixed number of digits. digits := prec switch fmt { @@ -157,21 +162,15 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { // Invalid mode. digits = 1 } - var buf [24]byte - if bitSize == 32 && digits <= 9 { + if digits <= 18 { + var buf [24]byte digs.d = buf[:] - ryuFtoaFixed32(&digs, uint32(mant), exp-int(flt.mantbits), digits) - ok = true - } else if digits <= 18 { - digs.d = buf[:] - ryuFtoaFixed64(&digs, mant, exp-int(flt.mantbits), digits) - ok = true + fixedFtoa(&digs, mant, exp-int(flt.mantbits), digits) + return formatDigits(dst, false, neg, digs, prec, fmt) } } - if !ok { - return bigFtoa(dst, prec, fmt, neg, mant, exp, flt) - } - return formatDigits(dst, shortest, neg, digs, prec, fmt) + + return bigFtoa(dst, prec, fmt, neg, mant, exp, flt) } // bigFtoa uses multiprecision computations to format a float. diff --git a/src/internal/strconv/ftoa_test.go b/src/internal/strconv/ftoa_test.go index 14d1200ff26b48..4e6f4629288c35 100644 --- a/src/internal/strconv/ftoa_test.go +++ b/src/internal/strconv/ftoa_test.go @@ -5,9 +5,9 @@ package strconv_test import ( + . "internal/strconv" "math" "math/rand" - . "internal/strconv" "testing" ) @@ -177,6 +177,16 @@ var ftoatests = []ftoaTest{ {1.801439850948199e+16, 'g', -1, "1.801439850948199e+16"}, {5.960464477539063e-08, 'g', -1, "5.960464477539063e-08"}, {1.012e-320, 'g', -1, "1.012e-320"}, + + // Cases from TestFtoaRandom that caught bugs in fixedFtoa. + {8177880169308380. * (1 << 1), 'e', 14, "1.63557603386168e+16"}, + {8393378656576888. * (1 << 1), 'e', 15, "1.678675731315378e+16"}, + {8738676561280626. * (1 << 4), 'e', 16, "1.3981882498049002e+17"}, + {8291032395191335. / (1 << 30), 'e', 5, "7.72163e+06"}, + + // Exercise divisiblePow5 case in fixedFtoa + {2384185791015625. * (1 << 12), 'e', 5, "9.76562e+18"}, + {2384185791015625. * (1 << 13), 'e', 5, "1.95312e+19"}, } func TestFtoa(t *testing.T) { @@ -253,7 +263,7 @@ func TestFtoaRandom(t *testing.T) { shortSlow = FormatFloat(x, 'e', prec, 64) SetOptimize(true) if shortSlow != shortFast { - t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow) + t.Errorf("%b printed with %%.%de as %s, want %s", x, prec, shortFast, shortSlow) } } } @@ -294,8 +304,10 @@ var ftoaBenches = []struct { {"64Fixed1", 123456, 'e', 3, 64}, {"64Fixed2", 123.456, 'e', 3, 64}, + {"64Fixed2.5", 1.2345e+06, 'e', 3, 64}, {"64Fixed3", 1.23456e+78, 'e', 3, 64}, {"64Fixed4", 1.23456e-78, 'e', 3, 64}, + {"64Fixed5Hard", 4.096e+25, 'e', 5, 64}, // needs divisiblePow5(..., 20) {"64Fixed12", 1.23456e-78, 'e', 12, 64}, {"64Fixed16", 1.23456e-78, 'e', 16, 64}, // From testdata/testfp.txt @@ -303,6 +315,10 @@ var ftoaBenches = []struct { {"64Fixed17Hard", math.Ldexp(8887055249355788, 665), 'e', 17, 64}, {"64Fixed18Hard", math.Ldexp(6994187472632449, 690), 'e', 18, 64}, + {"64FixedF1", 123.456, 'f', 6, 64}, + {"64FixedF2", 0.0123, 'f', 6, 64}, + {"64FixedF3", 12.3456, 'f', 2, 64}, + // Trigger slow path (see issue #15672). // The shortest is: 8.034137530808823e+43 {"Slowpath64", 8.03413753080882349e+43, 'e', -1, 64}, diff --git a/src/internal/strconv/ftoafixed.go b/src/internal/strconv/ftoafixed.go new file mode 100644 index 00000000000000..f3542d1cf584aa --- /dev/null +++ b/src/internal/strconv/ftoafixed.go @@ -0,0 +1,156 @@ +// 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 strconv + +import "math/bits" + +var uint64pow10 = [...]uint64{ + 1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, +} + +// fixedFtoa formats a number of decimal digits of mant*(2^exp) into d, +// where mant > 0 and 1 ≤ digits ≤ 18. +func fixedFtoa(d *decimalSlice, mant uint64, exp, digits int) { + // The strategy here is to multiply (mant * 2^exp) by a power of 10 + // to make the resulting integer be the number of digits we want. + // + // Adams proved in the Ryu paper that 128-bit precision in the + // power-of-10 constant is sufficient to produce correctly + // rounded output for all float64s, up to 18 digits. + // https://dl.acm.org/doi/10.1145/3192366.3192369 + // + // TODO(rsc): The paper is not focused on, nor terribly clear about, + // this fact in this context, and the proof seems too complicated. + // Post a shorter, more direct proof and link to it here. + + if digits > 18 { + panic("fixedFtoa called with digits > 18") + } + + // Shift mantissa to have 64 bits, + // so that the 192-bit product below will + // have at least 63 bits in its top word. + b := 64 - bits.Len64(mant) + mant <<= b + exp -= b + + // We have f = mant * 2^exp ≥ 2^(63+exp) + // and we want to multiply it by some 10^p + // to make it have the number of digits plus one rounding bit: + // + // 2 * 10^(digits-1) ≤ f * 10^p < ~2 * 10^digits + // + // The lower bound is required, but the upper bound is approximate: + // we must not have too few digits, but we can round away extra ones. + // + // f * 10^p ≥ 2 * 10^(digits-1) + // 10^p ≥ 2 * 10^(digits-1) / f [dividing by f] + // p ≥ (log₁₀ 2) + (digits-1) - log₁₀ f [taking log₁₀] + // p ≥ (log₁₀ 2) + (digits-1) - log₁₀ (mant * 2^exp) [expanding f] + // p ≥ (log₁₀ 2) + (digits-1) - (log₁₀ 2) * (64 + exp) [mant < 2⁶⁴] + // p ≥ (digits - 1) - (log₁₀ 2) * (63 + exp) [refactoring] + // + // Once we have p, we can compute the scaled value: + // + // dm * 2^de = mant * 2^exp * 10^p + // = mant * 2^exp * pow/2^128 * 2^exp2. + // = (mant * pow/2^128) * 2^(exp+exp2). + p := (digits - 1) - mulLog10_2(63+exp) + pow, exp2, ok := pow10(p) + if !ok { + // This never happens due to the range of float32/float64 exponent + panic("fixedFtoa: pow10 out of range") + } + if -22 <= p && p < 0 { + // Special case: Let q=-p. q is in [1,22]. We are dividing by 10^q + // and the mantissa may be a multiple of 5^q (5^22 < 2^53), + // in which case the division must be computed exactly and + // recorded as exact for correct rounding. Our normal computation is: + // + // dm = floor(mant * floor(10^p * 2^s)) + // + // for some scaling shift s. To make this an exact division, + // it suffices to change the inner floor to a ceil: + // + // dm = floor(mant * ceil(10^p * 2^s)) + // + // In the range of values we are using, the floor and ceil + // cancel each other out and the high 64 bits of the product + // come out exactly right. + // (This is the same trick compilers use for division by constants. + // See Hacker's Delight, 2nd ed., Chapter 10.) + pow.Lo++ + } + dm, lo1, lo0 := umul192(mant, pow) + de := exp + exp2 + + // Check whether any bits have been truncated from dm. + // If so, set dt != 0. If not, leave dt == 0 (meaning dm is exact). + var dt uint + switch { + default: + // Most powers of 10 use a truncated constant, + // meaning the result is also truncated. + dt = 1 + case 0 <= p && p <= 55: + // Small positive powers of 10 (up to 10⁵⁵) can be represented + // precisely in a 128-bit mantissa (5⁵⁵ ≤ 2¹²⁸), so the only truncation + // comes from discarding the low bits of the 192-bit product. + // + // TODO(rsc): The new proof mentioned above should also + // prove that we can't have lo1 == 0 and lo0 != 0. + // After proving that, drop computation and use of lo0 here. + dt = bool2uint(lo1|lo0 != 0) + case -22 <= p && p < 0 && divisiblePow5(mant, -p): + // If the original mantissa was a multiple of 5^p, + // the result is exact. (See comment above for pow.Lo++.) + dt = 0 + } + + // The value we want to format is dm * 2^de, where de < 0. + // Multply by 2^de by shifting, but leave one extra bit for rounding. + // After the shift, the "integer part" of dm is dm>>1, + // the "rounding bit" (the first fractional bit) is dm&1, + // and the "truncated bit" (have any bits been discarded?) is dt. + shift := -de - 1 + dt |= bool2uint(dm&(1<>= shift + + // Set decimal point in eventual formatted digits, + // so we can update it as we adjust the digits. + d.dp = digits - p + + // Trim excess digit if any, updating truncation and decimal point. + // The << 1 is leaving room for the rounding bit. + max := uint64pow10[digits] << 1 + if dm >= max { + var r uint + dm, r = dm/10, uint(dm%10) + dt |= bool2uint(r != 0) + d.dp++ + } + + // Round and shift away rounding bit. + // We want to round up when + // (a) the fractional part is > 0.5 (dm&1 != 0 and dt == 1) + // (b) or the fractional part is ≥ 0.5 and the integer part is odd + // (dm&1 != 0 and dm&2 != 0). + // The bitwise expression encodes that logic. + dm += uint64(uint(dm) & (dt | uint(dm)>>1) & 1) + dm >>= 1 + if dm == max>>1 { + // 999... rolled over to 1000... + dm = uint64pow10[digits-1] + d.dp++ + } + + // Format digits into d. + formatBase10(d.d[:digits], dm) + d.nd = digits + for d.d[d.nd-1] == '0' { + d.nd-- + } +} diff --git a/src/internal/strconv/ftoaryu.go b/src/internal/strconv/ftoaryu.go index 473e5b65be8d4c..9407bfec445680 100644 --- a/src/internal/strconv/ftoaryu.go +++ b/src/internal/strconv/ftoaryu.go @@ -4,203 +4,11 @@ package strconv -import ( - "math/bits" -) +import "math/bits" // binary to decimal conversion using the Ryū algorithm. // // See Ulf Adams, "Ryū: Fast Float-to-String Conversion" (doi:10.1145/3192366.3192369) -// -// Fixed precision formatting is a variant of the original paper's -// algorithm, where a single multiplication by 10^k is required, -// sharing the same rounding guarantees. - -// ryuFtoaFixed32 formats mant*(2^exp) with prec decimal digits. -func ryuFtoaFixed32(d *decimalSlice, mant uint32, exp int, prec int) { - if prec < 0 { - panic("ryuFtoaFixed32 called with negative prec") - } - if prec > 9 { - panic("ryuFtoaFixed32 called with prec > 9") - } - // Zero input. - if mant == 0 { - d.nd, d.dp = 0, 0 - return - } - // Renormalize to a 25-bit mantissa. - e2 := exp - if b := bits.Len32(mant); b < 25 { - mant <<= uint(25 - b) - e2 += b - 25 - } - // Choose an exponent such that rounded mant*(2^e2)*(10^q) has - // at least prec decimal digits, i.e - // mant*(2^e2)*(10^q) >= 10^(prec-1) - // Because mant >= 2^24, it is enough to choose: - // 2^(e2+24) >= 10^(-q+prec-1) - // or q = -mulLog10_2(e2+24) + prec - 1 - q := -mulLog10_2(e2+24) + prec - 1 - - // Now compute mant*(2^e2)*(10^q). - // Is it an exact computation? - // Only small positive powers of 10 are exact (5^28 has 66 bits). - exact := q <= 27 && q >= 0 - - di, dexp2, d0 := mult64bitPow10(mant, e2, q) - if dexp2 >= 0 { - panic("not enough significant bits after mult64bitPow10") - } - // As a special case, computation might still be exact, if exponent - // was negative and if it amounts to computing an exact division. - // In that case, we ignore all lower bits. - // Note that division by 10^11 cannot be exact as 5^11 has 26 bits. - if q < 0 && q >= -10 && divisibleByPower5(uint64(mant), -q) { - exact = true - d0 = true - } - // Remove extra lower bits and keep rounding info. - extra := uint(-dexp2) - extraMask := uint32(1<>extra, di&extraMask - roundUp := false - if exact { - // If we computed an exact product, d + 1/2 - // should round to d+1 if 'd' is odd. - roundUp = dfrac > 1<<(extra-1) || - (dfrac == 1<<(extra-1) && !d0) || - (dfrac == 1<<(extra-1) && d0 && di&1 == 1) - } else { - // otherwise, d+1/2 always rounds up because - // we truncated below. - roundUp = dfrac>>(extra-1) == 1 - } - if dfrac != 0 { - d0 = false - } - // Proceed to the requested number of digits - formatDecimal(d, uint64(di), !d0, roundUp, prec) - // Adjust exponent - d.dp -= q -} - -// ryuFtoaFixed64 formats mant*(2^exp) with prec decimal digits. -func ryuFtoaFixed64(d *decimalSlice, mant uint64, exp int, prec int) { - if prec > 18 { - panic("ryuFtoaFixed64 called with prec > 18") - } - // Zero input. - if mant == 0 { - d.nd, d.dp = 0, 0 - return - } - // Renormalize to a 55-bit mantissa. - e2 := exp - if b := bits.Len64(mant); b < 55 { - mant = mant << uint(55-b) - e2 += b - 55 - } - // Choose an exponent such that rounded mant*(2^e2)*(10^q) has - // at least prec decimal digits, i.e - // mant*(2^e2)*(10^q) >= 10^(prec-1) - // Because mant >= 2^54, it is enough to choose: - // 2^(e2+54) >= 10^(-q+prec-1) - // or q = -mulLog10_2(e2+54) + prec - 1 - // - // The minimal required exponent is -mulLog10_2(1025)+18 = -291 - // The maximal required exponent is mulLog10_2(1074)+18 = 342 - q := -mulLog10_2(e2+54) + prec - 1 - - // Now compute mant*(2^e2)*(10^q). - // Is it an exact computation? - // Only small positive powers of 10 are exact (5^55 has 128 bits). - exact := q <= 55 && q >= 0 - - di, dexp2, d0 := mult128bitPow10(mant, e2, q) - if dexp2 >= 0 { - panic("not enough significant bits after mult128bitPow10") - } - // As a special case, computation might still be exact, if exponent - // was negative and if it amounts to computing an exact division. - // In that case, we ignore all lower bits. - // Note that division by 10^23 cannot be exact as 5^23 has 54 bits. - if q < 0 && q >= -22 && divisibleByPower5(mant, -q) { - exact = true - d0 = true - } - // Remove extra lower bits and keep rounding info. - extra := uint(-dexp2) - extraMask := uint64(1<>extra, di&extraMask - roundUp := false - if exact { - // If we computed an exact product, d + 1/2 - // should round to d+1 if 'd' is odd. - roundUp = dfrac > 1<<(extra-1) || - (dfrac == 1<<(extra-1) && !d0) || - (dfrac == 1<<(extra-1) && d0 && di&1 == 1) - } else { - // otherwise, d+1/2 always rounds up because - // we truncated below. - roundUp = dfrac>>(extra-1) == 1 - } - if dfrac != 0 { - d0 = false - } - // Proceed to the requested number of digits - formatDecimal(d, di, !d0, roundUp, prec) - // Adjust exponent - d.dp -= q -} - -var uint64pow10 = [...]uint64{ - 1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, - 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, -} - -// formatDecimal fills d with at most prec decimal digits -// of mantissa m. The boolean trunc indicates whether m -// is truncated compared to the original number being formatted. -func formatDecimal(d *decimalSlice, m uint64, trunc bool, roundUp bool, prec int) { - max := uint64pow10[prec] - trimmed := 0 - for m >= max { - a, b := m/10, m%10 - m = a - trimmed++ - if b > 5 { - roundUp = true - } else if b < 5 { - roundUp = false - } else { // b == 5 - // round up if there are trailing digits, - // or if the new value of m is odd (round-to-even convention) - roundUp = trunc || m&1 == 1 - } - if b != 0 { - trunc = true - } - } - if roundUp { - m++ - } - if m >= max { - // Happens if di was originally 99999....xx - m /= 10 - trimmed++ - } - // render digits - formatBase10(d.d[:prec], m) - d.nd = prec - for d.d[d.nd-1] == '0' { - d.nd-- - trimmed++ - } - d.dp = d.nd + trimmed -} // ryuFtoaShortest formats mant*2^exp with prec decimal digits. func ryuFtoaShortest(d *decimalSlice, mant uint64, exp int, flt *floatInfo) { @@ -249,13 +57,13 @@ func ryuFtoaShortest(d *decimalSlice, mant uint64, exp int, flt *floatInfo) { if q < 0 && q >= -24 { // Division by a power of ten may be exact. // (note that 5^25 is a 59-bit number so division by 5^25 is never exact). - if divisibleByPower5(ml, -q) { + if divisiblePow5(ml, -q) { dl0 = true } - if divisibleByPower5(mc, -q) { + if divisiblePow5(mc, -q) { dc0 = true } - if divisibleByPower5(mu, -q) { + if divisiblePow5(mu, -q) { du0 = true } } @@ -464,7 +272,7 @@ func mult64bitPow10(m uint32, e2, q int) (resM uint32, resE int, exact bool) { pow.Hi++ } hi, lo := bits.Mul64(uint64(m), pow.Hi) - e2 += exp2 - 63 + 57 + e2 += exp2 - 64 + 57 return uint32(hi<<7 | lo>>57), e2, lo<<7 == 0 } @@ -492,21 +300,8 @@ func mult128bitPow10(m uint64, e2, q int) (resM uint64, resE int, exact bool) { // Inverse powers of ten must be rounded up. pow.Lo++ } - e2 += exp2 - 127 + 119 + e2 += exp2 - 128 + 119 hi, mid, lo := umul192(m, pow) return hi<<9 | mid>>55, e2, mid<<9 == 0 && lo == 0 } - -func divisibleByPower5(m uint64, k int) bool { - if m == 0 { - return true - } - for i := 0; i < k; i++ { - if m%5 != 0 { - return false - } - m /= 5 - } - return true -} diff --git a/src/internal/strconv/import_test.go b/src/internal/strconv/import_test.go index 0cbc451651a015..3dab2bf9e56e2b 100644 --- a/src/internal/strconv/import_test.go +++ b/src/internal/strconv/import_test.go @@ -8,6 +8,11 @@ import . "internal/strconv" type uint128 = Uint128 +const ( + pow10Min = Pow10Min + pow10Max = Pow10Max +) + var ( mulLog10_2 = MulLog10_2 mulLog2_10 = MulLog2_10 @@ -15,4 +20,7 @@ var ( pow10 = Pow10 umul128 = Umul128 umul192 = Umul192 + div5Tab = Div5Tab + divisiblePow5 = DivisiblePow5 + trimZeros = TrimZeros ) diff --git a/src/internal/strconv/itoa.go b/src/internal/strconv/itoa.go index d06de4770f1052..2375e034f59786 100644 --- a/src/internal/strconv/itoa.go +++ b/src/internal/strconv/itoa.go @@ -174,6 +174,14 @@ func small(i int) string { return smalls[i*2 : i*2+2] } +// RuntimeFormatBase10 formats u into the tail of a +// and returns the offset to the first byte written to a. +// It is only for use by package runtime. +// Other packages should use AppendUint. +func RuntimeFormatBase10(a []byte, u uint64) int { + return formatBase10(a, u) +} + // formatBase10 formats the decimal representation of u into the tail of a // and returns the offset of the first byte written to a. That is, after // diff --git a/src/internal/strconv/math.go b/src/internal/strconv/math.go index f0f3d5fe540b23..3b884e846a6222 100644 --- a/src/internal/strconv/math.go +++ b/src/internal/strconv/math.go @@ -27,13 +27,14 @@ func umul192(x uint64, y uint128) (hi, mid, lo uint64) { return hi + carry, mid, lo } -// pow10 returns the 128-bit mantissa and binary exponent of 10**e +// pow10 returns the 128-bit mantissa and binary exponent of 10**e. +// That is, 10^e = mant/2^128 * 2**exp. // If e is out of range, pow10 returns ok=false. func pow10(e int) (mant uint128, exp int, ok bool) { if e < pow10Min || e > pow10Max { return } - return pow10Tab[e-pow10Min], mulLog2_10(e), true + return pow10Tab[e-pow10Min], 1 + mulLog2_10(e), true } // mulLog10_2 returns math.Floor(x * log(2)/log(10)) for an integer x in @@ -55,3 +56,124 @@ func mulLog2_10(x int) int { // log(10)/log(2) ≈ 3.32192809489 ≈ 108853 / 2^15 return (x * 108853) >> 15 } + +func bool2uint(b bool) uint { + if b { + return 1 + } + return 0 +} + +// Exact Division and Remainder Checking +// +// An exact division x/c (exact means x%c == 0) +// can be implemented by x*m where m is the multiplicative inverse of c (m*c == 1). +// +// Since c is also the multiplicative inverse of m, x*m is lossless, +// and all the exact multiples of c map to all of [0, maxUint64/c]. +// The non-multiples are forced to map to larger values. +// This also gives a quick test for whether x is an exact multiple of c: +// compute the exact division and check whether it's at most maxUint64/c: +// x%c == 0 => x*m <= maxUint64/c. +// +// Only odd c have multiplicative inverses mod powers of two. +// To do an exact divide x / (c<>s instead. +// And to check for remainder, we need to check that those low s +// bits are all zero before we shift them away. We can merge that +// with the <= for the exact odd remainder check by rotating the +// shifted bits into the high part instead: +// x%(c< bits.RotateLeft64(x*m, -s) <= maxUint64/c. +// +// The compiler does this transformation automatically in general, +// but we apply it here by hand in a few ways that the compiler can't help with. +// +// For a more detailed explanation, see +// Henry S. Warren, Jr., Hacker's Delight, 2nd ed., sections 10-16 and 10-17. + +// divisiblePow5 reports whether x is divisible by 5^p. +// It returns false for p not in [1, 22], +// because we only care about float64 mantissas, and 5^23 > 2^53. +func divisiblePow5(x uint64, p int) bool { + return 1 <= p && p <= 22 && x*div5Tab[p-1][0] <= div5Tab[p-1][1] +} + +const maxUint64 = 1<<64 - 1 + +// div5Tab[p-1] is the multiplicative inverse of 5^p and maxUint64/5^p. +var div5Tab = [22][2]uint64{ + {0xcccccccccccccccd, maxUint64 / 5}, + {0x8f5c28f5c28f5c29, maxUint64 / 5 / 5}, + {0x1cac083126e978d5, maxUint64 / 5 / 5 / 5}, + {0xd288ce703afb7e91, maxUint64 / 5 / 5 / 5 / 5}, + {0x5d4e8fb00bcbe61d, maxUint64 / 5 / 5 / 5 / 5 / 5}, + {0x790fb65668c26139, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5}, + {0xe5032477ae8d46a5, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0xc767074b22e90e21, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x8e47ce423a2e9c6d, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x4fa7f60d3ed61f49, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x0fee64690c913975, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x3662e0e1cf503eb1, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0xa47a2cf9f6433fbd, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x54186f653140a659, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x7738164770402145, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0xe4a4d1417cd9a041, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0xc75429d9e5c5200d, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0xc1773b91fac10669, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x26b172506559ce15, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0xd489e3a9addec2d1, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x90e860bb892c8d5d, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, + {0x502e79bf1b6f4f79, maxUint64 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5 / 5}, +} + +// trimZeros trims trailing zeros from x. +// It finds the largest p such that x % 10^p == 0 +// and then returns x / 10^p, p. +// +// This is here for reference and tested, because it is an optimization +// used by other ftoa algorithms, but in our implementations it has +// never been benchmarked to be faster than trimming zeros after +// formatting into decimal bytes. +func trimZeros(x uint64) (uint64, int) { + const ( + div1e8m = 0xc767074b22e90e21 + div1e8le = maxUint64 / 100000000 + + div1e4m = 0xd288ce703afb7e91 + div1e4le = maxUint64 / 10000 + + div1e2m = 0x8f5c28f5c28f5c29 + div1e2le = maxUint64 / 100 + + div1e1m = 0xcccccccccccccccd + div1e1le = maxUint64 / 10 + ) + + // _ = assert[x - y] asserts at compile time that x == y. + // Assert that the multiplicative inverses are correct + // by checking that (div1eNm * 5^N) % 1<<64 == 1. + var assert [1]struct{} + _ = assert[(div1e8m*5*5*5*5*5*5*5*5)%(1<<64)-1] + _ = assert[(div1e4m*5*5*5*5)%(1<<64)-1] + _ = assert[(div1e2m*5*5)%(1<<64)-1] + _ = assert[(div1e1m*5)%(1<<64)-1] + + // Cut 8 zeros, then 4, then 2, then 1. + p := 0 + for d := bits.RotateLeft64(x*div1e8m, -8); d <= div1e8le; d = bits.RotateLeft64(x*div1e8m, -8) { + x = d + p += 8 + } + if d := bits.RotateLeft64(x*div1e4m, -4); d <= div1e4le { + x = d + p += 4 + } + if d := bits.RotateLeft64(x*div1e2m, -2); d <= div1e2le { + x = d + p += 2 + } + if d := bits.RotateLeft64(x*div1e1m, -1); d <= div1e1le { + x = d + p += 1 + } + return x, p +} diff --git a/src/internal/strconv/math_test.go b/src/internal/strconv/math_test.go index d4f881b5e74d20..55e25f98cfee28 100644 --- a/src/internal/strconv/math_test.go +++ b/src/internal/strconv/math_test.go @@ -5,8 +5,8 @@ package strconv_test import ( - "math" . "internal/strconv" + "math" "testing" ) @@ -17,21 +17,36 @@ var pow10Tests = []struct { ok bool }{ {-349, uint128{0, 0}, 0, false}, - {-348, uint128{0xFA8FD5A0081C0288, 0x1732C869CD60E453}, -1157, true}, - {0, uint128{0x8000000000000000, 0x0000000000000000}, 0, true}, - {347, uint128{0xD13EB46469447567, 0x4B7195F2D2D1A9FB}, 1152, true}, + {-348, uint128{0xFA8FD5A0081C0288, 0x1732C869CD60E453}, -1156, true}, + {0, uint128{0x8000000000000000, 0x0000000000000000}, 1, true}, + {347, uint128{0xD13EB46469447567, 0x4B7195F2D2D1A9FB}, 1153, true}, {348, uint128{0, 0}, 0, false}, } func TestPow10(t *testing.T) { for _, tt := range pow10Tests { - mant, exp2, ok := Pow10(tt.exp10) + mant, exp2, ok := pow10(tt.exp10) if mant != tt.mant || exp2 != tt.exp2 { t.Errorf("pow10(%d) = %#016x, %#016x, %d, %v want %#016x,%#016x, %d, %v", tt.exp10, mant.Hi, mant.Lo, exp2, ok, tt.mant.Hi, tt.mant.Lo, tt.exp2, tt.ok) } } + + for p := pow10Min; p <= pow10Max; p++ { + mant, exp2, ok := pow10(p) + if !ok { + t.Errorf("pow10(%d) not ok", p) + continue + } + // Note: -64 instead of -128 because we only used mant.Hi, not all of mant. + have := math.Ldexp(float64(mant.Hi), exp2-64) + want := math.Pow(10, float64(p)) + if math.Abs(have-want)/want > 0.00001 { + t.Errorf("pow10(%d) = %#016x%016x/2^128 * 2^%d = %g want ~%g", p, mant.Hi, mant.Lo, exp2, have, want) + } + } + } func u128(hi, lo uint64) uint128 { @@ -78,3 +93,73 @@ func TestMulLog2_10(t *testing.T) { } } } + +func pow5(p int) uint64 { + x := uint64(1) + for range p { + x *= 5 + } + return x +} + +func TestDivisiblePow5(t *testing.T) { + for p := 1; p <= 22; p++ { + x := pow5(p) + if divisiblePow5(1, p) { + t.Errorf("divisiblePow5(1, %d) = true, want, false", p) + } + if divisiblePow5(x-1, p) { + t.Errorf("divisiblePow5(%d, %d) = true, want false", x-1, p) + } + if divisiblePow5(x+1, p) { + t.Errorf("divisiblePow5(%d, %d) = true, want false", x-1, p) + } + if divisiblePow5(x/5, p) { + t.Errorf("divisiblePow5(%d, %d) = true, want false", x/5, p) + } + if !divisiblePow5(0, p) { + t.Errorf("divisiblePow5(0, %d) = false, want true", p) + } + if !divisiblePow5(x, p) { + t.Errorf("divisiblePow5(%d, %d) = false, want true", x, p) + } + if 2*x > x && !divisiblePow5(2*x, p) { + t.Errorf("divisiblePow5(%d, %d) = false, want true", 2*x, p) + } + } +} + +func TestDiv5Tab(t *testing.T) { + for p := 1; p <= 22; p++ { + m := div5Tab[p-1][0] + le := div5Tab[p-1][1] + + // See comment in math.go on div5Tab. + // m needs to be multiplicative inverse of pow5(p). + if m*pow5(p) != 1 { + t.Errorf("pow5Tab[%d-1][0] = %#x, but %#x * (5**%d) = %d, want 1", p, m, m, p, m*pow5(p)) + } + + // le needs to be ⌊(1<<64 - 1) / 5^p⌋. + want := (1<<64 - 1) / pow5(p) + if le != want { + t.Errorf("pow5Tab[%d-1][1] = %#x, want %#x", p, le, want) + } + } +} + +func TestTrimZeros(t *testing.T) { + for _, x := range []uint64{1, 2, 3, 4, 101, 123} { + want := x + for p := range 20 { + haveX, haveP := trimZeros(x) + if haveX != want || haveP != p { + t.Errorf("trimZeros(%d) = %d, %d, want %d, %d", x, haveX, haveP, want, p) + } + if x >= (1<<64-1)/10 { + break + } + x *= 10 + } + } +} diff --git a/src/runtime/heapdump.go b/src/runtime/heapdump.go index 3671c65ab73151..3a8c374fc0f497 100644 --- a/src/runtime/heapdump.go +++ b/src/runtime/heapdump.go @@ -382,7 +382,6 @@ func dumpgoroutine(gp *g) { dumpint(uint64(uintptr(unsafe.Pointer(d)))) dumpint(uint64(uintptr(unsafe.Pointer(gp)))) dumpint(uint64(d.sp)) - dumpint(uint64(d.pc)) fn := *(**funcval)(unsafe.Pointer(&d.fn)) dumpint(uint64(uintptr(unsafe.Pointer(fn)))) if d.fn == nil { diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 175452fec9c08b..ded85feffa12cb 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -354,7 +354,6 @@ func deferproc(fn func()) { d.link = gp._defer gp._defer = d d.fn = fn - d.pc = sys.GetCallerPC() // We must not be preempted between calling GetCallerSP and // storing it to d.sp because GetCallerSP's result is a // uintptr stack pointer. @@ -458,7 +457,6 @@ func deferrangefunc() any { d := newdefer() d.link = gp._defer gp._defer = d - d.pc = sys.GetCallerPC() // We must not be preempted between calling GetCallerSP and // storing it to d.sp because GetCallerSP's result is a // uintptr stack pointer. @@ -518,7 +516,6 @@ func deferconvert(d0 *_defer) { } for d1 := d; ; d1 = d1.link { d1.sp = d0.sp - d1.pc = d0.pc if d1.link == nil { d1.link = tail break @@ -547,17 +544,14 @@ func deferprocStack(d *_defer) { d.heap = false d.rangefunc = false d.sp = sys.GetCallerSP() - d.pc = sys.GetCallerPC() // The lines below implement: - // d.panic = nil - // d.fd = nil // d.link = gp._defer // d.head = nil // gp._defer = d - // But without write barriers. The first three are writes to + // But without write barriers. The first two are writes to // the stack so they don't need a write barrier, and furthermore // are to uninitialized memory, so they must not use a write barrier. - // The fourth write does not require a write barrier because we + // The third write does not require a write barrier because we // explicitly mark all the defer structures, so we don't need to // keep track of pointers to them with a write barrier. *(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer)) @@ -977,8 +971,6 @@ func (p *_panic) nextDefer() (func(), bool) { fn := d.fn - p.retpc = d.pc - // Unlink and free. popDefer(gp) @@ -1018,6 +1010,12 @@ func (p *_panic) nextFrame() (ok bool) { // it's non-zero. if u.frame.sp == limit { + f := u.frame.fn + if f.deferreturn == 0 { + throw("no deferreturn") + } + p.retpc = f.entry() + uintptr(f.deferreturn) + break // found a frame with linked defers } @@ -1273,15 +1271,6 @@ func recovery(gp *g) { pc, sp, fp := p.retpc, uintptr(p.sp), uintptr(p.fp) p0, saveOpenDeferState := p, p.deferBitsPtr != nil && *p.deferBitsPtr != 0 - // The linker records the f-relative address of a call to deferreturn in f's funcInfo. - // Assuming a "normal" call to recover() inside one of f's deferred functions - // invoked for a panic, that is the desired PC for exiting f. - f := findfunc(pc) - if f.deferreturn == 0 { - throw("no deferreturn") - } - gotoPc := f.entry() + uintptr(f.deferreturn) - // Unwind the panic stack. for ; p != nil && uintptr(p.startSP) < sp; p = p.link { // Don't allow jumping past a pending Goexit. @@ -1304,7 +1293,7 @@ func recovery(gp *g) { // With how subtle defer handling is, this might not actually be // worthwhile though. if p.goexit { - gotoPc, sp = p.startPC, uintptr(p.startSP) + pc, sp = p.startPC, uintptr(p.startSP) saveOpenDeferState = false // goexit is unwinding the stack anyway break } @@ -1367,7 +1356,7 @@ func recovery(gp *g) { // branch directly to the deferreturn gp.sched.sp = sp - gp.sched.pc = gotoPc + gp.sched.pc = pc gp.sched.lr = 0 // Restore the bp on platforms that support frame pointers. // N.B. It's fine to not set anything for platforms that don't diff --git a/src/runtime/print.go b/src/runtime/print.go index e32ecb94503e63..c01db9d7f98689 100644 --- a/src/runtime/print.go +++ b/src/runtime/print.go @@ -140,13 +140,32 @@ func printcomplex64(c complex64) { } func printuint(v uint64) { + // Note: Avoiding strconv.AppendUint so that it's clearer + // that there are no allocations in this routine. + // cmd/link/internal/ld.TestAbstractOriginSanity + // sees the append and doesn't realize it doesn't allocate. var buf [20]byte - gwrite(strconv.AppendUint(buf[:0], v, 10)) + i := strconv.RuntimeFormatBase10(buf[:], v) + gwrite(buf[i:]) } func printint(v int64) { + // Note: Avoiding strconv.AppendUint so that it's clearer + // that there are no allocations in this routine. + // cmd/link/internal/ld.TestAbstractOriginSanity + // sees the append and doesn't realize it doesn't allocate. + neg := v < 0 + u := uint64(v) + if neg { + u = -u + } var buf [20]byte - gwrite(strconv.AppendInt(buf[:0], v, 10)) + i := strconv.RuntimeFormatBase10(buf[:], u) + if neg { + i-- + buf[i] = '-' + } + gwrite(buf[i:]) } var minhexdigits = 0 // protected by printlock diff --git a/src/runtime/proc.go b/src/runtime/proc.go index ef3a0b7a0e4c60..91740d1fa6d58f 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -1254,8 +1254,8 @@ func castogscanstatus(gp *g, oldval, newval uint32) bool { } } print("runtime: castogscanstatus oldval=", hex(oldval), " newval=", hex(newval), "\n") - throw("castogscanstatus") - panic("not reached") + throw("bad oldval passed to castogscanstatus") + return false } // casgstatusAlwaysTrack is a debug flag that causes casgstatus to always track diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index b346337d60354c..3672b19f76461a 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -1090,7 +1090,6 @@ type _defer struct { heap bool rangefunc bool // true for rangefunc list sp uintptr // sp at time of defer - pc uintptr // pc at time of defer fn func() // can be nil for open-coded defers link *_defer // next defer on G; can point to either heap or stack! diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index de62762ba76906..6649f72471629a 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -1314,7 +1314,16 @@ func tracebacksomeothers(me *g, showf func(*g) bool) { // from a signal handler initiated during a systemstack call. // The original G is still in the running state, and we want to // print its stack. - if gp.m != getg().m && readgstatus(gp)&^_Gscan == _Grunning { + // + // There's a small window of time in exitsyscall where a goroutine could be + // in _Grunning as it's exiting a syscall. This could be the case even if the + // world is stopped or frozen. + // + // This is OK because the goroutine will not exit the syscall while the world + // is stopped or frozen. This is also why it's safe to check syscallsp here, + // and safe to take the goroutine's stack trace. The syscall path mutates + // syscallsp only just before exiting the syscall. + if gp.m != getg().m && readgstatus(gp)&^_Gscan == _Grunning && gp.syscallsp == 0 { print("\tgoroutine running on other thread; stack unavailable\n") printcreatedby(gp) } else { diff --git a/src/strings/strings_test.go b/src/strings/strings_test.go index b10b5f05ccae53..edfeb0e8138b2f 100644 --- a/src/strings/strings_test.go +++ b/src/strings/strings_test.go @@ -694,7 +694,7 @@ func rot13(r rune) rune { func TestMap(t *testing.T) { // Run a couple of awful growth/shrinkage tests a := tenRunes('a') - // 1. Grow. This triggers two reallocations in Map. + // 1. Grow. This triggers two reallocations in Map. maxRune := func(rune) rune { return unicode.MaxRune } m := Map(maxRune, a) expect := tenRunes(unicode.MaxRune)