From 0a6ccc557a7205172a9db17acb76cb18428a441e Mon Sep 17 00:00:00 2001 From: Jorropo Date: Tue, 14 Jul 2026 07:32:30 +0200 Subject: [PATCH 01/38] cmd/compile: on amd64 optimize x + carry and x - carry into ADC and SBB This doesn't restore the round-about old behavior where we used to take an inverse of the inverse of the carry flag to move data from flags to GP. Instead it performs the addition or substraction directly between GP and flags which is faster by 1 cycle (TP & LAT & instructions) Fixes #80399 Change-Id: Id90d3089322ae78887ad9e839838402b70d61ae2 Reviewed-on: https://go-review.googlesource.com/c/go/+/800460 Reviewed-by: Keith Randall Reviewed-by: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Jorropo Reviewed-by: Russ Cox --- src/cmd/compile/internal/ssa/_gen/AMD64.rules | 5 ++- src/cmd/compile/internal/ssa/rewriteAMD64.go | 44 +++++++++++++++++++ test/codegen/mathbits.go | 13 ++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/ssa/_gen/AMD64.rules b/src/cmd/compile/internal/ssa/_gen/AMD64.rules index 36c1e3354d6f06..bb6732d57dfcf3 100644 --- a/src/cmd/compile/internal/ssa/_gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/_gen/AMD64.rules @@ -48,7 +48,10 @@ (ADCQconst x [c] (InvertFlags f)) => (ADCQconst x [c] (Select1 (NEGLflags (MOVBQZX (SETA f))))) (SBBQ x y (InvertFlags f)) => (SBBQ x y (Select1 (NEGLflags (MOVBQZX (SETA f))))) (SBBQconst x [c] (InvertFlags f)) => (SBBQconst x [c] (Select1 (NEGLflags (MOVBQZX (SETA f))))) - +// ADDQ/SUBQ an from a carry flag into ADCQ/SBBQ +// TODO: maybe add ADCL and SBBL ? +(ADDQ x (MOVBQZX (SETB flags))) => (Select0 (ADCQconst [0] x flags)) +(SUBQ x (MOVBQZX (SETB flags))) => (Select0 (SBBQconst [0] x flags)) (Mul64uhilo ...) => (MULQU2 ...) (Div128u ...) => (DIVQU2 ...) diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index e37459035b8b0d..303db4728f8c12 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -8143,6 +8143,30 @@ func rewriteValueAMD64_OpAMD64ADDLmodify(v *Value) bool { func rewriteValueAMD64_OpAMD64ADDQ(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (ADDQ x (MOVBQZX (SETB flags))) + // result: (Select0 (ADCQconst [0] x flags)) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + x := v_0 + if v_1.Op != OpAMD64MOVBQZX { + continue + } + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64SETB { + continue + } + flags := v_1_0.Args[0] + v.reset(OpSelect0) + v0 := b.NewValue0(v.Pos, OpAMD64ADCQconst, types.NewTuple(typ.UInt64, types.TypeFlags)) + v0.AuxInt = int32ToAuxInt(0) + v0.AddArg2(x, flags) + v.AddArg(v0) + return true + } + break + } // match: (ADDQ (SHRQconst [1] x) (SHRQconst [1] x)) // result: (ANDQconst [-2] x) for { @@ -40322,6 +40346,26 @@ func rewriteValueAMD64_OpAMD64SUBQ(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + typ := &b.Func.Config.Types + // match: (SUBQ x (MOVBQZX (SETB flags))) + // result: (Select0 (SBBQconst [0] x flags)) + for { + x := v_0 + if v_1.Op != OpAMD64MOVBQZX { + break + } + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64SETB { + break + } + flags := v_1_0.Args[0] + v.reset(OpSelect0) + v0 := b.NewValue0(v.Pos, OpAMD64SBBQconst, types.NewTuple(typ.UInt64, types.TypeFlags)) + v0.AuxInt = int32ToAuxInt(0) + v0.AddArg2(x, flags) + v.AddArg(v0) + return true + } // match: (SUBQ x (MOVQconst [c])) // cond: is32Bit(c) // result: (SUBQconst x [int32(c)]) diff --git a/test/codegen/mathbits.go b/test/codegen/mathbits.go index 60db94d6a93f2a..72b425d6429f2c 100644 --- a/test/codegen/mathbits.go +++ b/test/codegen/mathbits.go @@ -763,6 +763,19 @@ func Add64MPanicOnOverflowGT(a, b [2]uint64) [2]uint64 { return r } +func issue80399add(a, b [2]uint64, s uint64) uint64 { + _, c := bits.Add64(a[0], b[0], 0) + _, c2 := bits.Add64(a[1], b[1], c) + // amd64:-"SET" -"MOVBLZX" "ADCQ" + return s + c2 +} +func issue80399sub(a, b [2]uint64, s uint64) uint64 { + _, c := bits.Add64(a[0], b[0], 0) + _, c2 := bits.Add64(a[1], b[1], c) + // amd64:-"SET" -"MOVBLZX" "SBBQ" + return s - c2 +} + // Verify independent carry chain operations are scheduled efficiently // and do not cause unnecessary save/restore of the CA bit. // From bc4eb9a15fe851d1281788a009d76428995e0bb3 Mon Sep 17 00:00:00 2001 From: goto1134 <1134togo@gmail.com> Date: Tue, 14 Jul 2026 20:23:02 +0000 Subject: [PATCH 02/38] go/build/constraint: match "go1." only as a tag prefix in GoVersion GoVersion split each tag with strings.Cut(tag, "go1."), which finds the separator anywhere in the string. A tag that merely contained "go1.", such as "notgo1.21", was parsed as a go1.N constraint, so GoVersion reported a Go version for a tag that names no version. Use strings.CutPrefix so that only a leading "go1." is treated as a version tag prefix. Fixes #80406 Change-Id: I1aa71a571e15b0ad44200b02b67b619d594b62ad GitHub-Last-Rev: c9a7eb3b28e9e6a93c1eb00a8c60cacf1205a37d GitHub-Pull-Request: golang/go#80407 Reviewed-on: https://go-review.googlesource.com/c/go/+/800600 Reviewed-by: Russ Cox Reviewed-by: Dmitri Shuralyov Reviewed-by: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/go/build/constraint/vers.go | 2 +- src/go/build/constraint/vers_test.go | 2 ++ src/go/version/version_test.go | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/go/build/constraint/vers.go b/src/go/build/constraint/vers.go index 13544484d69f3f..34886a4a55412e 100644 --- a/src/go/build/constraint/vers.go +++ b/src/go/build/constraint/vers.go @@ -66,7 +66,7 @@ func minVersion(z Expr, sign int) int { if z.Tag == "go1" { return 0 } - _, v, ok := strings.Cut(z.Tag, "go1.") + v, ok := strings.CutPrefix(z.Tag, "go1.") if !ok { return -1 } diff --git a/src/go/build/constraint/vers_test.go b/src/go/build/constraint/vers_test.go index 044de7f8b104fd..be0ca1246ff049 100644 --- a/src/go/build/constraint/vers_test.go +++ b/src/go/build/constraint/vers_test.go @@ -23,6 +23,8 @@ var tests = []struct { {"//go:build !go1.60", -1}, {"//go:build linux && go1.50 || darwin && go1.60", 50}, {"//go:build linux && go1.50 || !(!darwin || !go1.60)", 50}, + {"//go:build notgo1.21", -1}, + {"//go:build ago1.20", -1}, } func TestGoVersion(t *testing.T) { diff --git a/src/go/version/version_test.go b/src/go/version/version_test.go index ad83a258614f6f..9e14914a9f027b 100644 --- a/src/go/version/version_test.go +++ b/src/go/version/version_test.go @@ -15,6 +15,7 @@ var compareTests = []testCase2[string, string, int]{ {"", "", 0}, {"x", "x", 0}, {"", "x", 0}, + {"notgo1.21", "go1.21", -1}, {"1", "1.1", 0}, {"go1", "go1.1", -1}, {"go1.5", "go1.6", -1}, @@ -47,6 +48,7 @@ func TestLang(t *testing.T) { test1(t, langTests, "Lang", Lang) } var langTests = []testCase1[string, string]{ {"bad", ""}, + {"notgo1.21", ""}, {"go1.2rc3", "go1.2"}, {"go1.2.3", "go1.2"}, {"go1.2", "go1.2"}, @@ -60,6 +62,7 @@ func TestIsValid(t *testing.T) { test1(t, isValidTests, "IsValid", IsValid) } var isValidTests = []testCase1[string, bool]{ {"", false}, {"1.2.3", false}, + {"notgo1.21", false}, {"go1.2rc3", true}, {"go1.2.3", true}, {"go1.999testmod", true}, From 607cfcea17a480ae7102734f870b1567cf9df4bb Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 2 Jun 2026 20:11:39 -0700 Subject: [PATCH 03/38] cmd/compile: prevent arm[64] noov InvertFlags miscompilation at min int arm64 rules folded a-x*y < 0 into a no-overflow comparison. This could end up being absorbed into InvertFlags via canonicalization. But absorbing InvertFlags into a noov comparison swaps < and >, which assumes a-b < 0 iff b-a > 0. That is false for the min int. Something similar happens for ARM. The noov ops were introduced in CL 233097 (and CL 236637 for 32-bit arm) specifically to make these flag-based comparisons honor wrapping. Unfortunately, the InvertFlags absorption reintroduces a wrapping bug they were meant to prevent, and it has been there since that original CL. The boolean (CSET) was added in CL 526276 for #62469 and tuned again in CL 526237, and is unsound for the same reason. The test added after #62469 only checked that compilation succeeded, not that it yielded the correct result. There's no great way to rescue this optimization on arm64. The easiest fix is to decline to fold the asymmetric comparisons in the first place. Every other noov source is a commutative CMN, which canonicalization never reverses. This omission doesn't hurt much; no generated code in std/cmd changes. With no CMP-based noov remaining, the (dangerous) noov InvertFlags absorption rules are dead. Remove them. On arm, the optimizations fired in a few functions. Again, though, the work and code and risk to rescue it outweighs the impact of the optimization. Removing it in this commit adds an instruction or two to: bytes.(*Buffer).WriteByte bufio.(*Reader).Read crypto/tls.(*Conn).Read go/types.(*Scope).Innermost runtime.(*mcentral).uncacheSpan sync.runtime_notifyListWait encoding/gob.(*Decoder).ignoreStruct Instead of adding a new test file, strengthen the #62469 test to check the results and to increase test coverage. Updates #62469 Change-Id: I108855c67831d550a580f4ddd40ed7a9705400db Reviewed-on: https://go-review.googlesource.com/c/go/+/786520 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Russ Cox Reviewed-by: Keith Randall Reviewed-by: Keith Randall Auto-Submit: Keith Randall --- src/cmd/compile/internal/ssa/_gen/ARM.rules | 40 - src/cmd/compile/internal/ssa/_gen/ARM64.rules | 21 +- src/cmd/compile/internal/ssa/rewriteARM.go | 1030 ++--------------- src/cmd/compile/internal/ssa/rewriteARM64.go | 470 +------- test/codegen/comparisons.go | 29 +- test/fixedbugs/issue62469.go | 73 +- 6 files changed, 217 insertions(+), 1446 deletions(-) diff --git a/src/cmd/compile/internal/ssa/_gen/ARM.rules b/src/cmd/compile/internal/ssa/_gen/ARM.rules index 6020992a7de2c6..6af6cb7b55d26e 100644 --- a/src/cmd/compile/internal/ssa/_gen/ARM.rules +++ b/src/cmd/compile/internal/ssa/_gen/ARM.rules @@ -689,10 +689,6 @@ (UGE (InvertFlags cmp) yes no) => (ULE cmp yes no) (EQ (InvertFlags cmp) yes no) => (EQ cmp yes no) (NE (InvertFlags cmp) yes no) => (NE cmp yes no) -(LTnoov (InvertFlags cmp) yes no) => (GTnoov cmp yes no) -(GEnoov (InvertFlags cmp) yes no) => (LEnoov cmp yes no) -(LEnoov (InvertFlags cmp) yes no) => (GEnoov cmp yes no) -(GTnoov (InvertFlags cmp) yes no) => (LTnoov cmp yes no) // absorb flag constants into boolean values (Equal (FlagConstant [fc])) => (MOVWconst [b2i32(fc.eq())]) @@ -1339,24 +1335,6 @@ (NE (CMPconst [0] l:(XORshiftLLreg x y z)) yes no) && l.Uses==1 => (NE (TEQshiftLLreg x y z) yes no) (NE (CMPconst [0] l:(XORshiftRLreg x y z)) yes no) && l.Uses==1 => (NE (TEQshiftRLreg x y z) yes no) (NE (CMPconst [0] l:(XORshiftRAreg x y z)) yes no) && l.Uses==1 => (NE (TEQshiftRAreg x y z) yes no) -(LT (CMPconst [0] l:(SUB x y)) yes no) && l.Uses==1 => (LTnoov (CMP x y) yes no) -(LT (CMPconst [0] l:(MULS x y a)) yes no) && l.Uses==1 => (LTnoov (CMP a (MUL x y)) yes no) -(LT (CMPconst [0] l:(SUBconst [c] x)) yes no) && l.Uses==1 => (LTnoov (CMPconst [c] x) yes no) -(LT (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) && l.Uses==1 => (LTnoov (CMPshiftLL x y [c]) yes no) -(LT (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) && l.Uses==1 => (LTnoov (CMPshiftRL x y [c]) yes no) -(LT (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) && l.Uses==1 => (LTnoov (CMPshiftRA x y [c]) yes no) -(LT (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) && l.Uses==1 => (LTnoov (CMPshiftLLreg x y z) yes no) -(LT (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) && l.Uses==1 => (LTnoov (CMPshiftRLreg x y z) yes no) -(LT (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) && l.Uses==1 => (LTnoov (CMPshiftRAreg x y z) yes no) -(LE (CMPconst [0] l:(SUB x y)) yes no) && l.Uses==1 => (LEnoov (CMP x y) yes no) -(LE (CMPconst [0] l:(MULS x y a)) yes no) && l.Uses==1 => (LEnoov (CMP a (MUL x y)) yes no) -(LE (CMPconst [0] l:(SUBconst [c] x)) yes no) && l.Uses==1 => (LEnoov (CMPconst [c] x) yes no) -(LE (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) && l.Uses==1 => (LEnoov (CMPshiftLL x y [c]) yes no) -(LE (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) && l.Uses==1 => (LEnoov (CMPshiftRL x y [c]) yes no) -(LE (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) && l.Uses==1 => (LEnoov (CMPshiftRA x y [c]) yes no) -(LE (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) && l.Uses==1 => (LEnoov (CMPshiftLLreg x y z) yes no) -(LE (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) && l.Uses==1 => (LEnoov (CMPshiftRLreg x y z) yes no) -(LE (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) && l.Uses==1 => (LEnoov (CMPshiftRAreg x y z) yes no) (LT (CMPconst [0] l:(ADD x y)) yes no) && l.Uses==1 => (LTnoov (CMN x y) yes no) (LT (CMPconst [0] l:(MULA x y a)) yes no) && l.Uses==1 => (LTnoov (CMN a (MUL x y)) yes no) (LT (CMPconst [0] l:(ADDconst [c] x)) yes no) && l.Uses==1 => (LTnoov (CMNconst [c] x) yes no) @@ -1407,24 +1385,6 @@ (LE (CMPconst [0] l:(XORshiftLLreg x y z)) yes no) && l.Uses==1 => (LEnoov (TEQshiftLLreg x y z) yes no) (LE (CMPconst [0] l:(XORshiftRLreg x y z)) yes no) && l.Uses==1 => (LEnoov (TEQshiftRLreg x y z) yes no) (LE (CMPconst [0] l:(XORshiftRAreg x y z)) yes no) && l.Uses==1 => (LEnoov (TEQshiftRAreg x y z) yes no) -(GT (CMPconst [0] l:(SUB x y)) yes no) && l.Uses==1 => (GTnoov (CMP x y) yes no) -(GT (CMPconst [0] l:(MULS x y a)) yes no) && l.Uses==1 => (GTnoov (CMP a (MUL x y)) yes no) -(GT (CMPconst [0] l:(SUBconst [c] x)) yes no) && l.Uses==1 => (GTnoov (CMPconst [c] x) yes no) -(GT (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) && l.Uses==1 => (GTnoov (CMPshiftLL x y [c]) yes no) -(GT (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) && l.Uses==1 => (GTnoov (CMPshiftRL x y [c]) yes no) -(GT (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) && l.Uses==1 => (GTnoov (CMPshiftRA x y [c]) yes no) -(GT (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) && l.Uses==1 => (GTnoov (CMPshiftLLreg x y z) yes no) -(GT (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) && l.Uses==1 => (GTnoov (CMPshiftRLreg x y z) yes no) -(GT (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) && l.Uses==1 => (GTnoov (CMPshiftRAreg x y z) yes no) -(GE (CMPconst [0] l:(SUB x y)) yes no) && l.Uses==1 => (GEnoov (CMP x y) yes no) -(GE (CMPconst [0] l:(MULS x y a)) yes no) && l.Uses==1 => (GEnoov (CMP a (MUL x y)) yes no) -(GE (CMPconst [0] l:(SUBconst [c] x)) yes no) && l.Uses==1 => (GEnoov (CMPconst [c] x) yes no) -(GE (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) && l.Uses==1 => (GEnoov (CMPshiftLL x y [c]) yes no) -(GE (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) && l.Uses==1 => (GEnoov (CMPshiftRL x y [c]) yes no) -(GE (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) && l.Uses==1 => (GEnoov (CMPshiftRA x y [c]) yes no) -(GE (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) && l.Uses==1 => (GEnoov (CMPshiftLLreg x y z) yes no) -(GE (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) && l.Uses==1 => (GEnoov (CMPshiftRLreg x y z) yes no) -(GE (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) && l.Uses==1 => (GEnoov (CMPshiftRAreg x y z) yes no) (GT (CMPconst [0] l:(ADD x y)) yes no) && l.Uses==1 => (GTnoov (CMN x y) yes no) (GT (CMPconst [0] l:(ADDconst [c] x)) yes no) && l.Uses==1 => (GTnoov (CMNconst [c] x) yes no) (GT (CMPconst [0] l:(ADDshiftLL x y [c])) yes no) && l.Uses==1 => (GTnoov (CMNshiftLL x y [c]) yes no) diff --git a/src/cmd/compile/internal/ssa/_gen/ARM64.rules b/src/cmd/compile/internal/ssa/_gen/ARM64.rules index 4c6c437f26b9fe..db65737ed390e7 100644 --- a/src/cmd/compile/internal/ssa/_gen/ARM64.rules +++ b/src/cmd/compile/internal/ssa/_gen/ARM64.rules @@ -590,9 +590,15 @@ ((Equal|NotEqual|LessThan|GreaterEqual) (CMPconst [0] z:(ADD x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMN x y)) ((Equal|NotEqual|LessThan|GreaterEqual) (CMPWconst [0] z:(ADD x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMNW x y)) ((Equal|NotEqual|LessThan|GreaterEqual) (CMPconst [0] z:(MADD a x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMN a (MUL x y))) -((Equal|NotEqual|LessThan|GreaterEqual) (CMPconst [0] z:(MSUB a x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMP a (MUL x y))) ((Equal|NotEqual|LessThan|GreaterEqual) (CMPWconst [0] z:(MADDW a x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMNW a (MULW x y))) -((Equal|NotEqual|LessThan|GreaterEqual) (CMPWconst [0] z:(MSUBW a x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMPW a (MULW x y))) + +// Skip LessThan/GreaterEqual in the following rewrites. +// MSUB[W] a x y is a-x*y, which folds to a CMP. +// That can end up being reversed into InvertFlags. +// But absorbing InvertFlags into a no-overflow comparison flips < and >, +// which is wrong when the difference is the minimum int. +((Equal|NotEqual) (CMPconst [0] z:(MSUB a x y))) && z.Uses == 1 => ((Equal|NotEqual) (CMP a (MUL x y))) +((Equal|NotEqual) (CMPWconst [0] z:(MSUBW a x y))) && z.Uses == 1 => ((Equal|NotEqual) (CMPW a (MULW x y))) ((CMPconst|CMNconst) [c] y) && c < 0 && c != -1<<63 => ((CMNconst|CMPconst) [-c] y) ((CMPWconst|CMNWconst) [c] y) && c < 0 && c != -1<<31 => ((CMNWconst|CMPWconst) [-c] y) @@ -609,9 +615,10 @@ ((ZW|NZW) sub:(SUBconst [c] y)) && sub.Uses == 1 => ((EQ|NE) (CMPWconst [int32(c)] y)) ((EQ|NE|LT|LE|GT|GE) (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => ((EQ|NE|LTnoov|LEnoov|GTnoov|GEnoov) (CMN a (MUL x y)) yes no) -((EQ|NE|LT|LE|GT|GE) (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => ((EQ|NE|LTnoov|LEnoov|GTnoov|GEnoov) (CMP a (MUL x y)) yes no) ((EQ|NE|LT|LE|GT|GE) (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => ((EQ|NE|LTnoov|LEnoov|GTnoov|GEnoov) (CMNW a (MULW x y)) yes no) -((EQ|NE|LT|LE|GT|GE) (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => ((EQ|NE|LTnoov|LEnoov|GTnoov|GEnoov) (CMPW a (MULW x y)) yes no) +// No ordering ops here; see comments above about MSUB[W]. +((EQ|NE) (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => ((EQ|NE) (CMP a (MUL x y)) yes no) +((EQ|NE) (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => ((EQ|NE) (CMPW a (MULW x y)) yes no) // Absorb bit-tests into block (Z (ANDconst [c] x) yes no) && oneBit(c) => (TBZ [int64(ntz64(c))] x yes no) @@ -1343,10 +1350,6 @@ (FGT (InvertFlags cmp) yes no) => (FLT cmp yes no) (FLE (InvertFlags cmp) yes no) => (FGE cmp yes no) (FGE (InvertFlags cmp) yes no) => (FLE cmp yes no) -(LTnoov (InvertFlags cmp) yes no) => (GTnoov cmp yes no) -(GEnoov (InvertFlags cmp) yes no) => (LEnoov cmp yes no) -(LEnoov (InvertFlags cmp) yes no) => (GEnoov cmp yes no) -(GTnoov (InvertFlags cmp) yes no) => (LTnoov cmp yes no) // absorb InvertFlags into conditional instructions (CSEL [cc] x y (InvertFlags cmp)) => (CSEL [arm64Invert(cc)] x y cmp) @@ -1385,8 +1388,6 @@ (LessEqualF (InvertFlags x)) => (GreaterEqualF x) (GreaterThanF (InvertFlags x)) => (LessThanF x) (GreaterEqualF (InvertFlags x)) => (LessEqualF x) -(LessThanNoov (InvertFlags x)) => (CSEL0 [OpARM64NotEqual] (GreaterEqualNoov x) x) -(GreaterEqualNoov (InvertFlags x)) => (CSINC [OpARM64NotEqual] (LessThanNoov x) (MOVDconst [0]) x) // Don't bother extending if we're not using the higher bits. (MOV(B|BU)reg x) && v.Type.Size() <= 1 => x diff --git a/src/cmd/compile/internal/ssa/rewriteARM.go b/src/cmd/compile/internal/ssa/rewriteARM.go index da7dc28f97b42e..55cbda3e9a6dd5 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM.go +++ b/src/cmd/compile/internal/ssa/rewriteARM.go @@ -17183,217 +17183,6 @@ func rewriteBlockARM(b *Block) bool { b.resetWithControl(BlockARMLE, cmp) return true } - // match: (GE (CMPconst [0] l:(SUB x y)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMP x y) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUB { - break - } - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(MULS x y a)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMMULS { - break - } - a := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARMMUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBconst [c] x)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPconst [c] x) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBconst { - break - } - c := auxIntToInt32(l.AuxInt) - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPconst, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg(x) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftLL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftRL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftRA x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRA { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRA, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftLLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftRLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRAreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } // match: (GE (CMPconst [0] l:(ADD x y)) yes no) // cond: l.Uses==1 // result: (GEnoov (CMN x y) yes no) @@ -17949,272 +17738,16 @@ func rewriteBlockARM(b *Block) bool { b.resetWithControl(BlockARMGEnoov, v0) return true } - // match: (GE (CMPconst [0] l:(XORshiftRLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (TEQshiftRLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMXORshiftRLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMTEQshiftRLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(XORshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (TEQshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMXORshiftRAreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMTEQshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - case BlockARMGEnoov: - // match: (GEnoov (FlagConstant [fc]) yes no) - // cond: fc.geNoov() - // result: (First yes no) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(fc.geNoov()) { - break - } - b.Reset(BlockFirst) - return true - } - // match: (GEnoov (FlagConstant [fc]) yes no) - // cond: !fc.geNoov() - // result: (First no yes) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(!fc.geNoov()) { - break - } - b.Reset(BlockFirst) - b.swapSuccessors() - return true - } - // match: (GEnoov (InvertFlags cmp) yes no) - // result: (LEnoov cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMLEnoov, cmp) - return true - } - case BlockARMGT: - // match: (GT (FlagConstant [fc]) yes no) - // cond: fc.gt() - // result: (First yes no) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(fc.gt()) { - break - } - b.Reset(BlockFirst) - return true - } - // match: (GT (FlagConstant [fc]) yes no) - // cond: !fc.gt() - // result: (First no yes) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(!fc.gt()) { - break - } - b.Reset(BlockFirst) - b.swapSuccessors() - return true - } - // match: (GT (InvertFlags cmp) yes no) - // result: (LT cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMLT, cmp) - return true - } - // match: (GT (CMPconst [0] l:(SUB x y)) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMP x y) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUB { - break - } - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(MULS x y a)) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMMULS { - break - } - a := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARMMUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBconst [c] x)) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPconst [c] x) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBconst { - break - } - c := auxIntToInt32(l.AuxInt) - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPconst, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg(x) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPshiftLL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPshiftRL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPshiftRA x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRA { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRA, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) + // match: (GE (CMPconst [0] l:(XORshiftRLreg x y z)) yes no) // cond: l.Uses==1 - // result: (GTnoov (CMPshiftLLreg x y z) yes no) + // result: (GEnoov (TEQshiftRLreg x y z) yes no) for b.Controls[0].Op == OpARMCMPconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { break } l := v_0.Args[0] - if l.Op != OpARMSUBshiftLLreg { + if l.Op != OpARMXORshiftRLreg { break } z := l.Args[2] @@ -18223,21 +17756,21 @@ func rewriteBlockARM(b *Block) bool { if !(l.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLLreg, types.TypeFlags) + v0 := b.NewValue0(v_0.Pos, OpARMTEQshiftRLreg, types.TypeFlags) v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGTnoov, v0) + b.resetWithControl(BlockARMGEnoov, v0) return true } - // match: (GT (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) + // match: (GE (CMPconst [0] l:(XORshiftRAreg x y z)) yes no) // cond: l.Uses==1 - // result: (GTnoov (CMPshiftRLreg x y z) yes no) + // result: (GEnoov (TEQshiftRAreg x y z) yes no) for b.Controls[0].Op == OpARMCMPconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { break } l := v_0.Args[0] - if l.Op != OpARMSUBshiftRLreg { + if l.Op != OpARMXORshiftRAreg { break } z := l.Args[2] @@ -18246,32 +17779,69 @@ func rewriteBlockARM(b *Block) bool { if !(l.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRLreg, types.TypeFlags) + v0 := b.NewValue0(v_0.Pos, OpARMTEQshiftRAreg, types.TypeFlags) v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGTnoov, v0) + b.resetWithControl(BlockARMGEnoov, v0) return true } - // match: (GT (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { + case BlockARMGEnoov: + // match: (GEnoov (FlagConstant [fc]) yes no) + // cond: fc.geNoov() + // result: (First yes no) + for b.Controls[0].Op == OpARMFlagConstant { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(fc.geNoov()) { break } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRAreg { + b.Reset(BlockFirst) + return true + } + // match: (GEnoov (FlagConstant [fc]) yes no) + // cond: !fc.geNoov() + // result: (First no yes) + for b.Controls[0].Op == OpARMFlagConstant { + v_0 := b.Controls[0] + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(!fc.geNoov()) { break } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { + b.Reset(BlockFirst) + b.swapSuccessors() + return true + } + case BlockARMGT: + // match: (GT (FlagConstant [fc]) yes no) + // cond: fc.gt() + // result: (First yes no) + for b.Controls[0].Op == OpARMFlagConstant { + v_0 := b.Controls[0] + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(fc.gt()) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGTnoov, v0) + b.Reset(BlockFirst) + return true + } + // match: (GT (FlagConstant [fc]) yes no) + // cond: !fc.gt() + // result: (First no yes) + for b.Controls[0].Op == OpARMFlagConstant { + v_0 := b.Controls[0] + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(!fc.gt()) { + break + } + b.Reset(BlockFirst) + b.swapSuccessors() + return true + } + // match: (GT (InvertFlags cmp) yes no) + // result: (LT cmp yes no) + for b.Controls[0].Op == OpARMInvertFlags { + v_0 := b.Controls[0] + cmp := v_0.Args[0] + b.resetWithControl(BlockARMLT, cmp) return true } // match: (GT (CMPconst [0] l:(ADD x y)) yes no) @@ -18901,14 +18471,6 @@ func rewriteBlockARM(b *Block) bool { b.swapSuccessors() return true } - // match: (GTnoov (InvertFlags cmp) yes no) - // result: (LTnoov cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMLTnoov, cmp) - return true - } case BlockIf: // match: (If (Equal cc) yes no) // result: (EQ cc yes no) @@ -19006,243 +18568,32 @@ func rewriteBlockARM(b *Block) bool { // result: (First yes no) for b.Controls[0].Op == OpARMFlagConstant { v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(fc.le()) { - break - } - b.Reset(BlockFirst) - return true - } - // match: (LE (FlagConstant [fc]) yes no) - // cond: !fc.le() - // result: (First no yes) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(!fc.le()) { - break - } - b.Reset(BlockFirst) - b.swapSuccessors() - return true - } - // match: (LE (InvertFlags cmp) yes no) - // result: (GE cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMGE, cmp) - return true - } - // match: (LE (CMPconst [0] l:(SUB x y)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMP x y) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUB { - break - } - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(MULS x y a)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMMULS { - break - } - a := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARMMUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBconst [c] x)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPconst [c] x) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBconst { - break - } - c := auxIntToInt32(l.AuxInt) - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPconst, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg(x) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftLL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftRL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftRA x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRA { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRA, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftLLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(fc.le()) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLEnoov, v0) + b.Reset(BlockFirst) return true } - // match: (LE (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftRLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { + // match: (LE (FlagConstant [fc]) yes no) + // cond: !fc.le() + // result: (First no yes) + for b.Controls[0].Op == OpARMFlagConstant { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(!fc.le()) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLEnoov, v0) + b.Reset(BlockFirst) + b.swapSuccessors() return true } - // match: (LE (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { + // match: (LE (InvertFlags cmp) yes no) + // result: (GE cmp yes no) + for b.Controls[0].Op == OpARMInvertFlags { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRAreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLEnoov, v0) + cmp := v_0.Args[0] + b.resetWithControl(BlockARMGE, cmp) return true } // match: (LE (CMPconst [0] l:(ADD x y)) yes no) @@ -19872,14 +19223,6 @@ func rewriteBlockARM(b *Block) bool { b.swapSuccessors() return true } - // match: (LEnoov (InvertFlags cmp) yes no) - // result: (GEnoov cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMGEnoov, cmp) - return true - } case BlockARMLT: // match: (LT (FlagConstant [fc]) yes no) // cond: fc.lt() @@ -19914,217 +19257,6 @@ func rewriteBlockARM(b *Block) bool { b.resetWithControl(BlockARMGT, cmp) return true } - // match: (LT (CMPconst [0] l:(SUB x y)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMP x y) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUB { - break - } - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(MULS x y a)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMMULS { - break - } - a := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARMMUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBconst [c] x)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPconst [c] x) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBconst { - break - } - c := auxIntToInt32(l.AuxInt) - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPconst, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg(x) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftLL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftRL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftRA x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRA { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRA, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftLLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftRLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRAreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } // match: (LT (CMPconst [0] l:(ADD x y)) yes no) // cond: l.Uses==1 // result: (LTnoov (CMN x y) yes no) @@ -20752,14 +19884,6 @@ func rewriteBlockARM(b *Block) bool { b.swapSuccessors() return true } - // match: (LTnoov (InvertFlags cmp) yes no) - // result: (GTnoov cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMGTnoov, cmp) - return true - } case BlockARMNE: // match: (NE (CMPconst [0] (Equal cc)) yes no) // result: (EQ cc yes no) diff --git a/src/cmd/compile/internal/ssa/rewriteARM64.go b/src/cmd/compile/internal/ssa/rewriteARM64.go index 26d94b9ebf83fd..149de06c0aba9b 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM64.go +++ b/src/cmd/compile/internal/ssa/rewriteARM64.go @@ -5778,15 +5778,15 @@ func rewriteValueARM64_OpARM64Equal(v *Value) bool { v.AddArg(v0) return true } - // match: (Equal (CMPconst [0] z:(MSUB a x y))) + // match: (Equal (CMPWconst [0] z:(MADDW a x y))) // cond: z.Uses == 1 - // result: (Equal (CMP a (MUL x y))) + // result: (Equal (CMNW a (MULW x y))) for { - if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { + if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MSUB { + if z.Op != OpARM64MADDW { break } y := z.Args[2] @@ -5796,22 +5796,22 @@ func rewriteValueARM64_OpARM64Equal(v *Value) bool { break } v.reset(OpARM64Equal) - v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) + v0 := b.NewValue0(v.Pos, OpARM64CMNW, types.TypeFlags) + v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) v.AddArg(v0) return true } - // match: (Equal (CMPWconst [0] z:(MADDW a x y))) + // match: (Equal (CMPconst [0] z:(MSUB a x y))) // cond: z.Uses == 1 - // result: (Equal (CMNW a (MULW x y))) + // result: (Equal (CMP a (MUL x y))) for { - if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { + if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MADDW { + if z.Op != OpARM64MSUB { break } y := z.Args[2] @@ -5821,8 +5821,8 @@ func rewriteValueARM64_OpARM64Equal(v *Value) bool { break } v.reset(OpARM64Equal) - v0 := b.NewValue0(v.Pos, OpARM64CMNW, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) + v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) + v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) v.AddArg(v0) @@ -7689,31 +7689,6 @@ func rewriteValueARM64_OpARM64GreaterEqual(v *Value) bool { v.AddArg(v0) return true } - // match: (GreaterEqual (CMPconst [0] z:(MSUB a x y))) - // cond: z.Uses == 1 - // result: (GreaterEqualNoov (CMP a (MUL x y))) - for { - if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v.reset(OpARM64GreaterEqualNoov) - v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - v.AddArg(v0) - return true - } // match: (GreaterEqual (CMPWconst [0] z:(MADDW a x y))) // cond: z.Uses == 1 // result: (GreaterEqualNoov (CMNW a (MULW x y))) @@ -7739,31 +7714,6 @@ func rewriteValueARM64_OpARM64GreaterEqual(v *Value) bool { v.AddArg(v0) return true } - // match: (GreaterEqual (CMPWconst [0] z:(MSUBW a x y))) - // cond: z.Uses == 1 - // result: (GreaterEqualNoov (CMPW a (MULW x y))) - for { - if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v.reset(OpARM64GreaterEqualNoov) - v0 := b.NewValue0(v.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - v.AddArg(v0) - return true - } // match: (GreaterEqual (FlagConstant [fc])) // result: (MOVDconst [b2i(fc.ge())]) for { @@ -7805,8 +7755,6 @@ func rewriteValueARM64_OpARM64GreaterEqualF(v *Value) bool { } func rewriteValueARM64_OpARM64GreaterEqualNoov(v *Value) bool { v_0 := v.Args[0] - b := v.Block - typ := &b.Func.Config.Types // match: (GreaterEqualNoov (FlagConstant [fc])) // result: (MOVDconst [b2i(fc.geNoov())]) for { @@ -7818,22 +7766,6 @@ func rewriteValueARM64_OpARM64GreaterEqualNoov(v *Value) bool { v.AuxInt = int64ToAuxInt(b2i(fc.geNoov())) return true } - // match: (GreaterEqualNoov (InvertFlags x)) - // result: (CSINC [OpARM64NotEqual] (LessThanNoov x) (MOVDconst [0]) x) - for { - if v_0.Op != OpARM64InvertFlags { - break - } - x := v_0.Args[0] - v.reset(OpARM64CSINC) - v.AuxInt = opToAuxInt(OpARM64NotEqual) - v0 := b.NewValue0(v.Pos, OpARM64LessThanNoov, typ.Bool) - v0.AddArg(x) - v1 := b.NewValue0(v.Pos, OpARM64MOVDconst, typ.UInt64) - v1.AuxInt = int64ToAuxInt(0) - v.AddArg3(v0, v1, x) - return true - } return false } func rewriteValueARM64_OpARM64GreaterEqualU(v *Value) bool { @@ -8436,31 +8368,6 @@ func rewriteValueARM64_OpARM64LessThan(v *Value) bool { v.AddArg(v0) return true } - // match: (LessThan (CMPconst [0] z:(MSUB a x y))) - // cond: z.Uses == 1 - // result: (LessThanNoov (CMP a (MUL x y))) - for { - if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v.reset(OpARM64LessThanNoov) - v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - v.AddArg(v0) - return true - } // match: (LessThan (CMPWconst [0] z:(MADDW a x y))) // cond: z.Uses == 1 // result: (LessThanNoov (CMNW a (MULW x y))) @@ -8486,31 +8393,6 @@ func rewriteValueARM64_OpARM64LessThan(v *Value) bool { v.AddArg(v0) return true } - // match: (LessThan (CMPWconst [0] z:(MSUBW a x y))) - // cond: z.Uses == 1 - // result: (LessThanNoov (CMPW a (MULW x y))) - for { - if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v.reset(OpARM64LessThanNoov) - v0 := b.NewValue0(v.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - v.AddArg(v0) - return true - } // match: (LessThan (FlagConstant [fc])) // result: (MOVDconst [b2i(fc.lt())]) for { @@ -8552,8 +8434,6 @@ func rewriteValueARM64_OpARM64LessThanF(v *Value) bool { } func rewriteValueARM64_OpARM64LessThanNoov(v *Value) bool { v_0 := v.Args[0] - b := v.Block - typ := &b.Func.Config.Types // match: (LessThanNoov (FlagConstant [fc])) // result: (MOVDconst [b2i(fc.ltNoov())]) for { @@ -8565,20 +8445,6 @@ func rewriteValueARM64_OpARM64LessThanNoov(v *Value) bool { v.AuxInt = int64ToAuxInt(b2i(fc.ltNoov())) return true } - // match: (LessThanNoov (InvertFlags x)) - // result: (CSEL0 [OpARM64NotEqual] (GreaterEqualNoov x) x) - for { - if v_0.Op != OpARM64InvertFlags { - break - } - x := v_0.Args[0] - v.reset(OpARM64CSEL0) - v.AuxInt = opToAuxInt(OpARM64NotEqual) - v0 := b.NewValue0(v.Pos, OpARM64GreaterEqualNoov, typ.Bool) - v0.AddArg(x) - v.AddArg2(v0, x) - return true - } return false } func rewriteValueARM64_OpARM64LessThanU(v *Value) bool { @@ -14843,15 +14709,15 @@ func rewriteValueARM64_OpARM64NotEqual(v *Value) bool { v.AddArg(v0) return true } - // match: (NotEqual (CMPconst [0] z:(MSUB a x y))) + // match: (NotEqual (CMPWconst [0] z:(MADDW a x y))) // cond: z.Uses == 1 - // result: (NotEqual (CMP a (MUL x y))) + // result: (NotEqual (CMNW a (MULW x y))) for { - if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { + if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MSUB { + if z.Op != OpARM64MADDW { break } y := z.Args[2] @@ -14861,22 +14727,22 @@ func rewriteValueARM64_OpARM64NotEqual(v *Value) bool { break } v.reset(OpARM64NotEqual) - v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) + v0 := b.NewValue0(v.Pos, OpARM64CMNW, types.TypeFlags) + v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) v.AddArg(v0) return true } - // match: (NotEqual (CMPWconst [0] z:(MADDW a x y))) + // match: (NotEqual (CMPconst [0] z:(MSUB a x y))) // cond: z.Uses == 1 - // result: (NotEqual (CMNW a (MULW x y))) + // result: (NotEqual (CMP a (MUL x y))) for { - if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { + if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MADDW { + if z.Op != OpARM64MSUB { break } y := z.Args[2] @@ -14886,8 +14752,8 @@ func rewriteValueARM64_OpARM64NotEqual(v *Value) bool { break } v.reset(OpARM64NotEqual) - v0 := b.NewValue0(v.Pos, OpARM64CMNW, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) + v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) + v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) v.AddArg(v0) @@ -24785,16 +24651,16 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64EQ, v0) return true } - // match: (EQ (CMPconst [0] z:(MSUB a x y)) yes no) + // match: (EQ (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 - // result: (EQ (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { + // result: (EQ (CMNW a (MULW x y)) yes no) + for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { + if auxIntToInt32(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MSUB { + if z.Op != OpARM64MADDW { break } y := z.Args[2] @@ -24803,23 +24669,23 @@ func rewriteBlockARM64(b *Block) bool { if !(z.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) + v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) + v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) b.resetWithControl(BlockARM64EQ, v0) return true } - // match: (EQ (CMPWconst [0] z:(MADDW a x y)) yes no) + // match: (EQ (CMPconst [0] z:(MSUB a x y)) yes no) // cond: z.Uses==1 - // result: (EQ (CMNW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { + // result: (EQ (CMP a (MUL x y)) yes no) + for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { + if auxIntToInt64(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MADDW { + if z.Op != OpARM64MSUB { break } y := z.Args[2] @@ -24828,8 +24694,8 @@ func rewriteBlockARM64(b *Block) bool { if !(z.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) + v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) + v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) b.resetWithControl(BlockARM64EQ, v0) @@ -25187,31 +25053,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GEnoov, v0) return true } - // match: (GE (CMPconst [0] z:(MSUB a x y)) yes no) - // cond: z.Uses==1 - // result: (GEnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { - v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GEnoov, v0) - return true - } // match: (GE (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 // result: (GEnoov (CMNW a (MULW x y)) yes no) @@ -25237,31 +25078,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GEnoov, v0) return true } - // match: (GE (CMPWconst [0] z:(MSUBW a x y)) yes no) - // cond: z.Uses==1 - // result: (GEnoov (CMPW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GEnoov, v0) - return true - } // match: (GE (CMPWconst [0] x) yes no) // result: (TBZ [31] x yes no) for b.Controls[0].Op == OpARM64CMPWconst { @@ -25345,14 +25161,6 @@ func rewriteBlockARM64(b *Block) bool { b.swapSuccessors() return true } - // match: (GEnoov (InvertFlags cmp) yes no) - // result: (LEnoov cmp yes no) - for b.Controls[0].Op == OpARM64InvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARM64LEnoov, cmp) - return true - } case BlockARM64GT: // match: (GT (CMPconst [0] z:(AND x y)) yes no) // cond: z.Uses == 1 @@ -25583,31 +25391,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GTnoov, v0) return true } - // match: (GT (CMPconst [0] z:(MSUB a x y)) yes no) - // cond: z.Uses==1 - // result: (GTnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { - v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GTnoov, v0) - return true - } // match: (GT (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 // result: (GTnoov (CMNW a (MULW x y)) yes no) @@ -25633,31 +25416,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GTnoov, v0) return true } - // match: (GT (CMPWconst [0] z:(MSUBW a x y)) yes no) - // cond: z.Uses==1 - // result: (GTnoov (CMPW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GTnoov, v0) - return true - } // match: (GT (FlagConstant [fc]) yes no) // cond: fc.gt() // result: (First yes no) @@ -25717,14 +25475,6 @@ func rewriteBlockARM64(b *Block) bool { b.swapSuccessors() return true } - // match: (GTnoov (InvertFlags cmp) yes no) - // result: (LTnoov cmp yes no) - for b.Controls[0].Op == OpARM64InvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARM64LTnoov, cmp) - return true - } case BlockIf: // match: (If (Equal cc) yes no) // result: (EQ cc yes no) @@ -26089,31 +25839,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LEnoov, v0) return true } - // match: (LE (CMPconst [0] z:(MSUB a x y)) yes no) - // cond: z.Uses==1 - // result: (LEnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { - v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LEnoov, v0) - return true - } // match: (LE (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 // result: (LEnoov (CMNW a (MULW x y)) yes no) @@ -26139,31 +25864,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LEnoov, v0) return true } - // match: (LE (CMPWconst [0] z:(MSUBW a x y)) yes no) - // cond: z.Uses==1 - // result: (LEnoov (CMPW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LEnoov, v0) - return true - } // match: (LE (FlagConstant [fc]) yes no) // cond: fc.le() // result: (First yes no) @@ -26223,14 +25923,6 @@ func rewriteBlockARM64(b *Block) bool { b.swapSuccessors() return true } - // match: (LEnoov (InvertFlags cmp) yes no) - // result: (GEnoov cmp yes no) - for b.Controls[0].Op == OpARM64InvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARM64GEnoov, cmp) - return true - } case BlockARM64LT: // match: (LT (CMPconst [0] z:(AND x y)) yes no) // cond: z.Uses == 1 @@ -26461,31 +26153,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LTnoov, v0) return true } - // match: (LT (CMPconst [0] z:(MSUB a x y)) yes no) - // cond: z.Uses==1 - // result: (LTnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { - v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LTnoov, v0) - return true - } // match: (LT (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 // result: (LTnoov (CMNW a (MULW x y)) yes no) @@ -26511,31 +26178,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LTnoov, v0) return true } - // match: (LT (CMPWconst [0] z:(MSUBW a x y)) yes no) - // cond: z.Uses==1 - // result: (LTnoov (CMPW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LTnoov, v0) - return true - } // match: (LT (CMPWconst [0] x) yes no) // result: (TBNZ [31] x yes no) for b.Controls[0].Op == OpARM64CMPWconst { @@ -26619,14 +26261,6 @@ func rewriteBlockARM64(b *Block) bool { b.swapSuccessors() return true } - // match: (LTnoov (InvertFlags cmp) yes no) - // result: (GTnoov cmp yes no) - for b.Controls[0].Op == OpARM64InvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARM64GTnoov, cmp) - return true - } case BlockARM64NE: // match: (NE (CMPconst [0] z:(AND x y)) yes no) // cond: z.Uses == 1 @@ -26919,16 +26553,16 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64NE, v0) return true } - // match: (NE (CMPconst [0] z:(MSUB a x y)) yes no) + // match: (NE (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 - // result: (NE (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { + // result: (NE (CMNW a (MULW x y)) yes no) + for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { + if auxIntToInt32(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MSUB { + if z.Op != OpARM64MADDW { break } y := z.Args[2] @@ -26937,23 +26571,23 @@ func rewriteBlockARM64(b *Block) bool { if !(z.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) + v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) + v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) b.resetWithControl(BlockARM64NE, v0) return true } - // match: (NE (CMPWconst [0] z:(MADDW a x y)) yes no) + // match: (NE (CMPconst [0] z:(MSUB a x y)) yes no) // cond: z.Uses==1 - // result: (NE (CMNW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { + // result: (NE (CMP a (MUL x y)) yes no) + for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { + if auxIntToInt64(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MADDW { + if z.Op != OpARM64MSUB { break } y := z.Args[2] @@ -26962,8 +26596,8 @@ func rewriteBlockARM64(b *Block) bool { if !(z.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) + v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) + v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) b.resetWithControl(BlockARM64NE, v0) diff --git a/test/codegen/comparisons.go b/test/codegen/comparisons.go index 4394fb5a1f8ab4..0c25946094ce5a 100644 --- a/test/codegen/comparisons.go +++ b/test/codegen/comparisons.go @@ -357,7 +357,7 @@ func CmpToZero_ex1(a int64, e int32) int { } // arm64:`SUB` `TBNZ` - // arm:`CMP|CMN` -`(ADD|SUB)` `(BMI|BPL)` + // arm:`SUB` -`(BMI|BPL)` if e-11 >= 0 { return 8 } @@ -425,22 +425,22 @@ func CmpToZero_ex3(a, b, c, d int64, e, f, g, h int32) int { // var - var*var func CmpToZero_ex4(a, b, c, d int64, e, f, g, h int32) int { - // arm64:`CMP` -`MSUB` `MUL` `BEQ` `(BMI|BPL)` + // arm64:`MSUB` if a-b*c > 0 { return 1 } - // arm64:`CMP` -`MSUB` `MUL` `(BMI|BPL)` + // arm64:`MSUB` if b-c*d >= 0 { return 2 } - // arm64:`CMPW` -`MSUBW` `MULW` `(BMI|BPL)` + // arm64:`MSUBW` if e-f*g < 0 { return 5 } - // arm64:`CMPW` -`MSUBW` `MULW` `(BMI|BPL)` + // arm64:`MSUBW` if f-g*h >= 0 { return 6 } @@ -453,7 +453,7 @@ func CmpToZero_ex5(e, f int32, u uint32) int { return 1 } - // arm:`CMP` -`SUB` `(BMI|BPL)` + // arm:`SUB` -`(BMI|BPL)` if f-int32(u>>2) >= 0 { return 2 } @@ -748,7 +748,7 @@ func cmpToCmnLessThan(a, b, c, d int) int { if a*b+c < 0 { c3 = 1 } - // arm64:`CMP` `CSET MI` -`CMN` + // arm64:`MSUB` `CSET LT` -`CMN` if a-b*c < 0 { c4 = 1 } @@ -817,7 +817,7 @@ func cmpToCmnGreaterThanEqual(a, b, c, d int) int { if a*b+c >= 0 { c3 = 1 } - // arm64:`CMP` `CSET PL` -`CMN` + // arm64:`MSUB` `CSET GE` -`CMN` if a-b*c >= 0 { c4 = 1 } @@ -865,19 +865,6 @@ func cmp7() { cmp6[string]("") // force instantiation } -type Point struct { - X, Y int -} - -// invertLessThanNoov checks (LessThanNoov (InvertFlags x)) is lowered as -// CMP, CSET, CSEL instruction sequence. InvertFlags are only generated under -// certain conditions, see canonLessThan, so if the code below does not -// generate an InvertFlags OP, this check may fail. -func invertLessThanNoov(p1, p2, p3 Point) bool { - // arm64:`CMP` `CSET` `CSEL` - return (p1.X-p3.X)*(p2.Y-p3.Y)-(p2.X-p3.X)*(p1.Y-p3.Y) < 0 -} - func cmpstring1(x, y string) int { // amd64:".*cmpstring" if x < y { diff --git a/test/fixedbugs/issue62469.go b/test/fixedbugs/issue62469.go index d850ccb289b38d..50a98306b32c2f 100644 --- a/test/fixedbugs/issue62469.go +++ b/test/fixedbugs/issue62469.go @@ -1,15 +1,80 @@ -// compile +// run // Copyright 2023 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 p +// Comparing a wrapped product difference against zero must respect the +// sign of the wrapped value, even when it wraps to MinInt. +package main + +import "fmt" + +// int64 (not int) so the cross product is 64-bit on every GOARCH. +type point struct{ x, y int64 } + +//go:noinline func sign(p1, p2, p3 point) bool { return (p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y) < 0 } -type point struct { - x, y int +type point32 struct{ x, y int32 } + +//go:noinline +func sign32(p1, p2, p3 point32) bool { + if (p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y) < 0 { + return true + } + return false +} + +const minInt64 = -1 << 63 + +//go:noinline +func msub64(x, y int64) bool { + if minInt64-x*y < 0 { + return true + } + return false +} + +const minInt32 = -1 << 31 + +//go:noinline +func msub32(x, y int32) bool { + if minInt32-x*y < 0 { + return true + } + return false +} + +const minUint32 = uint32(1 << 31) + +//go:noinline +func umod32(y uint32) bool { + if int32(minUint32%y) < 0 { + return true + } + return false +} + +func main() { + var bad bool + check := func(name string, got, want bool) { + if got != want { + fmt.Printf("%s = %v, want %v\n", name, got, want) + bad = true + } + } + + check("sign", sign(point{0, 2}, point{1 << 62, 0}, point{0, 0}), true) // wraps to MinInt64 + check("sign32", sign32(point32{0, 2}, point32{1 << 30, 0}, point32{0, 0}), true) // wraps to MinInt32 + check("msub64", msub64(0, 123), true) // minInt64 - 0 + check("msub32", msub32(0, 123), true) // minInt32 - 0 + check("umod32", umod32(0xffffffff), true) // minUint32 % big == minUint32 + + if bad { + panic("InvertFlags noov MinInt miscompilation") + } } From ed5e6156e2ad6d4e661e71d2382fa23a2ed4ed83 Mon Sep 17 00:00:00 2001 From: sjarso Date: Sat, 11 Jul 2026 04:25:48 +0300 Subject: [PATCH 04/38] cmd/compile: fix typo in arm64 ssa comment Change "a a destructive" to "a destructive" in simdV21Narrow2's introductory comment. Change-Id: I0d86e97f140b384ac871498e922c6b10c0545bc8 Reviewed-on: https://go-review.googlesource.com/c/go/+/799721 Reviewed-by: Keith Randall Reviewed-by: Russ Cox Auto-Submit: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Keith Randall --- src/cmd/compile/internal/arm64/ssa.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go index 44bf01dfa47db2..04fd0555bfb9a7 100644 --- a/src/cmd/compile/internal/arm64/ssa.go +++ b/src/cmd/compile/internal/arm64/ssa.go @@ -466,7 +466,7 @@ func simdV11Narrow(s *ssagen.State, v *ssa.Value, arrangement int16) *obj.Prog { return p } -// simdV21Narrow2 generates a a destructive (updating upper half only) narrow "2" instruction, +// simdV21Narrow2 generates a destructive (updating upper half only) narrow "2" instruction, // e.g. XTN2 V1.4S, V0.8H. The arrangement parameter specifies the source arrangement. func simdV21Narrow2(s *ssagen.State, v *ssa.Value, arrangement int16) *obj.Prog { p := s.Prog(v.Op.Asm()) From 77a63bd8114d54e618b1bbf5253461e03cb162f1 Mon Sep 17 00:00:00 2001 From: sjarso Date: Sat, 11 Jul 2026 04:25:24 +0300 Subject: [PATCH 05/38] cmd/compile: fix typographical errors in Midway comment Change "the the" to "the" and "The places" to "This places" in the Midway compiler pass introductory comment. Change-Id: I3a43d8f0583a2ff68e75dc298f6f988bdc791fdc Reviewed-on: https://go-review.googlesource.com/c/go/+/799740 Reviewed-by: Keith Randall Reviewed-by: Russ Cox Reviewed-by: Keith Randall Auto-Submit: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/cmd/compile/internal/midway/rewrite.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/midway/rewrite.go b/src/cmd/compile/internal/midway/rewrite.go index e80930bcd79346..9858d44f9cca09 100644 --- a/src/cmd/compile/internal/midway/rewrite.go +++ b/src/cmd/compile/internal/midway/rewrite.go @@ -15,7 +15,7 @@ import ( // "Midway" rewriting // -// Go attempts to provide a package similar to the the "Highway" library +// Go attempts to provide a package similar to the "Highway" library // for C++ (https://google.github.io/highway). The library package is "simd" // and defines vector types with unspecified widths that are bound to particular // machine dependent types as late as program execution. This is accomplished @@ -26,7 +26,7 @@ import ( // The rewriting takes place early in the compiler, after type checking but // before conversion to "unified" IR. To ensure that types are correctly set // on the modified version of the code, type checking information is reset and -// the type checking phase is re-run. The places some limits on the shape of +// the type checking phase is re-run. This places some limits on the shape of // the rewrites, but it also ensures that the rewritten code is well-formed. // // Rewritten code does not reference "archsimd" types directly, but instead From 26fd8f14e6be3f04c8642786a55ecf967bfa8bab Mon Sep 17 00:00:00 2001 From: Pierre Gimalac Date: Wed, 15 Jul 2026 10:39:58 +0000 Subject: [PATCH 06/38] cmd/link: resolve crt0_64.o the same way as the other AIX startup files. crt0_64.o is hardcoded to /lib/crt0_64.o, while its neighbors crtcxa.o and crtdbase.o were resolved via the external linker's --print-file-name=. This breaks cross builds using a compiler with a non-standard sysroot, or when using custom path for crt0_64.o through LIBPATH. Built Go on AIX, then set a custom LIBPATH and validated that that path was used for crt0_64.o. Change-Id: If94ebd1302eaefaed68bd8a67600e7ffa3edf714 Reviewed-on: https://go-review.googlesource.com/c/go/+/800880 Reviewed-by: Ian Lance Taylor LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Cherry Mui Reviewed-by: Russ Cox Auto-Submit: Ian Lance Taylor --- src/cmd/link/internal/ld/lib.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 9cbc12919f1d00..b7ff79c0958bab 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1874,7 +1874,6 @@ func (ctxt *Link) hostlink() { // We want to have C files after Go files to remove // trampolines csects made by ld. argv = append(argv, "-nostartfiles") - argv = append(argv, "/lib/crt0_64.o") extld := ctxt.extld() name, args := extld[0], extld[1:] @@ -1887,6 +1886,7 @@ func (ctxt *Link) hostlink() { } return strings.Trim(string(out), "\n") } + argv = append(argv, getPathFile("crt0_64.o")) // Since GCC version 11, the 64-bit version of GCC starting files // are now suffixed by "_64". Even under "-maix64" multilib directory // "crtcxa.o" is 32-bit. From 12a1a350325267a18e46b0ea2b158f00438c8fbb Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sun, 28 Jun 2026 03:11:55 +0200 Subject: [PATCH 07/38] cmd/compile: fix bug in convertIntWithBitsize Correctly sext or zext based on needs. I am not sure if the add codepath actually cares, this might be latent, however I am writing new code which is definitely failing. Change-Id: I6610b63c83d70c6b1be2f7c83cdf15ab5ea9d62d Reviewed-on: https://go-review.googlesource.com/c/go/+/795080 Reviewed-by: Keith Randall Reviewed-by: Russ Cox LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/prove.go | 38 +++++++++++++++------- src/cmd/compile/internal/ssa/prove_test.go | 16 +++++++++ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index 6f2143271115ff..a91eae73b5f0ee 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -259,18 +259,32 @@ func noLimitForBitsize(bitsize uint) limit { } func convertIntWithBitsize[Target uint64 | int64, Source uint64 | int64](x Source, bitsize uint) Target { - switch bitsize { - case 64: - return Target(x) - case 32: - return Target(int32(x)) - case 16: - return Target(int16(x)) - case 8: - return Target(int8(x)) - default: - panic("unreachable") - } + if Target(0)-1 < 0 { + // Signed target: sign-extend the low bitsize bits. + switch bitsize { + case 64: + return Target(int64(x)) + case 32: + return Target(int32(x)) + case 16: + return Target(int16(x)) + case 8: + return Target(int8(x)) + } + } else { + // Unsigned target: zero-extend the low bitsize bits. + switch bitsize { + case 64: + return Target(uint64(x)) + case 32: + return Target(uint32(x)) + case 16: + return Target(uint16(x)) + case 8: + return Target(uint8(x)) + } + } + panic("unreachable") } // unsignedFixedLeadingBits extracts the all the most significant fixed bits from the limit. diff --git a/src/cmd/compile/internal/ssa/prove_test.go b/src/cmd/compile/internal/ssa/prove_test.go index 42a60dcb40d11b..e9357a064b6db7 100644 --- a/src/cmd/compile/internal/ssa/prove_test.go +++ b/src/cmd/compile/internal/ssa/prove_test.go @@ -85,3 +85,19 @@ func TestLimitBitlenUnsigned(t *testing.T) { func TestLimitPopcountUnsigned(t *testing.T) { testLimitUnaryOpUnsigned8(t, "popcount", limit{-128, 127, 0, 8}, limit.popcount, func(x uint8) uint8 { return uint8(bits.OnesCount8(x)) }) } + +func TestConvertIntWithBitsize(t *testing.T) { + if got := convertIntWithBitsize[int64, uint64](255, 8); got != -1 { + t.Errorf("convertIntWithBitsize(255, 8) = %d; want -1", got) + } + if got := convertIntWithBitsize[uint64, int64](-1, 8); got != 255 { + t.Errorf("convertIntWithBitsize(-1, 8) = %d; want 255", got) + } + + if got := convertIntWithBitsize[int64, uint64](127, 8); got != 127 { + t.Errorf("convertIntWithBitsize(127, 8) = %d; want 127", got) + } + if got := convertIntWithBitsize[uint64, int64](127, 8); got != 127 { + t.Errorf("convertIntWithBitsize(127, 8) = %d; want 127", got) + } +} From ff884052f003b44d50ef0df16f4ba1dbcaf157a6 Mon Sep 17 00:00:00 2001 From: Simon Law Date: Sat, 11 Jul 2026 01:10:47 -0700 Subject: [PATCH 08/38] net/url: add MustParse url.MustParse is to url.Parse as uuid.MustParse is to uuid.Parse. This function replaces the one-liners that programmers have been writing to initialize variables with valid URL string constants. Fixes #79946 Change-Id: I2f94c89e10d36d83da713579ca6659b5237604ca Reviewed-on: https://go-review.googlesource.com/c/go/+/799820 Reviewed-by: Sean Liao Reviewed-by: Russ Cox Reviewed-by: Damien Neil LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Damien Neil --- api/next/79946.txt | 1 + doc/next/6-stdlib/99-minor/net/url/79946.md | 5 ++ src/net/url/url.go | 10 ++++ src/net/url/url_test.go | 54 +++++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 api/next/79946.txt create mode 100644 doc/next/6-stdlib/99-minor/net/url/79946.md diff --git a/api/next/79946.txt b/api/next/79946.txt new file mode 100644 index 00000000000000..af588b25c02c2a --- /dev/null +++ b/api/next/79946.txt @@ -0,0 +1 @@ +pkg net/url, func MustParse(string) *URL #79946 diff --git a/doc/next/6-stdlib/99-minor/net/url/79946.md b/doc/next/6-stdlib/99-minor/net/url/79946.md new file mode 100644 index 00000000000000..5797ee261e2dce --- /dev/null +++ b/doc/next/6-stdlib/99-minor/net/url/79946.md @@ -0,0 +1,5 @@ + +The new [MustParse] function returns the parsed [URL] +or panics on error. +It can simplify some common uses of [Parse]. +For instance, initializing variables with valid URL string constants. diff --git a/src/net/url/url.go b/src/net/url/url.go index 77c2dd2b5ae93f..82bd9bd991b66c 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -411,6 +411,16 @@ func Parse(rawURL string) (*URL, error) { return url, nil } +// MustParse calls [Parse](rawURL) and panics on error. +// It is intended for use with hard-coded strings representing valid urls. +func MustParse(rawURL string) *URL { + url, err := Parse(rawURL) + if err != nil { + panic(err) + } + return url +} + // ParseRequestURI parses a raw url into a [URL] structure. It assumes that // url was received in an HTTP request, so the url is interpreted // only as an absolute URI or an absolute path. diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index dd093e6942357d..e1b84749774e90 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -715,6 +715,60 @@ func TestParse(t *testing.T) { if !reflect.DeepEqual(u, tt.out) { t.Errorf("Parse(%q):\n\tgot %v\n\twant %v\n", tt.in, ufmt(u), ufmt(tt.out)) } + u = MustParse(tt.in) + if !reflect.DeepEqual(u, tt.out) { + t.Errorf("MustParse(%q):\n\tgot %v\n\twant %v\n", tt.in, ufmt(u), ufmt(tt.out)) + } + } +} + +func TestInvalidParse(t *testing.T) { + cases := []struct { + name string + in string + }{ + { + name: "null-character", + in: "http://example.com/\x00", + }, + { + name: "control-character", + in: "http://example.com/\t", + }, + { + name: "invalid-escape", + in: "http://example.com/%zz", + }, + { + name: "invalid-port", + in: "http://example.com:port", + }, + { + name: "malformed-ipv6", + in: "http://[::1/", + }, + { + name: "ambiguous", + in: ":", + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + defer func() { + err := recover() + if err == nil { + t.Fatalf("MustParse(%q): should panic", tt.in) + } + if e, ok := err.(*Error); !ok || e.Op != "parse" { + t.Fatalf("MustParse(%q): unexpected panic: %#v", tt.in, err) + } + }() + if _, err := Parse(tt.in); err == nil { + t.Errorf("Parse(%q): should error", tt.in) + } + _ = MustParse(tt.in) + }) } } From 9fb7b20135165dca0a106b4bcf9b599a2d19ed3b Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Mon, 13 Jul 2026 16:16:14 -0400 Subject: [PATCH 09/38] cmd/go: do not pass -mthreads to C compiler on Windows Clang from MSVC does not support this flag. Instead, just pass -pthread like other platforms, which seems to work just fine with both MSVC clang and MinGW clang. Fixes #80290. Change-Id: If94a4b06d98b9a2778ea22ad74c29bb506653590 Reviewed-on: https://go-review.googlesource.com/c/go/+/800300 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Russ Cox Reviewed-by: Quim Muntal --- src/cmd/go/internal/work/exec.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 883177058b0164..72743e9c54a9be 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -2614,12 +2614,7 @@ func (b *Builder) compilerCmd(compiler []string, incdir, workdir string) []strin // gcc-4.5 and beyond require explicit "-pthread" flag // for multithreading with pthread library. if cfg.BuildContext.CgoEnabled { - switch cfg.Goos { - case "windows": - a = append(a, "-mthreads") - default: - a = append(a, "-pthread") - } + a = append(a, "-pthread") } if cfg.Goos == "aix" { From aa5fc60a63931413fa228c8caab8575ef25de321 Mon Sep 17 00:00:00 2001 From: Andrew Gaul Date: Wed, 24 Jun 2026 06:19:45 +0000 Subject: [PATCH 10/38] cmd/compile/internal/arm64: fuse adjacent spill/reload STR/LDR into STP/LDP The SSA pair pass (cmd/compile/internal/ssa/pair.go) runs before regalloc and only sees source-level loads and stores. Spill/reload code that regalloc inserts later for OpStoreReg/OpLoadReg becomes individual STR/LDR instructions that never get a chance to be paired, even when two spills target adjacent 8-byte stack slots. Fuse those pairs as the final step of code generation, in the compiler rather than the assembler. A new ssagen.ArchInfo.SSAGenFinish hook runs after genssa has emitted all of a function's Progs, resolved its branch and jump-table targets, and finalized the frame size in defframe (so the register-argument spills defframe inserts participate too); on arm64 it walks the Prog list and rewrites strictly-adjacent AMOVD spill pairs that share a base register and have consecutive 8-byte offsets into a single ASTP/ALDP. The second Prog is reduced to a 0-byte ANOP rather than unlinked so that branch targets referencing it remain valid. The pass is skipped under -N to keep unoptimized builds unoptimized. Doing this in the compiler keeps the assembler a simple translator. Fusion is gated on several safety and profitability conditions: - same base register and same Addr.Name (AUTO or PARAM, the only classes spill slots use), distinct destination registers (LDP with Rt1 == Rt2 is CONSTRAINED UNPREDICTABLE), and no pre/post-index or register-offset addressing - the resolved offset must encode in LDP/STP's signed 7-bit scaled immediate, [-512, 504]: the assembler rewrites an AUTO offset to off+framesize+8 and a PARAM offset to off+framesize+24 or +32 depending on frame alignment (off+8 in a frameless leaf, which is not decided until assembly, so a frameless PARAM must fit both). Checking the resolved value against the final frame size both admits deep spill slots in large frames, where spills are most common, and refuses fusions that would need an assembler-synthesized address (ADD + LDP), which is no smaller than the original pair and serializes through REGTMP - the first load of a pair must not write the base register: executed sequentially, the second load computes its address from the just-loaded value, while LDP computes both addresses from the original base - the second instruction is not a branch or jump-table target (otherwise paths that jump directly to it would skip the work the LDP/STP now does at the first instruction's position) and does not carry a statement boundary: genssa promotes instructions it reuses as inline marks to statements, and inline marks must never become zero-sized, while plain statement boundaries must keep their line table entries The prologue's register-argument spills around morestack, which the assembler inserts during preprocess, are already emitted as STP/LDP pairs (CL 621556). TestPairSpills in cmd/compile/internal/arm64 drives pairSpills directly with hand-constructed Prog chains, asserting the fused operands and covering each fusion path and each gating condition. test/codegen/memcombine.go pins down the spill/reload pattern that the SSA pair pass misses but this pass catches. test/fixedbugs/spillreload_arm64_pair.go exercises the conditional-call- with-adjacent-reloads pattern from runtime.schedule that miscompiled before the branch-target check was added. BenchmarkSpillReloadPair in cmd/compile/internal/test improves from about 1.92 to 1.78 ns/op on an Apple M4 Max (~7%). armlint reports that adjacent STR/LDR pairings drop from 4354 -> 235 on gofmt (94.6% reduction) and 26022 -> 772 on cmd/go (97.0% reduction). The text section shrinks by 16720 bytes (1.40%) on gofmt and 101312 bytes (1.52%) on cmd/go. Change-Id: Ib16ce06efdb733af71a5909ba15fedc47061a656 GitHub-Last-Rev: b7405f78357e0272d5cfccc373cba55b7dde2493 GitHub-Pull-Request: golang/go#79689 Reviewed-on: https://go-review.googlesource.com/c/go/+/783660 Reviewed-by: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Keith Randall Reviewed-by: Russ Cox --- src/cmd/compile/internal/arm64/galign.go | 1 + src/cmd/compile/internal/arm64/pair.go | 194 +++++++++ src/cmd/compile/internal/arm64/pair_test.go | 419 ++++++++++++++++++++ src/cmd/compile/internal/ssagen/arch.go | 7 + src/cmd/compile/internal/ssagen/ssa.go | 14 +- src/cmd/compile/internal/test/bench_test.go | 31 ++ test/codegen/memcombine.go | 25 ++ test/fixedbugs/spillreload_arm64_pair.go | 106 +++++ 8 files changed, 795 insertions(+), 2 deletions(-) create mode 100644 src/cmd/compile/internal/arm64/pair.go create mode 100644 src/cmd/compile/internal/arm64/pair_test.go create mode 100644 test/fixedbugs/spillreload_arm64_pair.go diff --git a/src/cmd/compile/internal/arm64/galign.go b/src/cmd/compile/internal/arm64/galign.go index 3ebd860de8f887..3b0aeddb9599b3 100644 --- a/src/cmd/compile/internal/arm64/galign.go +++ b/src/cmd/compile/internal/arm64/galign.go @@ -24,4 +24,5 @@ func Init(arch *ssagen.ArchInfo) { arch.SSAGenBlock = ssaGenBlock arch.LoadRegResult = loadRegResult arch.SpillArgReg = spillArgReg + arch.SSAGenFinish = ssaGenFinish } diff --git a/src/cmd/compile/internal/arm64/pair.go b/src/cmd/compile/internal/arm64/pair.go new file mode 100644 index 00000000000000..5a9dec40ab7cc2 --- /dev/null +++ b/src/cmd/compile/internal/arm64/pair.go @@ -0,0 +1,194 @@ +// Copyright 2026 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 arm64 + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/objw" + "cmd/internal/obj" + "cmd/internal/obj/arm64" + "cmd/internal/src" +) + +// movdImmMemInfo describes a plain "MOVD reg, off(base)" or "MOVD off(base), reg" +// instruction. base, off, name, and sym record the memory operand's base +// register, offset, addressing class (NAME_AUTO or NAME_PARAM), and symbol; +// reg is the register stored or loaded, and isLoad distinguishes the two +// forms. +type movdImmMemInfo struct { + base, reg int16 + off int64 + name obj.AddrName + sym *obj.LSym + isLoad bool +} + +// movdImmMem decodes p into a movdImmMemInfo. ok reports whether p is a +// plain MOVD load or store using an immediate-offset addressing form +// suitable for LDP/STP coalescing. +func movdImmMem(p *obj.Prog) (info movdImmMemInfo, ok bool) { + if p.As != arm64.AMOVD || p.Scond != 0 || p.Reg != 0 { + return movdImmMemInfo{}, false + } + var a *obj.Addr + switch { + case p.From.Type == obj.TYPE_MEM && p.To.Type == obj.TYPE_REG: + // Plain load: MOVD mem, reg. + a = &p.From + info.reg = p.To.Reg + info.isLoad = true + case p.From.Type == obj.TYPE_REG && p.To.Type == obj.TYPE_MEM: + // Plain store: MOVD reg, mem. + a = &p.To + info.reg = p.From.Reg + default: + return movdImmMemInfo{}, false + } + if a.Index != 0 || (a.Name != obj.NAME_AUTO && a.Name != obj.NAME_PARAM) { + return movdImmMemInfo{}, false + } + info.base, info.off, info.name, info.sym = a.Reg, a.Offset, a.Name, a.Sym + return info, true +} + +// ssaGenFinish runs after genssa has emitted all of a function's Progs, +// resolved its branch and jump-table targets, and finalized the frame size +// in defframe. +func ssaGenFinish(pp *objw.Progs) { + if base.Flag.N != 0 { + // Keep unoptimized builds unoptimized. + return + } + pairSpills(pp.Text, pp.Text.To.Offset, pp.CurFunc.LSym.Func().JumpTables) +} + +// pairSpills fuses strictly-adjacent MOVD load or store pairs that target the +// same base register at consecutive 8-byte offsets into a single LDP or STP. +// text is the function's first Prog, framesize its final frame size, and +// jumpTables its resolved jump tables. +// +// This recovers spill/reload pairings that the SSA-level pair pass +// (cmd/compile/internal/ssa/pair.go) cannot perform: that pass runs before +// regalloc and only sees source-level loads/stores, so the STR/LDR pairs that +// regalloc later inserts for OpStoreReg/OpLoadReg never get a chance to be +// paired. Doing the fusion here, as the last step of code generation, keeps the +// optimization in the compiler so the assembler can stay a simple translator. +func pairSpills(text *obj.Prog, framesize int64, jumpTables []obj.JumpTable) { + // Branch and jump-table targets, collected lazily once a candidate pair + // survives the cheaper checks; most functions have no candidates. + // Fusing into the first of a pair whose second instruction is a target + // would silently change control flow: paths that jump directly to the + // second instruction would skip the work the LDP/STP now does at the + // first instruction's position. + var isTarget map[*obj.Prog]bool + targets := func() map[*obj.Prog]bool { + if isTarget == nil { + isTarget = map[*obj.Prog]bool{} + for p := text; p != nil; p = p.Link { + if t := p.To.Target(); t != nil { + isTarget[t] = true + } + } + for _, jt := range jumpTables { + for _, t := range jt.Targets { + isTarget[t] = true + } + } + } + return isTarget + } + for p := text; p != nil; p = p.Link { + q := p.Link + if q == nil { + continue + } + a, ok := movdImmMem(p) + if !ok { + continue + } + b, ok := movdImmMem(q) + if !ok || a.isLoad != b.isLoad { + continue + } + if a.base != b.base || a.name != b.name { + continue + } + if a.reg == b.reg { + // LDP with Rt1 == Rt2 is CONSTRAINED UNPREDICTABLE. (STP with + // identical source registers would be fine, but spills never + // store the same register twice, so don't bother.) + continue + } + if a.isLoad && a.reg == a.base { + // The first load overwrites the base register: executed + // sequentially, the second load computes its address from the + // just-loaded value, while LDP computes both addresses from + // the original base. (The second load overwriting the base is + // fine; no address depends on it.) + continue + } + if q.Pos.IsStmt() == src.PosIsStmt { + // q carries a statement boundary, and may also be registered + // as an inline mark: genssa promotes instructions it reuses + // as inline marks to statements. Reducing q to a 0-byte ANOP + // would drop the statement from the line tables and violate + // the invariant that inline marks are never zero-sized (they + // are identified by PC). + continue + } + lo, hi := a, b + if lo.off > hi.off { + lo, hi = hi, lo + } + if hi.off != lo.off+8 { + continue + } + // Fuse only when the result will encode as a single LDP/STP, whose + // signed 7-bit immediate scaled by 8 covers offsets [-512, 504]. + // The assembler resolves a NAME_AUTO offset to off+framesize+8 + // (the 8 is the saved LR) and a NAME_PARAM offset to + // off+framesize+24 or +32 depending on frame alignment padding — + // or off+8 in a frameless leaf, which is not decided until + // assembly, so a frameless PARAM must fit both. An out-of-range + // LDP/STP needs an assembler-synthesized address (ADD + LDP), + // which is no smaller than the original pair and serializes the + // accesses through REGTMP. + var biasLo, biasHi int64 + switch { + case lo.name == obj.NAME_AUTO: + // Autos exist only in functions with a frame, and their offsets + // are negative from FP, so framesize+8 rebases them onto the + // non-negative SP-relative range the LDP/STP immediate must reach. + biasLo = framesize + 8 + biasHi = biasLo + case framesize == 0: + biasLo, biasHi = 8, 24 + case framesize%16 == 0: + biasLo = framesize + 24 + biasHi = biasLo + default: + biasLo = framesize + 32 + biasHi = biasLo + } + if lo.off%8 != 0 || lo.off+biasLo < -512 || lo.off+biasHi > 504 { + continue + } + if targets()[q] { + continue + } + mem := obj.Addr{Type: obj.TYPE_MEM, Reg: lo.base, Offset: lo.off, Name: lo.name, Sym: lo.sym} + regs := obj.Addr{Type: obj.TYPE_REGREG, Reg: lo.reg, Offset: int64(hi.reg)} + if a.isLoad { + p.As = arm64.ALDP + p.From, p.To = mem, regs + } else { + p.As = arm64.ASTP + p.From, p.To = regs, mem + } + // Reduce q to a 0-byte ANOP rather than unlinking it so that + // branch targets referencing q remain valid. + obj.Nopout(q) + } +} diff --git a/src/cmd/compile/internal/arm64/pair_test.go b/src/cmd/compile/internal/arm64/pair_test.go new file mode 100644 index 00000000000000..1da7009a38491e --- /dev/null +++ b/src/cmd/compile/internal/arm64/pair_test.go @@ -0,0 +1,419 @@ +// Copyright 2026 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 arm64 + +import ( + "cmd/internal/obj" + "cmd/internal/obj/arm64" + "cmd/internal/src" + "testing" +) + +func TestPairSpills(t *testing.T) { + movdLoad := func(dst, base int16, off int64) *obj.Prog { + return &obj.Prog{ + As: arm64.AMOVD, + From: obj.Addr{Type: obj.TYPE_MEM, Reg: base, Offset: off, Name: obj.NAME_AUTO}, + To: obj.Addr{Type: obj.TYPE_REG, Reg: dst}, + } + } + movdStore := func(src, base int16, off int64) *obj.Prog { + return &obj.Prog{ + As: arm64.AMOVD, + From: obj.Addr{Type: obj.TYPE_REG, Reg: src}, + To: obj.Addr{Type: obj.TYPE_MEM, Reg: base, Offset: off, Name: obj.NAME_AUTO}, + } + } + // param rewrites p's memory operand to NAME_PARAM. + param := func(p *obj.Prog) *obj.Prog { + if p.From.Type == obj.TYPE_MEM { + p.From.Name = obj.NAME_PARAM + } else { + p.To.Name = obj.NAME_PARAM + } + return p + } + chain := func(progs ...*obj.Prog) *obj.Prog { + for i := 0; i < len(progs)-1; i++ { + progs[i].Link = progs[i+1] + } + return progs[0] + } + countAs := func(head *obj.Prog) map[obj.As]int { + m := map[obj.As]int{} + for p := head; p != nil; p = p.Link { + m[p.As]++ + } + return m + } + + // pairWant describes the expected operands of the fused LDP/STP: + // MOVDs of lo and hi (lo at the lower address off, hi at off+8) + // against base, with the memory operand's addressing class name. + type pairWant struct { + as obj.As + base int16 + off int64 + lo, hi int16 + name obj.AddrName + } + + tests := []struct { + name string + framesize int64 + setup func() (*obj.Prog, []obj.JumpTable) + wantLDP int + wantSTP int + wantMOVD int + wantNOP int + wantPair *pairWant + }{ + { + name: "adjacent LDRs fuse to LDP", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "adjacent STRs fuse to STP", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdStore(arm64.REG_R0, arm64.REGSP, 16), + movdStore(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantSTP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ASTP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "reverse-order LDRs fuse to LDP", + setup: func() (*obj.Prog, []obj.JumpTable) { + // p loads from the higher address, q from the lower. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 24), + movdLoad(arm64.REG_R1, arm64.REGSP, 16), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R1, arm64.REG_R0, obj.NAME_AUTO}, + }, + { + name: "reverse-order STRs fuse to STP", + setup: func() (*obj.Prog, []obj.JumpTable) { + // p stores to the higher address, q to the lower. + return chain( + movdStore(arm64.REG_R0, arm64.REGSP, 24), + movdStore(arm64.REG_R1, arm64.REGSP, 16), + ), nil + }, + wantSTP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ASTP, arm64.REGSP, 16, arm64.REG_R1, arm64.REG_R0, obj.NAME_AUTO}, + }, + { + name: "PARAM pair fuses", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + param(movdLoad(arm64.REG_R0, arm64.REGSP, 16)), + param(movdLoad(arm64.REG_R1, arm64.REGSP, 24)), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_PARAM}, + }, + { + name: "mixed AUTO and PARAM do not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + param(movdLoad(arm64.REG_R1, arm64.REGSP, 24)), + ), nil + }, + wantMOVD: 2, + }, + { + name: "non-adjacent offsets do not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REGSP, 32), + ), nil + }, + wantMOVD: 2, + }, + { + name: "different base registers do not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REG_R28, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "load followed by store does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdStore(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "same destination register does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R0, arm64.REGSP, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "first load writing the second's base does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Executed sequentially, the second load computes its + // address from the value the first load just wrote into + // R1; LDP would compute both addresses from the original + // R1. + return chain( + movdLoad(arm64.REG_R1, arm64.REG_R1, 16), + movdLoad(arm64.REG_R2, arm64.REG_R1, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "second load writing the base fuses", + setup: func() (*obj.Prog, []obj.JumpTable) { + // No address depends on the base after the second load + // overwrites it, so LDP has identical semantics. + return chain( + movdLoad(arm64.REG_R0, arm64.REG_R5, 16), + movdLoad(arm64.REG_R5, arm64.REG_R5, 24), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REG_R5, 16, arm64.REG_R0, arm64.REG_R5, obj.NAME_AUTO}, + }, + { + name: "store whose source is the base fuses", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Stores never write registers, so there is no base + // hazard for STP. + return chain( + movdStore(arm64.REG_R5, arm64.REG_R5, 16), + movdStore(arm64.REG_R1, arm64.REG_R5, 24), + ), nil + }, + wantSTP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ASTP, arm64.REG_R5, 16, arm64.REG_R5, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "skip when second instruction is a branch target", + setup: func() (*obj.Prog, []obj.JumpTable) { + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24) + br := &obj.Prog{As: arm64.AB, To: obj.Addr{Type: obj.TYPE_BRANCH}} + br.To.SetTarget(p2) + return chain(br, p1, p2), nil + }, + wantMOVD: 2, + }, + { + name: "skip when second instruction is a backward-branch target", + setup: func() (*obj.Prog, []obj.JumpTable) { + // The branch sits after the pair, so target collection + // must consider the whole Prog list, not just what + // precedes the pair. + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24) + br := &obj.Prog{As: arm64.AB, To: obj.Addr{Type: obj.TYPE_BRANCH}} + br.To.SetTarget(p2) + return chain(p1, p2, br), nil + }, + wantMOVD: 2, + }, + { + name: "skip when second instruction is a jump-table target", + setup: func() (*obj.Prog, []obj.JumpTable) { + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24) + return chain(p1, p2), []obj.JumpTable{{Targets: []*obj.Prog{p2}}} + }, + wantMOVD: 2, + }, + { + name: "skip when second instruction is a statement boundary", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Statement-marked instructions (including those genssa + // reuses as inline marks, which it promotes to + // statements) must not become zero-sized. The statement + // bit needs a known position to stick to. + var tab src.PosTable + pos := tab.XPos(src.MakePos(src.NewFileBase("f.go", "f.go"), 1, 1)) + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24) + p2.Pos = pos.WithIsStmt() + return chain(p1, p2), nil + }, + wantMOVD: 2, + }, + { + name: "at the encodable bound fuses", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Resolved offset 496+0+8 = 504, LDP's maximum. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 496), + movdLoad(arm64.REG_R1, arm64.REGSP, 504), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 496, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "resolved offset out of LDP range does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Resolved offset 504+0+8 = 512, just past the bound. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 504), + movdLoad(arm64.REG_R1, arm64.REGSP, 512), + ), nil + }, + wantMOVD: 2, + }, + { + name: "large frame pushes resolved offset out of range", + framesize: 1024, + setup: func() (*obj.Prog, []obj.JumpTable) { + // 16+1024+8 = 1048: fusing would force an + // assembler-synthesized address, no smaller than the + // original pair. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "deep spill slots fuse in a large frame", + framesize: 1024, + setup: func() (*obj.Prog, []obj.JumpTable) { + // -528+1024+8 = 504: encodable even though the + // pre-resolution offset is far below -512. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, -528), + movdLoad(arm64.REG_R1, arm64.REGSP, -520), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, -528, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "misaligned offsets do not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, -12), + movdLoad(arm64.REG_R1, arm64.REGSP, -4), + ), nil + }, + wantMOVD: 2, + }, + { + name: "three adjacent loads fuse greedily", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REGSP, 24), + movdLoad(arm64.REG_R2, arm64.REGSP, 32), + ), nil + }, + wantLDP: 1, wantNOP: 1, wantMOVD: 1, + }, + { + name: "intervening instruction blocks fusion", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + &obj.Prog{As: arm64.AHINT}, + movdLoad(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "pre-indexed addressing does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p1.Scond = arm64.C_XPRE + return chain(p1, movdLoad(arm64.REG_R1, arm64.REGSP, 24)), nil + }, + wantMOVD: 2, + }, + { + name: "post-indexed addressing does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p1.Scond = arm64.C_XPOST + return chain(p1, movdLoad(arm64.REG_R1, arm64.REGSP, 24)), nil + }, + wantMOVD: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + head, jumpTables := tt.setup() + pairSpills(head, tt.framesize, jumpTables) + got := countAs(head) + if got[arm64.ALDP] != tt.wantLDP { + t.Errorf("ALDP count = %d, want %d", got[arm64.ALDP], tt.wantLDP) + } + if got[arm64.ASTP] != tt.wantSTP { + t.Errorf("ASTP count = %d, want %d", got[arm64.ASTP], tt.wantSTP) + } + if got[arm64.AMOVD] != tt.wantMOVD { + t.Errorf("AMOVD count = %d, want %d", got[arm64.AMOVD], tt.wantMOVD) + } + if got[obj.ANOP] != tt.wantNOP { + t.Errorf("ANOP count = %d, want %d", got[obj.ANOP], tt.wantNOP) + } + if w := tt.wantPair; w != nil { + var fused *obj.Prog + for p := head; p != nil; p = p.Link { + if p.As == arm64.ALDP || p.As == arm64.ASTP { + fused = p + break + } + } + if fused == nil { + t.Fatal("no LDP/STP emitted") + } + mem, regs := &fused.From, &fused.To // LDP order + if fused.As == arm64.ASTP { + regs, mem = &fused.From, &fused.To + } + if fused.As != w.as { + t.Errorf("fused As = %v, want %v", fused.As, w.as) + } + if mem.Type != obj.TYPE_MEM || mem.Reg != w.base || mem.Offset != w.off || mem.Name != w.name { + t.Errorf("memory operand = {Type %v, Reg %v, Offset %d, Name %v}, want {TYPE_MEM, %v, %d, %v}", + mem.Type, mem.Reg, mem.Offset, mem.Name, w.base, w.off, w.name) + } + if regs.Type != obj.TYPE_REGREG || regs.Reg != w.lo || regs.Offset != int64(w.hi) { + t.Errorf("register pair = {Type %v, Reg %v, Offset %v}, want {TYPE_REGREG, %v, %v}", + regs.Type, regs.Reg, regs.Offset, w.lo, w.hi) + } + } + }) + } +} diff --git a/src/cmd/compile/internal/ssagen/arch.go b/src/cmd/compile/internal/ssagen/arch.go index ef5d8f59d7168a..fea2630bea26c8 100644 --- a/src/cmd/compile/internal/ssagen/arch.go +++ b/src/cmd/compile/internal/ssagen/arch.go @@ -53,4 +53,11 @@ type ArchInfo struct { // SpillArgReg emits instructions that spill reg to n+off. SpillArgReg func(pp *objw.Progs, p *obj.Prog, f *ssa.Func, t *types.Type, reg int16, n *ir.Name, off int64) *obj.Prog + + // SSAGenFinish, if non-nil, is called after all of a function's Progs + // have been generated and defframe has run: branch and jump-table + // targets are resolved and the frame size is final. It allows the + // backend to run a last Prog-level pass. Used on arm64 to fuse adjacent + // spill/reload MOVDs into STP/LDP. + SSAGenFinish func(pp *objw.Progs) } diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 7d523e56928d79..9a3d08831a0c57 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -7396,6 +7396,18 @@ func genssa(f *ssa.Func, pp *objw.Progs) { fi.JumpTables = append(fi.JumpTables, obj.JumpTable{Sym: jt.Aux.(*obj.LSym), Targets: targets}) } + // Finalize the frame, then let the backend run a final pass over the + // generated Progs (e.g. arm64 fuses adjacent spill/reload MOVDs into + // STP/LDP). Branch and jump-table targets are resolved at this point. + // Doing this before the debug dumps below means -S and GOSSAFUNC's genssa + // output reflect the instructions that are actually assembled. defframe + // must run first: it finalizes the frame size, which the backend pass + // depends on. + defframe(&s, e, f) + if Arch.SSAGenFinish != nil { + Arch.SSAGenFinish(s.pp) + } + if e.log { // spew to stdout filename := "" for p := s.pp.Text; p != nil; p = p.Link { @@ -7515,8 +7527,6 @@ func genssa(f *ssa.Func, pp *objw.Progs) { } } - defframe(&s, e, f) - f.HTMLWriter.Close() f.HTMLWriter = nil } diff --git a/src/cmd/compile/internal/test/bench_test.go b/src/cmd/compile/internal/test/bench_test.go index 1d74d39a49fe78..22db15f9e296d3 100644 --- a/src/cmd/compile/internal/test/bench_test.go +++ b/src/cmd/compile/internal/test/bench_test.go @@ -204,6 +204,37 @@ func containsLeft(strs []string, str string) bool { return false } +//go:noinline +func benchSpillReloadSink() {} + +// benchSpillReloadDistinctAutos loads two values from independent pointers, +// spills both across a call, and returns them as a pair. The spills and +// reloads do not exist when the SSA pair pass runs (regalloc inserts them +// later), so only the late pair pass in cmd/compile/internal/arm64 can fuse +// the reloads into a single LDP. +// +//go:noinline +func benchSpillReloadDistinctAutos(p, q *int) (int, int) { + a := *p + b := *q + benchSpillReloadSink() + return a, b +} + +// BenchmarkSpillReloadPair exercises the spill/reload pattern the late +// arm64 pair pass fuses but the SSA pair pass cannot. The numbers are not +// meaningful in absolute terms; the benchmark exists so builds with and +// without the pass can be compared. +func BenchmarkSpillReloadPair(b *testing.B) { + x, y := 1, 2 + var s int + for b.Loop() { + a, c := benchSpillReloadDistinctAutos(&x, &y) + s += a + c + } + globl = int64(s) +} + // BenchmarkStringEqParamOrder tests that the operand order of string // equality comparisons does not affect performance. See issue #74471. func BenchmarkStringEqParamOrder(b *testing.B) { diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go index f71c806ade9dcc..cbcb391098b1dc 100644 --- a/test/codegen/memcombine.go +++ b/test/codegen/memcombine.go @@ -1150,3 +1150,28 @@ func dwstoreOrder(p *struct { p.e = true p.b = b } + +// --------------------------------------------------- // +// arm64 spill/reload pair coalescing // +// --------------------------------------------------- // + +// Spills and reloads do not exist when the SSA pair pass runs: regalloc +// inserts them later, so they never get a chance to be fused by that pass. +// A late Prog-level pass in cmd/compile/internal/arm64 catches adjacent +// spill/reload pairs that target the same base register at consecutive +// 8-byte offsets. These tests pin down that behavior. + +//go:noinline +func dwpairClobber() {} + +// Two distinct values that need to survive a call: regalloc spills both, +// and the late pass coalesces the adjacent reloads into a single LDP. The +// pattern requires a spill-slot (SP) base so that a frame-pointer epilogue +// LDP, which uses (RSP), cannot satisfy it. +func dwpairSpillReloadDistinct(p, q *int) (int, int) { + a := *p + b := *q + dwpairClobber() + // arm64:`LDP\s.+\(SP\), \(R[0-9]+, R[0-9]+\)` + return a, b +} diff --git a/test/fixedbugs/spillreload_arm64_pair.go b/test/fixedbugs/spillreload_arm64_pair.go new file mode 100644 index 00000000000000..8572c85587d7b4 --- /dev/null +++ b/test/fixedbugs/spillreload_arm64_pair.go @@ -0,0 +1,106 @@ +// run + +// Copyright 2026 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. + +// Regression coverage for the late spill/reload pair coalescer on arm64 +// (cmd/compile/internal/arm64.pairSpills). When the coalescer fused two +// adjacent AMOVD reloads into a single LDP, paths that branched directly to +// the second reload would silently skip the fused load and observe a stale +// or uninitialized register. The fix records branch and jump-table targets +// before fusing and refuses to fuse when the second instruction is one. +// These functions exercise patterns that produce strictly-adjacent reloads +// and conditional branches over calls, including the shape from +// runtime.schedule. +// +// The check is end-to-end: each function returns a value that depends on +// every spilled variable, so a missing reload produces a wrong result and +// the program panics. + +package main + +import "fmt" + +//go:noinline +func sink(int) {} + +//go:noinline +func sink2(int, int) {} + +func callTwoVars(p, q *int) (int, int) { + a := *p + b := *q + sink(0) + return a, b +} + +func condCallTwoVars(c bool, p, q *int) (int, int) { + a := *p + b := *q + if c { + sink(0) + } + return a, b +} + +// condCallThreeVars mimics the shape that produced the original +// miscompile: a conditional call surrounded by values that need to +// survive it, with the join landing on the second of two adjacent +// reloads. +func condCallThreeVars(c bool, p, q, r *int) (int, int, int) { + a := *p + b := *q + d := *r + if c { + sink2(a, b) + } + return a, b, d +} + +func loopReload(p []int) int { + s := 0 + for _, v := range p { + sink(v) + s += v + } + return s +} + +func nestedConds(c1, c2 bool, p, q, r *int) (int, int, int) { + a := *p + b := *q + d := *r + if c1 { + sink(a) + if c2 { + sink(b) + } + } + return a, b, d +} + +func main() { + x, y, z := 7, 11, 13 + if a, b := callTwoVars(&x, &y); a != 7 || b != 11 { + panic(fmt.Sprintf("callTwoVars = %d, %d", a, b)) + } + for _, c := range []bool{false, true} { + if a, b := condCallTwoVars(c, &x, &y); a != 7 || b != 11 { + panic(fmt.Sprintf("condCallTwoVars(%v) = %d, %d", c, a, b)) + } + if a, b, d := condCallThreeVars(c, &x, &y, &z); a != 7 || b != 11 || d != 13 { + panic(fmt.Sprintf("condCallThreeVars(%v) = %d, %d, %d", c, a, b, d)) + } + } + for _, c1 := range []bool{false, true} { + for _, c2 := range []bool{false, true} { + if a, b, d := nestedConds(c1, c2, &x, &y, &z); a != 7 || b != 11 || d != 13 { + panic(fmt.Sprintf("nestedConds(%v,%v) = %d, %d, %d", c1, c2, a, b, d)) + } + } + } + if s := loopReload([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); s != 55 { + panic(fmt.Sprintf("loopReload = %d", s)) + } +} From 0948ed6c8748acfd485ad14b525b3fb7744634fa Mon Sep 17 00:00:00 2001 From: "khr@golang.org" Date: Wed, 8 Jul 2026 10:31:05 -0700 Subject: [PATCH 11/38] cmd/compile: remove LEA splitting Fixes #80310 Change-Id: Ia4a37a1f4327df5ebe7f7b5c764969a44be2774a Reviewed-on: https://go-review.googlesource.com/c/go/+/798400 Reviewed-by: Jorropo Reviewed-by: Florian Lehner Reviewed-by: Keith Randall Auto-Submit: Jorropo Reviewed-by: Russ Cox LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/cmd/compile/internal/amd64/ssa.go | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index cd5a2be7057c59..4dadede1b1c72e 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -739,25 +739,9 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { ssa.OpAMD64LEAW1, ssa.OpAMD64LEAW2, ssa.OpAMD64LEAW4, ssa.OpAMD64LEAW8: p := s.Prog(v.Op.Asm()) memIdx(&p.From, v) - o := v.Reg() - p.To.Type = obj.TYPE_REG - p.To.Reg = o - if v.AuxInt != 0 && v.Aux == nil { - // Emit an additional LEA to add the displacement instead of creating a slow 3 operand LEA. - switch v.Op { - case ssa.OpAMD64LEAQ1, ssa.OpAMD64LEAQ2, ssa.OpAMD64LEAQ4, ssa.OpAMD64LEAQ8: - p = s.Prog(x86.ALEAQ) - case ssa.OpAMD64LEAL1, ssa.OpAMD64LEAL2, ssa.OpAMD64LEAL4, ssa.OpAMD64LEAL8: - p = s.Prog(x86.ALEAL) - case ssa.OpAMD64LEAW1, ssa.OpAMD64LEAW2, ssa.OpAMD64LEAW4, ssa.OpAMD64LEAW8: - p = s.Prog(x86.ALEAW) - } - p.From.Type = obj.TYPE_MEM - p.From.Reg = o - p.To.Type = obj.TYPE_REG - p.To.Reg = o - } ssagen.AddAux(&p.From, v) + p.To.Type = obj.TYPE_REG + p.To.Reg = v.Reg() case ssa.OpAMD64LEAQ, ssa.OpAMD64LEAL, ssa.OpAMD64LEAW: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM From f23bcc2fa78e5b47665ec9322d1263ae654bd226 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 29 Jun 2026 09:42:33 -0700 Subject: [PATCH 12/38] cmd/compile: remove write barrier wbMove ABI optimization It isn't safe, as it depends on the body of wbMove not spilling its arguments. Fixes #80196 Change-Id: If42afa161fff70527e310256cdc619d5741b7753 Reviewed-on: https://go-review.googlesource.com/c/go/+/795380 Reviewed-by: Keith Randall Reviewed-by: Cherry Mui LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Keith Randall --- src/cmd/compile/internal/ssa/writebarrier.go | 40 +++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/cmd/compile/internal/ssa/writebarrier.go b/src/cmd/compile/internal/ssa/writebarrier.go index ec5a0fed29d791..a735c41d36903f 100644 --- a/src/cmd/compile/internal/ssa/writebarrier.go +++ b/src/cmd/compile/internal/ssa/writebarrier.go @@ -326,31 +326,25 @@ func writebarrier(f *Func) { tmp *Value // address of temporary we've copied the volatile value into } var volatiles []volatileCopy - - if !(f.ABIDefault == f.ABI1 && len(f.Config.intParamRegs) >= 3) { - // We don't need to do this if the calls we're going to do take - // all their arguments in registers. - // 3 is the magic number because it covers wbZero, wbMove, cgoCheckMemmove. - copyLoop: - for _, w := range stores { - if w.Op == OpMoveWB { - val := w.Args[1] - if isVolatile(val) { - for _, c := range volatiles { - if val == c.src { - continue copyLoop // already copied - } + copyLoop: + for _, w := range stores { + if w.Op == OpMoveWB { + val := w.Args[1] + if isVolatile(val) { + for _, c := range volatiles { + if val == c.src { + continue copyLoop // already copied } - - t := val.Type.Elem() - tmp := f.NewLocal(w.Pos, t) - mem = b.NewValue1A(w.Pos, OpVarDef, types.TypeMem, tmp, mem) - tmpaddr := b.NewValue2A(w.Pos, OpLocalAddr, t.PtrTo(), tmp, sp, mem) - siz := t.Size() - mem = b.NewValue3I(w.Pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem) - mem.Aux = t - volatiles = append(volatiles, volatileCopy{val, tmpaddr}) } + + t := val.Type.Elem() + tmp := f.NewLocal(w.Pos, t) + mem = b.NewValue1A(w.Pos, OpVarDef, types.TypeMem, tmp, mem) + tmpaddr := b.NewValue2A(w.Pos, OpLocalAddr, t.PtrTo(), tmp, sp, mem) + siz := t.Size() + mem = b.NewValue3I(w.Pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem) + mem.Aux = t + volatiles = append(volatiles, volatileCopy{val, tmpaddr}) } } } From 553ac454c272e50ba2a8c0da3adb69392d8bd93e Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 6 Jun 2026 08:29:37 +0200 Subject: [PATCH 13/38] cmd/compile: cleanup PPC64 atomics ssa asm generation Follows up CL 774020. This should have no behavior change. Change-Id: I5f1070bc5740c477654de10393d9c498ce257728 Reviewed-on: https://go-review.googlesource.com/c/go/+/787760 Reviewed-by: David Chase Reviewed-by: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Keith Randall --- src/cmd/compile/internal/ppc64/ssa.go | 28 +++++++++------------------ 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/cmd/compile/internal/ppc64/ssa.go b/src/cmd/compile/internal/ppc64/ssa.go index a0d81d31d6de7d..316d18d4f7bd27 100644 --- a/src/cmd/compile/internal/ppc64/ssa.go +++ b/src/cmd/compile/internal/ppc64/ssa.go @@ -146,8 +146,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { r1 := v.Args[1].Reg() // LWSYNC - Assuming shared data not write-through-required nor // caching-inhibited. See Appendix B.2.2.2 in the ISA 2.07b. - plwsync := s.Prog(ppc64.ALWSYNC) - plwsync.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ALWSYNC) // LBAR or LWAR p := s.Prog(ld) p.From.Type = obj.TYPE_MEM @@ -194,9 +193,8 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { r0 := v.Args[0].Reg() r1 := v.Args[1].Reg() out := v.Reg0() - // LWSYNC - Provide acquire ordering to pair with the - // release (pre-LWSYNC) above, making the operation - // sequentially consistent. + // LWSYNC - Assuming shared data not write-through-required nor + // caching-inhibited. See Appendix B.2.2.2 in the ISA 2.07b. s.Prog(ppc64.ALWSYNC) // LDAR or LWAR p := s.Prog(ld) @@ -232,8 +230,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // LWSYNC - Provide acquire ordering to pair with the // release (pre-LWSYNC) above, making the operation // sequentially consistent. - plwsync2 := s.Prog(ppc64.ALWSYNC) - plwsync2.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ALWSYNC) case ssa.OpPPC64LoweredAtomicExchange8, ssa.OpPPC64LoweredAtomicExchange32, @@ -258,8 +255,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { out := v.Reg0() // LWSYNC - Assuming shared data not write-through-required nor // caching-inhibited. See Appendix B.2.2.2 in the ISA 2.07b. - plwsync := s.Prog(ppc64.ALWSYNC) - plwsync.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ALWSYNC) // L[B|W|D]AR p := s.Prog(ld) p.From.Type = obj.TYPE_MEM @@ -277,8 +273,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p2.To.Type = obj.TYPE_BRANCH p2.To.SetTarget(p) // ISYNC - pisync := s.Prog(ppc64.AISYNC) - pisync.To.Type = obj.TYPE_NONE + s.Prog(ppc64.AISYNC) case ssa.OpPPC64LoweredAtomicLoad8, ssa.OpPPC64LoweredAtomicLoad32, @@ -302,8 +297,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { out := v.Reg0() // SYNC when AuxInt == 1; otherwise, load-acquire if v.AuxInt == 1 { - psync := s.Prog(ppc64.ASYNC) - psync.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ASYNC) } // Load p := s.Prog(ld) @@ -322,7 +316,6 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p2.To.Type = obj.TYPE_BRANCH // ISYNC pisync := s.Prog(ppc64.AISYNC) - pisync.To.Type = obj.TYPE_NONE p2.To.SetTarget(pisync) case ssa.OpPPC64LoweredAtomicStore8, @@ -345,8 +338,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { if v.AuxInt == 0 { syncOp = ppc64.ALWSYNC } - psync := s.Prog(syncOp) - psync.To.Type = obj.TYPE_NONE + s.Prog(syncOp) // Store p := s.Prog(st) p.To.Type = obj.TYPE_MEM @@ -387,8 +379,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Reg = out // LWSYNC - Assuming shared data not write-through-required nor // caching-inhibited. See Appendix B.2.2.2 in the ISA 2.07b. - plwsync1 := s.Prog(ppc64.ALWSYNC) - plwsync1.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ALWSYNC) // LDAR or LWAR p0 := s.Prog(ld) p0.From.Type = obj.TYPE_MEM @@ -430,7 +421,6 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // If the operation is a CAS-Release, then synchronization is not necessary. if v.AuxInt != 0 { plwsync2 := s.Prog(ppc64.ALWSYNC) - plwsync2.To.Type = obj.TYPE_NONE p2.To.SetTarget(plwsync2) } else { // done (label) From 56d7cfd457a7823b48af8bcb77135c12ac5aa225 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 13 May 2023 00:28:59 +0200 Subject: [PATCH 14/38] cmd/compile: on AMD64 use leave instruction for go compiled functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the go compiler compiles a function it saves the frame pointer on the stack and pushes the stack pointer. Thus it is safe to restore from the stack. For assembly function still use the mathematical based restore as users might use the frame pointer as scratch space. I also had to fix a bug in the runtime where in one edge case we were restoring the framepointer to a zero value. The results are very good, file size down 0.8% codesize down 2%. Files: file before after Δ % addr2line 3950752 3919200 -31552 -0.799% asm 7228164 7176284 -51880 -0.718% buildid 3813377 3782545 -30832 -0.809% cgo 6184696 6135728 -48968 -0.792% compile 37066595 36734315 -332280 -0.896% covdata 4529478 4497638 -31840 -0.703% cover 7906781 7840805 -65976 -0.834% dist 5329896 5285504 -44392 -0.833% distpack 4028506 3996578 -31928 -0.793% fix 12706126 12614110 -92016 -0.724% link 10097646 10020534 -77112 -0.764% nm 3924002 3896858 -27144 -0.692% objdump 6570663 6523399 -47264 -0.719% pack 3255175 3229463 -25712 -0.790% pprof 20273692 20094988 -178704 -0.881% preprofile 3373625 3351201 -22424 -0.665% test2json 4563667 4524259 -39408 -0.864% trace 18675260 18526132 -149128 -0.799% vet 12297424 12200576 -96848 -0.788% total 175775525 174350117 -1425408 -0.811% Code: total 45787786 44851997 -935789 -2.044% Change-Id: Iea5801a4e14fc76bee06b48973b929d3d7d67a83 Reviewed-on: https://go-review.googlesource.com/c/go/+/548317 Auto-Submit: Jorropo Reviewed-by: Michael Pratt LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Russ Cox Reviewed-by: Keith Randall --- src/cmd/internal/obj/x86/obj6.go | 35 ++++++++++++++++++++------------ src/runtime/proc.go | 1 + 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index 8125acfa2967ff..118c936e813d87 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -823,21 +823,30 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if autoffset != 0 { to := p.To // Keep To attached to RET for retjmp below p.To = obj.Addr{} - if localoffset != 0 { - p.As = AADJSP - p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(-localoffset) - p.Spadj = -localoffset - p = obj.Appendp(p, newprog) - } - if bpsize > 0 { - // Restore caller's BP - p.As = APOPQ - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_BP - p.Spadj = -int32(bpsize) + needSpRestore, needBpRestore := localoffset != 0, bpsize > 0 + // We can't use LEAVE with assembly because the go asm promise + // it will insert save and restores for BP. Thus many pieces + // of code use BP as a scratch register. + if !ctxt.IsAsm && needSpRestore && needBpRestore { + p.As = ALEAVEQ + p.Spadj = -localoffset - int32(bpsize) p = obj.Appendp(p, newprog) + } else { + if needSpRestore { + p.As = AADJSP + p.From.Type = obj.TYPE_CONST + p.From.Offset = int64(-localoffset) + p.Spadj = -localoffset + p = obj.Appendp(p, newprog) + } + if needBpRestore { + p.As = APOPQ + p.To.Type = obj.TYPE_REG + p.To.Reg = REG_BP + p.Spadj = -int32(bpsize) + p = obj.Appendp(p, newprog) + } } p.As = obj.ARET diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 9edb1f57bb853d..3511c834f7532e 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -1930,6 +1930,7 @@ func mstart1() { gp.sched.g = guintptr(unsafe.Pointer(gp)) gp.sched.pc = sys.GetCallerPC() gp.sched.sp = sys.GetCallerSP() + gp.sched.bp = getcallerfp() asminit() minit() From f192b5be2ef8f82e627e923ea72507c38b9f5ea5 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 28 May 2026 15:22:34 +0700 Subject: [PATCH 15/38] cmd/compile/internal/ir: handle RHS = nil in static value For #5370 Change-Id: I04ca4e38e5e4d54814eb5ec50e698d190c67a652 Reviewed-on: https://go-review.googlesource.com/c/go/+/784280 Reviewed-by: Keith Randall Reviewed-by: Russ Cox Auto-Submit: Cuong Manh Le LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Pratt --- src/cmd/compile/internal/ir/expr.go | 3 +++ src/cmd/compile/internal/ir/reassignment.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 9b4577bd37f332..e45b1d6bb3020b 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -935,6 +935,9 @@ FindRHS: return nil } if rhs == nil { + if n.AutoTemp() { + return nil + } base.FatalfAt(defn.Pos(), "RHS is nil: %v", defn) } diff --git a/src/cmd/compile/internal/ir/reassignment.go b/src/cmd/compile/internal/ir/reassignment.go index ff3d08cd5b1bbc..19926dfacb483a 100644 --- a/src/cmd/compile/internal/ir/reassignment.go +++ b/src/cmd/compile/internal/ir/reassignment.go @@ -221,6 +221,9 @@ FindRHS: return nil } if rhs == nil { + if n.AutoTemp() { + return nil + } base.FatalfAt(defn.Pos(), "RHS is nil: %v", defn) } From 2a95a616e9da16f49dfc55b11d7ca9f079bbb430 Mon Sep 17 00:00:00 2001 From: Julien Cretel Date: Sun, 31 May 2026 12:53:07 +0000 Subject: [PATCH 16/38] net/http: reject userinfo in CrossOriginProtection (*CrossOriginProtection).AddTrustedOrigin fails if its argument contains a path, a query, and/or a fragment, but it doesn't check for the presence of a userinfo part. Such an omission doesn't affect security (because serialized origins don't contain any userinfo part), but it causes invalid origins to be silently accepted, which could create some confusion. With this CL, the method now also fails if its argument contains a userinfo part (even if it's empty). Change-Id: Ic570997dfe062284c51498272f61f7c743c8eb16 GitHub-Last-Rev: 232ab09df3a4d577b85f79694958ff0e9e13ed7b GitHub-Pull-Request: golang/go#79766 Reviewed-on: https://go-review.googlesource.com/c/go/+/785520 Reviewed-by: Russ Cox Reviewed-by: Viacheslav Danilin Auto-Submit: Filippo Valsorda Reviewed-by: Filippo Valsorda Reviewed-by: Michael Pratt LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/net/http/csrf.go | 4 ++-- src/net/http/csrf_test.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/net/http/csrf.go b/src/net/http/csrf.go index 0b71b403899a2a..448052bdf5a554 100644 --- a/src/net/http/csrf.go +++ b/src/net/http/csrf.go @@ -65,8 +65,8 @@ func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error { if u.Host == "" { return fmt.Errorf("invalid origin %q: host is required", origin) } - if u.Path != "" || u.RawQuery != "" || u.Fragment != "" { - return fmt.Errorf("invalid origin %q: path, query, and fragment are not allowed", origin) + if u.User != nil || u.Path != "" || u.RawQuery != "" || u.Fragment != "" { + return fmt.Errorf("invalid origin %q: userinfo, path, query, and fragment are not allowed", origin) } c.trustedMu.Lock() defer c.trustedMu.Unlock() diff --git a/src/net/http/csrf_test.go b/src/net/http/csrf_test.go index a29e3ae16dff6b..0d1f6201524ea7 100644 --- a/src/net/http/csrf_test.go +++ b/src/net/http/csrf_test.go @@ -235,6 +235,8 @@ func TestCrossOriginProtectionAddTrustedOriginErrors(t *testing.T) { {"http origin", "http://example.com", false}, {"missing scheme", "example.com", true}, {"missing host", "https://", true}, + {"with empty userinfo", "https://@example.com", true}, + {"with nonempty userinfo", "https://foo:bar@example.com", true}, {"trailing slash", "https://example.com/", true}, {"with path", "https://example.com/path", true}, {"with query", "https://example.com?query=value", true}, From f0c78119eaf8128e664c3ab9d6e61d42ec456009 Mon Sep 17 00:00:00 2001 From: Jake Bailey Date: Mon, 8 Jun 2026 16:02:04 -0700 Subject: [PATCH 17/38] cmd/compile/internal/types: store SIMD flags in Type.flags bitset Replace the isSIMDTag and isSIMD bool fields on types.Type with bits in the existing flags bitset. Widen flags from bitset8 to bitset16 to make room. Change-Id: I856c6d4e28feaf7cc9dc5ef7a99b613bf9784d31 Reviewed-on: https://go-review.googlesource.com/c/go/+/788580 Reviewed-by: Keith Randall Reviewed-by: Michael Pratt LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Michael Pratt Reviewed-by: Russ Cox --- src/cmd/compile/internal/types/size.go | 6 +++--- src/cmd/compile/internal/types/type.go | 15 ++++++++------- src/cmd/compile/internal/types/utils.go | 10 ++++++++++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/cmd/compile/internal/types/size.go b/src/cmd/compile/internal/types/size.go index 4d64c7f8d28cc9..c52ce6e8c4170d 100644 --- a/src/cmd/compile/internal/types/size.go +++ b/src/cmd/compile/internal/types/size.go @@ -469,10 +469,10 @@ func simdify(st *Type, isTag bool) { st.align = 8 st.alg = ANOALG // not comparable with == st.intRegs = 0 - st.isSIMD = true + st.flags |= typeIsSIMD if isTag { st.width = 0 - st.isSIMDTag = true + st.flags |= typeIsSIMDTag st.floatRegs = 0 } else { st.floatRegs = 1 @@ -584,7 +584,7 @@ func CalcStructSize(t *Type) { } } - if len(t.Fields()) >= 1 && t.Fields()[0].Type.isSIMDTag { + if len(t.Fields()) >= 1 && t.Fields()[0].Type.flags&typeIsSIMDTag != 0 { // this catches `type Foo simd.Whatever` -- Foo is also SIMD. simdify(t, false) } diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go index 3fa0b90e12938e..f20932ae7ecafe 100644 --- a/src/cmd/compile/internal/types/type.go +++ b/src/cmd/compile/internal/types/type.go @@ -200,9 +200,8 @@ type Type struct { intRegs, floatRegs uint8 // registers needed for ABIInternal - flags bitset8 - alg AlgKind // valid if Align > 0 - isSIMDTag, isSIMD bool // tag is the marker type, isSIMD means has marker type + flags bitset16 + alg AlgKind // valid if Align > 0 // size of prefix of object that contains all pointers. valid if Align > 0. // Note that for pointers, this is always PtrSize even if the element type @@ -233,6 +232,8 @@ const ( // typeIsFullyInstantiated reports whether a type is fully instantiated generic type; i.e. // an instantiated generic type where all type arguments are non-generic or fully instantiated generic types. typeIsFullyInstantiated + typeIsSIMDTag // type is the SIMD marker type + typeIsSIMD // type contains the SIMD marker type ) func (t *Type) NotInHeap() bool { return t.flags&typeNotInHeap != 0 } @@ -597,7 +598,7 @@ func newSSA(name string) *Type { func newSIMD(name string) *Type { t := newSSA(name) - t.isSIMD = true + t.flags |= typeIsSIMD return t } @@ -1683,8 +1684,8 @@ func (t *Type) SetUnderlying(underlying *Type) { if underlying.HasShape() { t.SetHasShape(true) } - if underlying.isSIMD { - simdify(t, underlying.isSIMDTag) + if underlying.flags&typeIsSIMD != 0 { + simdify(t, underlying.flags&typeIsSIMDTag != 0) } // spec: "The declared type does not inherit any methods bound @@ -1988,5 +1989,5 @@ var SimType [NTYPE]Kind var ShapePkg = NewPkg("go.shape", "go.shape") func (t *Type) IsSIMD() bool { - return t.isSIMD + return t.flags&typeIsSIMD != 0 } diff --git a/src/cmd/compile/internal/types/utils.go b/src/cmd/compile/internal/types/utils.go index f9f629ca3ea6cf..58c9641c42f4ca 100644 --- a/src/cmd/compile/internal/types/utils.go +++ b/src/cmd/compile/internal/types/utils.go @@ -15,3 +15,13 @@ func (f *bitset8) set(mask uint8, b bool) { *(*uint8)(f) &^= mask } } + +type bitset16 uint16 + +func (f *bitset16) set(mask uint16, b bool) { + if b { + *(*uint16)(f) |= mask + } else { + *(*uint16)(f) &^= mask + } +} From 4472de41fdd43549ee5f2a865cf12f8e8924885f Mon Sep 17 00:00:00 2001 From: kinsonnee Date: Fri, 3 Jul 2026 19:47:00 +0800 Subject: [PATCH 18/38] cmd/internal/obj/riscv: document rva23u64 feature macros Document that GORISCV64=rva23u64 defines hasZfa and hasZicond, matching the feature macros provided by runtime/asm_riscv64.h. Change-Id: I26e2d49ea8f74951ebf35002beeb812dde632a40 Reviewed-on: https://go-review.googlesource.com/c/go/+/796820 Reviewed-by: Russ Cox Reviewed-by: Michael Pratt Auto-Submit: Joel Sing Reviewed-by: Julian Zhu Reviewed-by: Meng Zhuo LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Joel Sing --- src/cmd/internal/obj/riscv/doc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmd/internal/obj/riscv/doc.go b/src/cmd/internal/obj/riscv/doc.go index d8b2c9ad7193c3..e3dbccf14bbd06 100644 --- a/src/cmd/internal/obj/riscv/doc.go +++ b/src/cmd/internal/obj/riscv/doc.go @@ -71,9 +71,9 @@ that ensure they are hardware supported. The file asm_riscv64.h defines macros for each RISC-V extension that is enabled by setting the GORISCV64 environment variable to a value other than [rva20u64]. For example, if GORISCV64=rva22u64 the macros hasZba, hasZbb and hasZbs will be -defined. If GORISCV64=rva23u64 hasV will be defined in addition to hasZba, -hasZbb and hasZbs. These macros can be used to determine whether it's safe -to use an instruction in hand-written assembly. +defined. If GORISCV64=rva23u64, hasV, hasZfa and hasZicond will be defined in +addition to hasZba, hasZbb and hasZbs. These macros can be used to determine +whether it's safe to use an instruction in hand-written assembly. It is not always necessary to include asm_riscv64.h and use #ifdefs in your code to safely take advantage of instructions present in the [rva22u64] From cff61723f360884a0ca0b3d373715de5ec8d079e Mon Sep 17 00:00:00 2001 From: David Chase Date: Fri, 10 Jul 2026 10:39:31 -0400 Subject: [PATCH 19/38] reflect: extract potentially embedded reflect types before use Embedding types can override exported reflect methods, which can cause problems. Initially Gemini-authored, extensively revised. This addresses some of the issues mentioned in comments on CL 799481. Fixes #80332. Change-Id: Icbaf2ba83cf372e611a432d732094bcfe50a2e6c Reviewed-on: https://go-review.googlesource.com/c/go/+/801180 Reviewed-by: Keith Randall Reviewed-by: Ian Lance Taylor Reviewed-by: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/reflect/all_test.go | 54 +++++++++++++++++++++++++++++++++++++++++ src/reflect/makefunc.go | 3 ++- src/reflect/map.go | 2 ++ src/reflect/type.go | 5 +++- src/reflect/value.go | 5 ++++ 5 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index c15b5882ede554..11e3a427fde479 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -4796,6 +4796,60 @@ func TestConvertPanic(t *testing.T) { }) } +type issue80332ZeroLen struct { + Type +} + +func (z issue80332ZeroLen) Len() int { + return 0 +} + +func TestIssue80332(t *testing.T) { + type helper struct { + x [1]int + y uintptr + } + var e helper + value := ValueOf(e.x[:]) + fakeType := issue80332ZeroLen{TypeOf([2]int{})} + + shouldPanic("reflect: cannot convert slice with length 1 to array with length 2", func() { + _ = value.Convert(fakeType) + }) +} + +type issue80332FakeChan struct { + Type +} + +func (c issue80332FakeChan) Kind() Kind { + return Chan +} + +func (c issue80332FakeChan) ChanDir() ChanDir { + return BothDir +} + +type issue80332FakeMap struct { + Type +} + +func (m issue80332FakeMap) Kind() Kind { + return Map +} + +func TestIssue80332Others(t *testing.T) { + fakeChan := issue80332FakeChan{TypeOf(0)} + shouldPanic("reflect.MakeChan of non-chan type", func() { + _ = MakeChan(fakeChan, 0) + }) + + fakeMap := issue80332FakeMap{TypeOf(0)} + shouldPanic("reflect.MakeMapWithSize of non-map type", func() { + _ = MakeMapWithSize(fakeMap, 0) + }) +} + func TestConvertSlice2Array(t *testing.T) { s := make([]int, 4) p := [4]int{} diff --git a/src/reflect/makefunc.go b/src/reflect/makefunc.go index d35c92a14c7317..b550ce0d8eef28 100644 --- a/src/reflect/makefunc.go +++ b/src/reflect/makefunc.go @@ -45,11 +45,12 @@ type makeFuncImpl struct { // The Examples section of the documentation includes an illustration // of how to use MakeFunc to build a swap function for different types. func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { + t := typ.common() + typ = toType(t) // for #80332, ensure t's exported methods are not shadowed if typ.Kind() != Func { panic("reflect: call of MakeFunc with non-Func type") } - t := typ.common() ftyp := (*funcType)(unsafe.Pointer(t)) code := abi.FuncPCABI0(makeFuncStub) diff --git a/src/reflect/map.go b/src/reflect/map.go index 6c81d5f7e25227..bd3378aabb5a32 100644 --- a/src/reflect/map.go +++ b/src/reflect/map.go @@ -30,6 +30,8 @@ func (t *rtype) Key() Type { func MapOf(key, elem Type) Type { ktyp := key.common() etyp := elem.common() + key = toType(ktyp) + elem = toType(etyp) if ktyp.Equal == nil { panic("reflect.MapOf: invalid key type " + stringFor(ktyp)) diff --git a/src/reflect/type.go b/src/reflect/type.go index cb040037b771b1..67eaefa0f9f95d 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -1821,6 +1821,7 @@ var funcLookupCache struct { // If t's size is equal to or exceeds this limit, ChanOf panics. func ChanOf(dir ChanDir, t Type) Type { typ := t.common() + t = toType(typ) // for #80332, ensure t's exported methods are not shadowed // Look in cache. ckey := cacheKey{Chan, typ, nil, uintptr(dir)} @@ -1912,7 +1913,7 @@ func initFuncTypes(n int) Type { // panics if the in[len(in)-1] does not represent a slice and variadic is // true. func FuncOf(in, out []Type, variadic bool) Type { - if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { + if variadic && (len(in) == 0 || toType(in[len(in)-1].common()).Kind() != Slice) { panic("reflect.FuncOf: last arg of variadic func must be slice") } @@ -2128,6 +2129,7 @@ func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) { // For example, if t represents int, SliceOf(t) represents []int. func SliceOf(t Type) Type { typ := t.common() + t = toType(typ) // for #80332, ensure t's exported methods are not shadowed // Look in cache. ckey := cacheKey{Slice, typ, nil, 0} @@ -2656,6 +2658,7 @@ func ArrayOf(length int, elem Type) Type { } typ := elem.common() + elem = toType(typ) // for #80332, ensure elem's exported methods are not shadowed // Look in cache. ckey := cacheKey{Array, typ, nil, uintptr(length)} diff --git a/src/reflect/value.go b/src/reflect/value.go index 7afab5376d19f7..51f52001134472 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -3083,6 +3083,7 @@ func unsafe_NewArray(*abi.Type, int) unsafe.Pointer // MakeSlice creates a new zero-initialized slice value // for the specified slice type, length, and capacity. func MakeSlice(typ Type, len, cap int) Value { + typ = toType(typ.common()) if typ.Kind() != Slice { panic("reflect.MakeSlice of non-slice type") } @@ -3112,6 +3113,7 @@ func SliceAt(typ Type, p unsafe.Pointer, n int) Value { // MakeChan creates a new channel with the specified type and buffer size. func MakeChan(typ Type, buffer int) Value { + typ = toType(typ.common()) if typ.Kind() != Chan { panic("reflect.MakeChan of non-chan type") } @@ -3134,6 +3136,7 @@ func MakeMap(typ Type) Value { // MakeMapWithSize creates a new map with the specified type // and initial space for approximately n elements. func MakeMapWithSize(typ Type, n int) Value { + typ = toType(typ.common()) if typ.Kind() != Map { panic("reflect.MakeMapWithSize of non-map type") } @@ -3260,6 +3263,7 @@ func (v Value) Convert(t Type) Value { if v.flag&flagMethod != 0 { v = makeMethodValue("Convert", v) } + t = toType(t.common()) op := convertOp(t.common(), v.typ()) if op == nil { panic("reflect.Value.Convert: value of type " + stringFor(v.typ()) + " cannot be converted to type " + t.String()) @@ -3271,6 +3275,7 @@ func (v Value) Convert(t Type) Value { // If v.CanConvert(t) returns true then v.Convert(t) will not panic. func (v Value) CanConvert(t Type) bool { vt := v.Type() + t = toType(t.common()) if !vt.ConvertibleTo(t) { return false } From 58e8638c8058889cad79fc65b94ebcfd6e3b6ea8 Mon Sep 17 00:00:00 2001 From: kinsonnee Date: Mon, 29 Jun 2026 13:25:26 +0800 Subject: [PATCH 20/38] cmd/compile: require alignment for memcombine on riscv64 On strict-alignment targets, only combine loads and stores when the merged memory access is known to be aligned. Share pointer-alignment analysis between load and store combining. Keep it conservative by deriving alignment from typed base pointers and constant offsets, and by treating unknown pointer operations as minimally aligned. Add riscv64 codegen tests covering aligned and unaligned load and store memcombine cases. Fixes #71778 Change-Id: If6fbd040edb43fc02b7f343085fa20027695c77d Reviewed-on: https://go-review.googlesource.com/c/go/+/795260 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Russ Cox Reviewed-by: Keith Randall Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/memcombine.go | 121 +++++++- test/codegen/memcombine.go | 340 +++++++++++++++++++++ 2 files changed, 447 insertions(+), 14 deletions(-) diff --git a/src/cmd/compile/internal/ssa/memcombine.go b/src/cmd/compile/internal/ssa/memcombine.go index 288b7f145ab423..fd627c790c854d 100644 --- a/src/cmd/compile/internal/ssa/memcombine.go +++ b/src/cmd/compile/internal/ssa/memcombine.go @@ -13,20 +13,21 @@ import ( ) // memcombine combines smaller loads and stores into larger ones. -// We ensure this generates good code for encoding/binary operations. -// It may help other cases also. +// This produces good code for encoding/binary operations and may help other +// cases too. On architectures that do not allow unaligned accesses, the pass +// uses pointer alignment facts to avoid introducing unaligned wider operations. func memcombine(f *Func) { - // This optimization requires that the architecture has - // unaligned loads and unaligned stores. + var ptrAlignments []int8 if !f.Config.unalignedOK { - return + ptrAlignments = f.Cache.allocInt8Slice(f.NumValues()) + defer f.Cache.freeInt8Slice(ptrAlignments) + computePtrAlignments(f, ptrAlignments) } - - memcombineLoads(f) - memcombineStores(f) + memcombineLoads(f, ptrAlignments) + memcombineStores(f, ptrAlignments) } -func memcombineLoads(f *Func) { +func memcombineLoads(f *Func, ptrAlignments []int8) { // Find "OR trees" to start with. mark := f.newSparseSet(f.NumValues()) defer f.retSparseSet(mark) @@ -77,7 +78,7 @@ func memcombineLoads(f *Func) { continue } for n := max; n > 1; n /= 2 { - if combineLoads(v, n) { + if combineLoads(v, n, ptrAlignments) { break } } @@ -195,7 +196,90 @@ func splitPtr(ptr *Value) (BaseAddress, int64) { return BaseAddress{ptr: ptr, idx: idx}, off } -func combineLoads(root *Value, n int64) bool { +// computePtrAlignments computes pointer alignment facts from typed base pointers +// and constant offsets. +func computePtrAlignments(f *Func, ptrAlignments []int8) { + for _, b := range slices.Backward(f.postorder()) { + for _, v := range b.Values { + ptrAlignments[v.ID] = int8(valuePtrAlignment(v, ptrAlignments)) + } + } +} + +// ptrAlignment only reads already-computed facts. Zero/not-yet-known means +// alignment 1, avoiding recursive Phi/cycle walks. +func ptrAlignment(ptr *Value, ptrAlignments []int8) int64 { + if align := ptrAlignments[ptr.ID]; align > 0 { + return int64(align) + } + return 1 +} + +// valuePtrAlignment computes one entry in ptrAlignments. +func valuePtrAlignment(v *Value, ptrAlignments []int8) int64 { + // computePtrAlignments visits every SSA value, not just pointer values. + if !v.Type.IsPtr() { + return 1 + } + + switch v.Op { + case OpOffPtr: + return offsetAlignment(ptrAlignment(v.Args[0], ptrAlignments), v.AuxInt) + case OpCopy, OpNilCheck: + return ptrAlignment(v.Args[0], ptrAlignments) + case OpAddr, OpLocalAddr, OpArg, OpArgIntReg: + return typeAlignment(v.Type.Elem()) + case OpPhi: + align := ptrAlignment(v.Args[0], ptrAlignments) + for _, arg := range v.Args[1:] { + if argAlign := ptrAlignment(arg, ptrAlignments); argAlign < align { + align = argAlign + } + } + return align + } + return 1 +} + +// typeAlignment returns a conservative alignment for t without calling +// Type.Alignment, which may try to calculate type sizes while the compiler +// back end is running concurrently. +func typeAlignment(t *types.Type) int64 { + switch t.Kind() { + case types.TBOOL, types.TINT8, types.TUINT8: + return 1 + case types.TINT16, types.TUINT16: + return 2 + case types.TINT32, types.TUINT32, types.TFLOAT32, types.TCOMPLEX64: + return 4 + case types.TINT64, types.TUINT64, types.TFLOAT64, types.TCOMPLEX128: + return 8 + case types.TINT, types.TUINT, types.TUINTPTR, types.TPTR, types.TUNSAFEPTR, types.TSTRING, types.TSLICE, types.TFUNC, types.TMAP, types.TCHAN: + return int64(types.PtrSize) + case types.TARRAY: + return typeAlignment(t.Elem()) + case types.TSTRUCT: + align := int64(1) + for _, f := range t.Fields() { + fieldAlign := typeAlignment(f.Type) + if fieldAlign > align { + align = fieldAlign + } + } + return align + } + return 1 +} + +func offsetAlignment(align, off int64) int64 { + off &= align - 1 + if off == 0 { + return align + } + return off & -off +} + +func combineLoads(root *Value, n int64, ptrAlignments []int8) bool { orOp := root.Op var shiftOp Op switch orOp { @@ -307,6 +391,9 @@ func combineLoads(root *Value, n int64) bool { return false } } + if !root.Block.Func.Config.unalignedOK && ptrAlignment(r[0].load.Args[0], ptrAlignments) < n*size { + return false + } // Check for reads in little-endian or big-endian order. shift0 := r[0].shift @@ -394,7 +481,7 @@ func combineLoads(root *Value, n int64) bool { return true } -func memcombineStores(f *Func) { +func memcombineStores(f *Func, ptrAlignments []int8) { mark := f.newSparseSet(f.NumValues()) defer f.retSparseSet(mark) var order []*Value @@ -438,13 +525,13 @@ func memcombineStores(f *Func) { continue } - combineStores(v) + combineStores(v, ptrAlignments) } } } // combineStores tries to combine the stores ending in root. -func combineStores(root *Value) { +func combineStores(root *Value, ptrAlignments []int8) { // Helper functions. maxRegSize := root.Block.Func.Config.RegSize type StoreRecord struct { @@ -625,6 +712,9 @@ func combineStores(root *Value) { } // Memory location we're going to write at (the lowest one). ptr := a[0].store.Args[0] + if !root.Block.Func.Config.unalignedOK && ptrAlignment(ptr, ptrAlignments) < aTotalSize { + return + } // Check for constant stores isConst := true @@ -718,6 +808,9 @@ func combineStores(root *Value) { if loadMem != nil { // Modify the first load to do a larger load instead. load := a[0].store.Args[1] + if !root.Block.Func.Config.unalignedOK && ptrAlignment(load.Args[0], ptrAlignments) < aTotalSize { + return + } switch aTotalSize { case 2: load.Type = types.Types[types.TUINT16] diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go index cbcb391098b1dc..d3f2e338097193 100644 --- a/test/codegen/memcombine.go +++ b/test/codegen/memcombine.go @@ -17,6 +17,7 @@ import ( func load_le64(b []byte) uint64 { // amd64:`MOVQ \(.*\),` -`MOV[BWL] [^$]` -`OR` + // riscv64:-`MOV \(X[0-9]+\), X[0-9]+` // s390x:`MOVDBR \(.*\),` // arm64:`MOVD \(R[0-9]+\),` -`MOV[BHW]` // loong64:`MOVV \(R[0-9]+\),` @@ -184,6 +185,225 @@ func load_le_byte8_uint64_inv(s []byte) uint64 { return uint64(s[7])<<56 | uint64(s[6])<<48 | uint64(s[5])<<40 | uint64(s[4])<<32 | uint64(s[3])<<24 | uint64(s[2])<<16 | uint64(s[1])<<8 | uint64(s[0]) } +type memCombineAligned struct { + x uint64 + b [16]byte +} + +func load_le_byte8_uint64_aligned(s *memCombineAligned) uint64 { + // riscv64:`MOV 8\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_aligned_offset1(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[1:]) +} + +func load_le_byte8_uint64_aligned_offset2(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[2:]) +} + +func load_le_byte8_uint64_aligned_offset3(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[3:]) +} + +func load_le_byte8_uint64_aligned_offset4(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[4:]) +} + +func load_le_byte8_uint64_aligned_offset5(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[5:]) +} + +func load_le_byte8_uint64_aligned_offset6(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[6:]) +} + +func load_le_byte8_uint64_aligned_offset7(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[7:]) +} + +func load_le_byte8_uint64_aligned_offset8(s *memCombineAligned) uint64 { + // riscv64:`MOV 16\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint64(s.b[8:]) +} + +func load_le_byte4_uint32_aligned(s *memCombineAligned) uint32 { + // riscv64:`MOVWU 8\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint32(s.b[:]) +} + +func load_le_byte4_uint32_aligned_offset2(s *memCombineAligned) uint32 { + // riscv64:`MOVBU` -`MOVWU` + return binary.LittleEndian.Uint32(s.b[2:]) +} + +func load_le_byte4_uint32_aligned_offset4(s *memCombineAligned) uint32 { + // riscv64:`MOVWU 12\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint32(s.b[4:]) +} + +func load_le_byte2_uint16_aligned(s *memCombineAligned) uint16 { + // riscv64:`MOVHU 8\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint16(s.b[:]) +} + +func load_le_byte2_uint16_aligned_offset1(s *memCombineAligned) uint16 { + // riscv64:`MOVBU` -`MOVHU` + return binary.LittleEndian.Uint16(s.b[1:]) +} + +type memCombineUnaligned08 struct { + x byte + _ [0]byte + b [8]byte +} + +type memCombineUnaligned18 struct { + x byte + _ [1]byte + b [8]byte +} + +type memCombineUnaligned28 struct { + x byte + _ [2]byte + b [8]byte +} + +type memCombineUnaligned38 struct { + x byte + _ [3]byte + b [8]byte +} + +type memCombineUnaligned48 struct { + x byte + _ [4]byte + b [8]byte +} + +type memCombineUnaligned58 struct { + x byte + _ [5]byte + b [8]byte +} + +type memCombineUnaligned68 struct { + x byte + _ [6]byte + b [8]byte +} + +type memCombineUnaligned78 struct { + x byte + _ [7]byte + b [8]byte +} + +type memCombineUnaligned88 struct { + x byte + _ [8]byte + b [8]byte +} + +type memCombineUnaligned16 struct { + x byte + b [16]byte +} + +func load_le_byte8_uint64_unaligned_offset0(s *memCombineUnaligned08) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset1(s *memCombineUnaligned18) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset2(s *memCombineUnaligned28) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset3(s *memCombineUnaligned38) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset4(s *memCombineUnaligned48) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset5(s *memCombineUnaligned58) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset6(s *memCombineUnaligned68) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset7(s *memCombineUnaligned78) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset8(s *memCombineUnaligned88) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset1(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[1:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset2(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[2:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset3(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[3:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset4(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[4:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset5(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[5:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset6(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[6:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset7(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[7:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset8(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[8:]) +} + func load_be_byte2_uint16(s []byte) uint16 { // arm64:`MOVHU \(R[0-9]+\)` `REV16W` -`ORR` -`MOVB` // amd64:`MOVWLZX \([A-Z]+\)` `ROLW` -`MOVB` -`OR` @@ -512,6 +732,126 @@ func store_le64_load(b []byte, x *[8]byte) { binary.LittleEndian.PutUint64(b, binary.LittleEndian.Uint64(x[:])) } +func store_le_byte8_uint64_aligned(s *memCombineAligned, x uint64) { + // riscv64:`MOV X[0-9]+, 8\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint64(s.b[:], x) +} + +func store_le_byte8_uint64_aligned_offset1(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[1:], x) +} + +func store_le_byte8_uint64_aligned_offset2(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[2:], x) +} + +func store_le_byte8_uint64_aligned_offset3(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[3:], x) +} + +func store_le_byte8_uint64_aligned_offset4(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[4:], x) +} + +func store_le_byte8_uint64_aligned_offset5(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[5:], x) +} + +func store_le_byte8_uint64_aligned_offset6(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[6:], x) +} + +func store_le_byte8_uint64_aligned_offset7(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[7:], x) +} + +func store_le_byte8_uint64_aligned_offset8(s *memCombineAligned, x uint64) { + // riscv64:`MOV X[0-9]+, 16\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint64(s.b[8:], x) +} + +func store_le_byte4_uint32_aligned(s *memCombineAligned, x uint32) { + // riscv64:`MOVW X[0-9]+, 8\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint32(s.b[:], x) +} + +func store_le_byte4_uint32_aligned_offset2(s *memCombineAligned, x uint32) { + // riscv64:`MOVB` -`MOVW` + binary.LittleEndian.PutUint32(s.b[2:], x) +} + +func store_le_byte4_uint32_aligned_offset4(s *memCombineAligned, x uint32) { + // riscv64:`MOVW X[0-9]+, 12\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint32(s.b[4:], x) +} + +func store_le_byte2_uint16_aligned(s *memCombineAligned, x uint16) { + // riscv64:`MOVH X[0-9]+, 8\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint16(s.b[:], x) +} + +func store_le_byte2_uint16_aligned_offset1(s *memCombineAligned, x uint16) { + // riscv64:`MOVB` -`MOVH` + binary.LittleEndian.PutUint16(s.b[1:], x) +} + +func store_le_byte8_uint64_unaligned(s *memCombineUnaligned08, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[:], x) +} + +func store_le_byte8_uint64_unaligned_offset7(s *memCombineUnaligned78, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset1(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[1:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset2(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[2:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset3(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[3:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset4(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[4:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset5(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[5:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset6(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[6:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset7(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[7:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset8(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[8:], x) +} + func store_le32(b []byte, x uint32) { // amd64:`MOVL ` // arm64:`MOVW` -`MOV[BH]` From a6351c94fb080b239b5897ff5a533c0349097d42 Mon Sep 17 00:00:00 2001 From: harjoth Date: Mon, 13 Jul 2026 23:10:03 +0000 Subject: [PATCH 21/38] encoding/xml: reject invalid comment tokens EncodeToken currently rejects comment contents only when they contain "-->", which allows other occurrences of "--" and emits invalid XML. It also emits "--->" when comment content ends in a hyphen. Reject any "--" in Comment tokens, as required by XML 1.0, and insert a space before the closing delimiter when comment content ends in a hyphen. This matches encoding of struct fields tagged ",comment". Fixes #80155 Change-Id: I9ca9af44af15d58908cd471a9cf7ddd76baae33f GitHub-Last-Rev: 8e5beca65adca57d47a9d388c976db2e9fd6cc67 GitHub-Pull-Request: golang/go#80395 Reviewed-on: https://go-review.googlesource.com/c/go/+/800361 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Roland Shoemaker Reviewed-by: Russ Cox --- src/encoding/xml/marshal.go | 8 ++++++-- src/encoding/xml/marshal_test.go | 14 +++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/encoding/xml/marshal.go b/src/encoding/xml/marshal.go index 13fbeeeedc75ce..be1e84f47af9eb 100644 --- a/src/encoding/xml/marshal.go +++ b/src/encoding/xml/marshal.go @@ -224,11 +224,15 @@ func (enc *Encoder) EncodeToken(t Token) error { case CharData: escapeText(p, t, false) case Comment: - if bytes.Contains(t, endComment) { - return fmt.Errorf("xml: EncodeToken of Comment containing --> marker") + if bytes.Contains(t, ddBytes) { + return fmt.Errorf("xml: EncodeToken of Comment containing -- marker") } p.WriteString("" is invalid grammar. Make it "- -->" + p.WriteByte(' ') + } p.WriteString("-->") return p.cachedWriteError() case ProcInst: diff --git a/src/encoding/xml/marshal_test.go b/src/encoding/xml/marshal_test.go index 6c7e711aac0566..fd37412d2ca5c0 100644 --- a/src/encoding/xml/marshal_test.go +++ b/src/encoding/xml/marshal_test.go @@ -2006,7 +2006,19 @@ var encodeTokenTests = []struct { toks: []Token{ Comment("foo-->"), }, - err: "xml: EncodeToken of Comment containing --> marker", + err: "xml: EncodeToken of Comment containing -- marker", +}, { + desc: "comment with double hyphen", + toks: []Token{ + Comment("foo--bar"), + }, + err: "xml: EncodeToken of Comment containing -- marker", +}, { + desc: "comment ending in hyphen", + toks: []Token{ + Comment("foo-"), + }, + want: ``, }, { desc: "proc instruction", toks: []Token{ From c78af9ddc8cae7de702680bff121fd5046409c64 Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Fri, 10 Jul 2026 14:01:33 -0400 Subject: [PATCH 22/38] reflect: improve escape analysis for Value.Set For Value.Set, when assigning to an interface, we use the receiver's storage as a scratch space for the implicit conversion to interface, which then assigns back to the receiver. The compiler does not know 1. this occurs only when assigning to an interface with an implicit conversion, 2. this is actually a self- assignment. So it treats the receiver escape. Mark the scratch space noescape to help the compiler. So now Value.Set does not leak the receiver. Change-Id: If904277fa15912653936fd4038173691170e8789 Reviewed-on: https://go-review.googlesource.com/c/go/+/799480 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Alan Donovan Reviewed-by: David Chase --- src/reflect/value.go | 7 ++++++- test/escape_reflect.go | 22 +++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index 51f52001134472..8a81fe6ba1e6e8 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -2166,7 +2166,12 @@ func (v Value) Set(x Value) { x.mustBeExported() // do not let unexported x leak var target unsafe.Pointer if v.kind() == Interface { - target = v.ptr + // x.assignTo below uses target as a scratch space, which + // then will be assigned back to v in the code below. + // So it is a self-assignment, therefore does not cause + // escape, but the compiler cannot see it. Mark it noescape + // to help the compiler. + target = abi.NoEscape(v.ptr) } x = x.assignTo("reflect.Set", v.typ(), target) if x.flag&flagIndir != 0 { diff --git a/test/escape_reflect.go b/test/escape_reflect.go index e50323702e9162..69cc5d6aca2fd4 100644 --- a/test/escape_reflect.go +++ b/test/escape_reflect.go @@ -370,17 +370,17 @@ func convert2(x []byte) string { // ERROR "leaking param: x$" return v.Convert(stringTyp).String() } -// Unfortunate: v doesn't need to leak, x (the interface storage) doesn't need to escape. -func set1(v reflect.Value, x int) { // ERROR "leaking param: v$" +// Unfortunate: x (the interface storage) doesn't need to escape. +func set1(v reflect.Value, x int) { // ERROR "v does not escape" vx := reflect.ValueOf(x) // ERROR "x escapes to heap" v.Set(vx) } -// Unfortunate: a can be stack allocated, x (the interface storage) doesn't need to escape. +// Unfortunate: x (the interface storage) doesn't need to escape. func set2(x int) int64 { - var a int // ERROR "moved to heap: a" - v := reflect.ValueOf(&a).Elem() - vx := reflect.ValueOf(x) // ERROR "x escapes to heap" + var a int + v := reflect.ValueOf(&a).Elem() // a should not escape, no error printed + vx := reflect.ValueOf(x) // ERROR "x escapes to heap" v.Set(vx) return v.Int() } @@ -396,15 +396,19 @@ func set4(x int) int { return int(v.Int()) } -func set5(v reflect.Value, x string) { // ERROR "v does not escape" "leaking param: x$" +func set5(v any, x reflect.Value) { // ERROR "v does not escape" "leaking param: x$" + reflect.ValueOf(v).Elem().Set(x) +} + +func setstring(v reflect.Value, x string) { // ERROR "v does not escape" "leaking param: x$" v.SetString(x) } -func set6(v reflect.Value, x []byte) { // ERROR "v does not escape" "leaking param: x$" +func setbytes(v reflect.Value, x []byte) { // ERROR "v does not escape" "leaking param: x$" v.SetBytes(x) } -func set7(v reflect.Value, x unsafe.Pointer) { // ERROR "v does not escape" "leaking param: x$" +func setpointer(v reflect.Value, x unsafe.Pointer) { // ERROR "v does not escape" "leaking param: x$" v.SetPointer(x) } From d060863557ecd0b3269ca81c8877f4054142b010 Mon Sep 17 00:00:00 2001 From: Bob Put Date: Sun, 24 May 2026 16:07:18 -0400 Subject: [PATCH 23/38] cmd/compile: avoid temporaries for float division call args mayCall treats division as potentially panicking, forcing it into a temporary before marshaling call arguments. That is necessary for integer division, but ordinary float division does not panic or call unless the target uses soft-float lowering. Handle float division like the other soft-float-sensitive arithmetic ops so hard-float targets can pass it directly as a call argument. Fixes #28698. Change-Id: I6b2377176255aef1ae31be16a578641f75e71f75 Reviewed-on: https://go-review.googlesource.com/c/go/+/782461 Reviewed-by: Russ Cox Reviewed-by: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Keith Randall --- src/cmd/compile/internal/walk/walk.go | 8 +++++++- src/cmd/compile/testdata/script/issue28698.txt | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/cmd/compile/testdata/script/issue28698.txt diff --git a/src/cmd/compile/internal/walk/walk.go b/src/cmd/compile/internal/walk/walk.go index 259649019a414b..68e8545cd8d690 100644 --- a/src/cmd/compile/internal/walk/walk.go +++ b/src/cmd/compile/internal/walk/walk.go @@ -337,11 +337,17 @@ func mayCall(n ir.Node) bool { return true case ir.OINDEX, ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR, ir.OSLICESTR, - ir.ODEREF, ir.ODOTPTR, ir.ODOTTYPE, ir.ODYNAMICDOTTYPE, ir.ODIV, ir.OMOD, + ir.ODEREF, ir.ODOTPTR, ir.ODOTTYPE, ir.ODYNAMICDOTTYPE, ir.OMOD, ir.OSLICE2ARR, ir.OSLICE2ARRPTR: // These ops might panic, make sure they are done // before we start marshaling args for a call. See issue 16760. return true + case ir.ODIV: + n := n.(*ir.BinaryExpr) + if types.IsFloat[n.X.Type().Kind()] { + return ssagen.Arch.SoftFloat + } + return true case ir.OANDAND, ir.OOROR: n := n.(*ir.LogicalExpr) diff --git a/src/cmd/compile/testdata/script/issue28698.txt b/src/cmd/compile/testdata/script/issue28698.txt new file mode 100644 index 00000000000000..87b39bd0c1e71b --- /dev/null +++ b/src/cmd/compile/testdata/script/issue28698.txt @@ -0,0 +1,15 @@ +# Float division does not require a temporary when preparing call arguments. + +go tool compile -W p.go +stdout 'DIV float64' +! stdout 'autotmp_.*float64' + +-- p.go -- +package p + +//go:noinline +func sink(i int, f float64) {} + +func div(i int, x, y float64) { + sink(i, x/y) +} From 59468eb95730ad79db4e8a4911b911d857508b39 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Wed, 1 Jul 2026 18:53:07 +0800 Subject: [PATCH 24/38] html/template: test JavaScript escaping of cyclic values CL 248358 changed encoding/json to detect cyclic maps and slices and return an error, so jsValEscaper no longer risks unbounded recursion for these values. Add regression tests verifying that cyclic values produce a safely commented error followed by null, and remove the stale TODO. Change-Id: I83b6171b1d043c43d54890bd40983e2aef01ff4b Reviewed-on: https://go-review.googlesource.com/c/go/+/796121 Reviewed-by: Neal Patel Reviewed-by: Neal Patel Reviewed-by: Roland Shoemaker Reviewed-by: Russ Cox LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/html/template/js.go | 2 -- src/html/template/js_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/html/template/js.go b/src/html/template/js.go index e2db30f966ed7d..9ed458db6d6ec8 100644 --- a/src/html/template/js.go +++ b/src/html/template/js.go @@ -170,8 +170,6 @@ func jsValEscaper(args ...any) string { } a = fmt.Sprint(args...) } - // TODO: detect cycles before calling Marshal which loops infinitely on - // cyclic data. This may be an unacceptable DoS risk. b, err := json.Marshal(a) if err != nil { // While the standard JSON marshaler does not include user controlled diff --git a/src/html/template/js_test.go b/src/html/template/js_test.go index 015d97e6b50119..270e4ea36593f1 100644 --- a/src/html/template/js_test.go +++ b/src/html/template/js_test.go @@ -180,6 +180,31 @@ func TestJSValEscaper(t *testing.T) { } } +func TestJSValEscaperCycles(t *testing.T) { + mapCycle := map[string]any{} + mapCycle["self"] = mapCycle + sliceCycle := []any{nil} + sliceCycle[0] = sliceCycle + + for _, test := range []struct { + name string + value any + }{ + {"map", mapCycle}, + {"slice", sliceCycle}, + } { + t.Run(test.name, func(t *testing.T) { + got := jsValEscaper(test.value) + if !strings.Contains(strings.ToLower(got), "cycle") { + t.Errorf("got %q, want cycle error", got) + } + if want := "*/null "; !strings.HasSuffix(got, want) { + t.Errorf("got %q, want %q", got, want) + } + }) + } +} + func TestJSStrEscaper(t *testing.T) { tests := []struct { x any From e75c0ec6b3ec1a78240f2de00fec260edd0b6269 Mon Sep 17 00:00:00 2001 From: Sean Liao Date: Fri, 24 Oct 2025 17:30:00 +0100 Subject: [PATCH 25/38] crypto: clarify allowed overlap for AEAD Fixes #75968 Change-Id: I3ce84d01d0db6560ceb042915ec8fa11aa871a34 Reviewed-on: https://go-review.googlesource.com/c/go/+/714620 Auto-Submit: Sean Liao Reviewed-by: Eric Grosse Reviewed-by: Roland Shoemaker Reviewed-by: Russ Cox Reviewed-by: Filippo Valsorda LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/crypto/cipher/cipher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/cipher/cipher.go b/src/crypto/cipher/cipher.go index 4d631991ee1104..4bba78127d11f9 100644 --- a/src/crypto/cipher/cipher.go +++ b/src/crypto/cipher/cipher.go @@ -79,7 +79,7 @@ type AEAD interface { // // To reuse plaintext's storage for the encrypted output, use plaintext[:0] // as dst. Otherwise, the remaining capacity of dst must not overlap plaintext. - // dst and additionalData may not overlap. + // The remaining capacity of dst must not overlap additionalData. Seal(dst, nonce, plaintext, additionalData []byte) []byte // Open decrypts and authenticates ciphertext, authenticates the @@ -90,7 +90,7 @@ type AEAD interface { // // To reuse ciphertext's storage for the decrypted output, use ciphertext[:0] // as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext. - // dst and additionalData may not overlap. + // The remaining capacity of dst and additionalData may not overlap. // // Even if the function fails, the contents of dst, up to its capacity, // may be overwritten. From 86eb9afa8c7dbab5180c618d63c3e5fc09bc13dc Mon Sep 17 00:00:00 2001 From: Jake Bailey Date: Mon, 8 Jun 2026 16:02:44 -0700 Subject: [PATCH 26/38] cmd/compile: optimize abi.Type.TFlag loads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the rttype fold in (*ssa.Func).isFixedLoad / rewriteFixedLoad to also handle abi.Type.TFlag, so that loads of *(*abi.Type)(p).TFlag are constant-folded against the static type. Store the abi.TFlag value as a cached field on types.Type, computed on first use by (*Type).TFlag with no side effects, so the parallel SSA backend can invoke it safely. TFlagGCMaskOnDemand is derived directly from PtrDataSize without calling dgcsym, and the new hasUncommon predicate covers TFlagUncommon without generating method wrappers. hasUncommon requires typecheck.CalcMethods to have run on the receiver base type and asserts a new (*Type).MethodsComputed bit, set by CalcMethods, to catch missing preparation. compilecmp HEAD^ -> HEAD HEAD^ (920fc8d5f3be): cmd/compile/internal/types: store SIMD flags in Type.flags bitset HEAD (9053d82f4827): cmd/compile: optimize abi.Type.TFlag loads file before after Δ % asm 7220974 7220918 -56 -0.001% cgo 6199238 6198918 -320 -0.005% compile 35848643 35846357 -2286 -0.006% cover 7907279 7895727 -11552 -0.146% fix 12695300 12695124 -176 -0.001% link 10090470 10088702 -1768 -0.018% preprofile 3380078 3379006 -1072 -0.032% vet 12295155 12288275 -6880 -0.056% total 95637137 95613027 -24110 -0.025% cmd/cgo/internal/test cmd/cgo/internal/test.test45451 319 -> 255 (-20.06%) cmd/compile/internal/base cmd/compile/internal/base.registerFlags 3120 -> 3088 (-1.03%) cmd/compile/internal/reflectdata cmd/compile/internal/reflectdata.dcommontype 2079 -> 1886 (-9.28%) cmd/compile/internal/ssa cmd/compile/internal/ssa.isFixedLoad 702 -> 712 (+1.42%) cmd/compile/internal/ssa.rewriteFixedLoad 2757 -> 2853 (+3.48%) cmd/compile/internal/typecheck cmd/compile/internal/typecheck.InitUniverse 3916 -> 3965 (+1.25%) cmd/compile/internal/types inserted cmd/compile/internal/types.(*Type).TFlag inserted cmd/compile/internal/types.(*Type).TFlag.deferwrap1 inserted cmd/compile/internal/types.computeTFlag inserted cmd/compile/internal/types.hasUncommon cmd/gofmt main.init 1100 -> 1068 (-2.91%) main.rewriteFile 716 -> 696 (-2.79%) main.rewriteFile.func1 538 -> 517 (-3.90%) cmd/vendor/github.com/google/pprof/internal/driver cmd/vendor/github.com/google/pprof/internal/driver.(*config).fieldPtr 133 -> 113 (-15.04%) cmd/vendor/golang.org/x/telemetry/internal/crashmonitor cmd/vendor/golang.org/x/telemetry/internal/crashmonitor.sentinel 72 -> 51 (-29.17%) cmd/vendor/golang.org/x/tools/go/ast/astutil cmd/vendor/golang.org/x/tools/go/ast/astutil.updateBasicLitPos 317 -> 302 (-4.73%) cmd/vendor/golang.org/x/tools/internal/refactor/inline cmd/vendor/golang.org/x/tools/internal/refactor/inline.clearPositions.func1 525 -> 505 (-3.81%) cmd/vendor/golang.org/x/tools/internal/typesinternal cmd/vendor/golang.org/x/tools/internal/typesinternal.ErrorCodeStartEnd 631 -> 599 (-5.07%) cmd/vendor/golang.org/x/tools/internal/typesinternal.SetUsesCgo 185 -> 165 (-10.81%) encoding/asn1 encoding/asn1.parseField 9101 -> 9037 (-0.70%) encoding/gob encoding/gob.(*Decoder).recvType 340 -> 325 (-4.41%) encoding/gob.(*Encoder).sendActualType 1117 -> 1103 (-1.25%) encoding/json/v2 encoding/json/v2.makeInterfaceArshaler.func1 2840 -> 2760 (-2.82%) encoding/json/v2.marshalArrayAny 1463 -> 1431 (-2.19%) encoding/json/v2.marshalObjectAny 2490 -> 2416 (-2.97%) encoding/json/v2.unmarshalInlinedFallbackNext 2813 -> 2768 (-1.60%) encoding/xml encoding/xml.(*Decoder).unmarshal 8725 -> 8597 (-1.47%) encoding/xml.(*Decoder).unmarshalAttr 1838 -> 1816 (-1.20%) encoding/xml.(*printer).marshalSimple 1064 -> 1034 (-2.82%) fmt fmt.(*pp).fmtBytes 1716 -> 1690 (-1.52%) internal/buildcfg internal/buildcfg.ParseGOEXPERIMENT 2040 -> 2021 (-0.93%) internal/buildcfg.expList 1350 -> 1306 (-3.26%) net/http/internal/http2 net/http/internal/http2.StreamError.As 1208 -> 1112 (-7.95%) reflect reflect.Value.Seq.func1.1 229 -> 178 (-22.27%) reflect.Value.Seq.func2 167 -> 134 (-19.76%) reflect.Value.Seq.func3 208 -> 178 (-14.42%) reflect.Value.Seq.func4 255 -> 221 (-13.33%) reflect.Value.Seq2.func1.1 261 -> 217 (-16.86%) reflect.Value.Seq2.func2 335 -> 293 (-12.54%) reflect.Value.Seq2.func3 284 -> 245 (-13.73%) reflect.Value.Seq2.func4 336 -> 284 (-15.48%) runtime runtime.stkobjinit 202 -> 165 (-18.32%) testing testing.(*F).Fuzz.func1.1 856 -> 824 (-3.74%) text/template text/template.(*state).evalArg 2252 -> 2228 (-1.07%) text/template.(*state).evalCall 4188 -> 4156 (-0.76%) text/template.(*state).evalCommand 1301 -> 1266 (-2.69%) text/template.(*state).evalEmptyInterface 988 -> 955 (-3.34%) text/template.(*state).evalField 3211 -> 3115 (-2.99%) text/template.(*state).idealConstant 504 -> 445 (-11.71%) text/template.(*state).validateType 1655 -> 1640 (-0.91%) text/template.(*state).walkRange 3269 -> 3205 (-1.96%) text/template.init 459 -> 431 (-6.10%) Change-Id: I5ffefdc4e09586ab4f154e560b99b2d9cd30c2ea Reviewed-on: https://go-review.googlesource.com/c/go/+/701299 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Russ Cox --- .../compile/internal/reflectdata/reflect.go | 31 +------ src/cmd/compile/internal/ssa/rewrite.go | 6 +- src/cmd/compile/internal/typecheck/subr.go | 3 +- src/cmd/compile/internal/types/tflag.go | 82 +++++++++++++++++++ src/cmd/compile/internal/types/type.go | 5 ++ 5 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 src/cmd/compile/internal/types/tflag.go diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index 1fca65469b17b4..bf2957bad80c89 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -15,7 +15,6 @@ import ( "cmd/compile/internal/base" "cmd/compile/internal/bitvec" - "cmd/compile/internal/compare" "cmd/compile/internal/ir" "cmd/compile/internal/objw" "cmd/compile/internal/rttype" @@ -57,7 +56,7 @@ type typeSig struct { func commonSize() int { return int(rttype.Type.Size()) } // Sizeof(runtime._type{}) func uncommonSize(t *types.Type) int { // Sizeof(runtime.uncommontype{}) - if t.Sym() == nil && len(methods(t)) == 0 { + if t.TFlag()&abi.TFlagUncommon == 0 { return 0 } return int(rttype.UncommonType.Size()) @@ -464,20 +463,6 @@ func dcommontype(c rttype.Cursor, t *types.Type) { c.Field("PtrBytes").WriteUintptr(uint64(ptrdata)) c.Field("Hash").WriteUint32(types.TypeHash(t)) - var tflag abi.TFlag - if uncommonSize(t) != 0 { - tflag |= abi.TFlagUncommon - } - if t.Sym() != nil && t.Sym().Name != "" { - tflag |= abi.TFlagNamed - } - if compare.IsRegularMemory(t) { - tflag |= abi.TFlagRegularMemory - } - if onDemand { - tflag |= abi.TFlagGCMaskOnDemand - } - exported := false p := t.NameString() // If we're writing out type T, @@ -485,9 +470,8 @@ func dcommontype(c rttype.Cursor, t *types.Type) { // Use the string "*T"[1:] for "T", so that the two // share storage. This is a cheap way to reduce the // amount of space taken up by reflect strings. - if !strings.HasPrefix(p, "*") { + if t.TFlag()&abi.TFlagExtraStar != 0 { p = "*" + p - tflag |= abi.TFlagExtraStar if t.Sym() != nil { exported = types.IsExported(t.Sym().Name) } @@ -496,15 +480,8 @@ func dcommontype(c rttype.Cursor, t *types.Type) { exported = types.IsExported(t.Elem().Sym().Name) } } - if types.IsDirectIface(t) { - tflag |= abi.TFlagDirectIface - } - if tflag != abi.TFlag(uint8(tflag)) { - // this should optimize away completely - panic("Unexpected change in size of abi.TFlag") - } - c.Field("TFlag").WriteUint8(uint8(tflag)) + c.Field("TFlag").WriteUint8(uint8(t.TFlag())) // runtime (and common sense) expects alignment to be a power of two. i := int(uint8(t.Alignment())) @@ -1254,7 +1231,7 @@ func GCSym(t *types.Type, onDemandAllowed bool) (lsym *obj.LSym, ptrdata int64) // When write is true, it writes the symbol data. func dgcsym(t *types.Type, write, onDemandAllowed bool) (lsym *obj.LSym, onDemand bool, ptrdata int64) { ptrdata = types.PtrDataSize(t) - if !onDemandAllowed || ptrdata/int64(types.PtrSize) <= abi.MaxPtrmaskBytes*8 { + if !onDemandAllowed || t.TFlag()&abi.TFlagGCMaskOnDemand == 0 { lsym = dgcptrmask(t, write) return } diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index 17448799db3792..e9091745487615 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -2119,7 +2119,7 @@ func isFixedLoad(v *Value, sym Sym, off int64) bool { for _, f := range rttype.Type.Fields() { if f.Offset == off && copyCompatibleType(v.Type, f.Type) { switch f.Sym.Name { - case "Size_", "PtrBytes", "Hash", "Kind_", "GCData": + case "Size_", "PtrBytes", "Hash", "Kind_", "GCData", "TFlag": return true default: // fmt.Println("unknown field", f.Sym.Name) @@ -2195,6 +2195,10 @@ func rewriteFixedLoad(v *Value, sym Sym, sb *Value, off int64) *Value { v.reset(OpConst32) v.AuxInt = int64(int32(types.TypeHash(t))) return v + case "TFlag": + v.reset(OpConst8) + v.AuxInt = int64(t.TFlag()) + return v case "Kind_": v.reset(OpConst8) v.AuxInt = int64(int8(reflectdata.ABIKindOfType(t))) diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go index 27943f2902c85c..b8b8dc2dafe051 100644 --- a/src/cmd/compile/internal/typecheck/subr.go +++ b/src/cmd/compile/internal/typecheck/subr.go @@ -96,9 +96,10 @@ func AddImplicitDots(n *ir.SelectorExpr) *ir.SelectorExpr { // CalcMethods calculates all the methods (including embedding) of a non-interface // type t. func CalcMethods(t *types.Type) { - if t == nil || len(t.AllMethods()) != 0 { + if t == nil || t.MethodsComputed() { return } + defer t.SetMethodsComputed(true) // mark top-level method symbols // so that expand1 doesn't consider them. diff --git a/src/cmd/compile/internal/types/tflag.go b/src/cmd/compile/internal/types/tflag.go new file mode 100644 index 00000000000000..15b53ebe3625db --- /dev/null +++ b/src/cmd/compile/internal/types/tflag.go @@ -0,0 +1,82 @@ +// Copyright 2026 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 types + +import ( + "cmd/compile/internal/base" + "internal/abi" + "strings" + "sync" +) + +// tflagComputed is set in the high bit of Type.tflag once the low +// bits hold a valid abi.TFlag. abi.TFlag itself uses only the low 6. +const tflagComputed uint8 = 1 << 7 + +var tflagMu sync.Mutex + +// TFlag returns the abi.TFlag value for t's runtime type. Callers +// must have run typecheck.CalcMethods on ReceiverBaseType(t). +func (t *Type) TFlag() abi.TFlag { + tflagMu.Lock() + defer tflagMu.Unlock() + if t.tflag&tflagComputed != 0 { + return abi.TFlag(t.tflag &^ tflagComputed) + } + tflag := computeTFlag(t) + t.tflag = uint8(tflag) | tflagComputed + return tflag +} + +func computeTFlag(t *Type) abi.TFlag { + var tflag abi.TFlag + if hasUncommon(t) { + tflag |= abi.TFlagUncommon + } + if t.Sym() != nil && t.Sym().Name != "" { + tflag |= abi.TFlagNamed + } + if t.alg == AMEM { + tflag |= abi.TFlagRegularMemory + } + if PtrDataSize(t)/int64(PtrSize) > abi.MaxPtrmaskBytes*8 { + tflag |= abi.TFlagGCMaskOnDemand + } + if !strings.HasPrefix(t.NameString(), "*") { + tflag |= abi.TFlagExtraStar + } + if IsDirectIface(t) { + tflag |= abi.TFlagDirectIface + } + return tflag +} + +// hasUncommon reports whether t needs an abi.UncommonType. +// See TFlag for the precondition. +func hasUncommon(t *Type) bool { + if t.Sym() != nil { + return true + } + if t.HasShape() { + return false + } + mt := ReceiverBaseType(t) + if mt == nil { + return false + } + if !mt.MethodsComputed() { + base.Fatalf("hasUncommon: methods not computed on %v", mt) + } + for _, f := range mt.AllMethods() { + if f.Nointerface() { + continue + } + if !IsMethodApplicable(t, f) { + continue + } + return true + } + return false +} diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go index f20932ae7ecafe..6b13b3b0f6799b 100644 --- a/src/cmd/compile/internal/types/type.go +++ b/src/cmd/compile/internal/types/type.go @@ -203,6 +203,8 @@ type Type struct { flags bitset16 alg AlgKind // valid if Align > 0 + tflag uint8 + // size of prefix of object that contains all pointers. valid if Align > 0. // Note that for pointers, this is always PtrSize even if the element type // is NotInHeap. See size.go:PtrDataSize for details. @@ -234,6 +236,7 @@ const ( typeIsFullyInstantiated typeIsSIMDTag // type is the SIMD marker type typeIsSIMD // type contains the SIMD marker type + typeMethodsComputed ) func (t *Type) NotInHeap() bool { return t.flags&typeNotInHeap != 0 } @@ -243,12 +246,14 @@ func (t *Type) Recur() bool { return t.flags&typeRecur != 0 } func (t *Type) IsShape() bool { return t.flags&typeIsShape != 0 } func (t *Type) HasShape() bool { return t.flags&typeHasShape != 0 } func (t *Type) IsFullyInstantiated() bool { return t.flags&typeIsFullyInstantiated != 0 } +func (t *Type) MethodsComputed() bool { return t.flags&typeMethodsComputed != 0 } func (t *Type) SetNotInHeap(b bool) { t.flags.set(typeNotInHeap, b) } func (t *Type) SetNoalg(b bool) { t.flags.set(typeNoalg, b) } func (t *Type) SetDeferwidth(b bool) { t.flags.set(typeDeferwidth, b) } func (t *Type) SetRecur(b bool) { t.flags.set(typeRecur, b) } func (t *Type) SetIsFullyInstantiated(b bool) { t.flags.set(typeIsFullyInstantiated, b) } +func (t *Type) SetMethodsComputed(b bool) { t.flags.set(typeMethodsComputed, b) } // Should always do SetHasShape(true) when doing SetIsShape(true). func (t *Type) SetIsShape(b bool) { t.flags.set(typeIsShape, b) } From d3c517908df6a9b239fe8377ea71e042036e3e60 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Fri, 12 Jun 2026 20:55:18 +0100 Subject: [PATCH 27/38] cmd/compile: avoid heap-allocating LocalSlot in addNamedValue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit addNamedValue takes &loc of a local and stores it in the long-lived f.Names and f.CanonicalLocalSlots, causing loc to escape on every call. Allocate only on the first-sighting path, mirroring localSlotAddr. Passes toolstash -cmp. compilebench -alloc, allocs/op, linux/amd64 (-count 20): │ /tmp/old.txt │ /tmp/new.txt │ │ allocs/op │ allocs/op vs base │ Template 820.9k ± 0% 816.2k ± 0% -0.58% (p=0.000 n=20) Unicode 574.2k ± 0% 573.7k ± 0% -0.07% (p=0.000 n=20) GoTypes 5.013M ± 0% 4.971M ± 0% -0.83% (p=0.000 n=20) Compiler 702.2k ± 0% 698.6k ± 0% -0.51% (p=0.000 n=20) SSA 47.02M ± 0% 46.50M ± 0% -1.12% (p=0.000 n=20) Flate 823.9k ± 0% 813.8k ± 0% -1.23% (p=0.000 n=20) GoParser 791.3k ± 0% 788.1k ± 0% -0.41% (p=0.000 n=20) Reflect 2.318M ± 0% 2.302M ± 0% -0.69% (p=0.000 n=20) Tar 937.7k ± 0% 930.1k ± 0% -0.81% (p=0.000 n=20) XML 1.099M ± 0% 1.090M ± 0% -0.77% (p=0.000 n=20) LinkCompiler 729.0k ± 0% 729.1k ± 0% ~ (p=0.101 n=20) ExternalLinkCompiler 731.6k ± 0% 731.6k ± 0% ~ (p=0.449 n=20) LinkWithoutDebugCompiler 143.3k ± 0% 143.3k ± 0% ~ (p=0.358 n=20) geomean 1.187M 1.181M -0.54% Fixes #79990 Change-Id: Ia0dfff98d4135912f4b008ea44a6e732f72c0d8a Reviewed-on: https://go-review.googlesource.com/c/go/+/790300 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Podtserkovskii Reviewed-by: Cherry Mui Reviewed-by: Keith Randall Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssagen/ssa.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 9a3d08831a0c57..809c8b86e53bbb 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -6688,8 +6688,10 @@ func (s *state) addNamedValue(n *ir.Name, v *ssa.Value) { loc := ssa.LocalSlot{N: n, Type: n.Type(), Off: 0} values, ok := s.f.NamedValues[loc] if !ok { - s.f.Names = append(s.f.Names, &loc) - s.f.CanonicalLocalSlots[loc] = &loc + locp := new(ssa.LocalSlot) + *locp = loc + s.f.Names = append(s.f.Names, locp) + s.f.CanonicalLocalSlots[loc] = locp } s.f.NamedValues[loc] = append(values, v) } From 1e28d9f5801c526403651cab6fe2b4912cd0ce8c Mon Sep 17 00:00:00 2001 From: Julian Soreavis Date: Sat, 11 Jul 2026 22:15:07 +0000 Subject: [PATCH 28/38] encoding/base64, encoding/base32: panic when encoded length overflows int EncodedLen computes the encoded length with int arithmetic that can wrap for very large n, returning a negative or wrapped value. Callers that size an allocation from the result (EncodeToString, AppendEncode) then panic with no indication of the cause, or allocate an undersized buffer. CL 510635 and CL 512200 raised the overflow threshold of the unpadded branches but did not eliminate it, and the padded branches were unchanged: on 32-bit platforms base64.StdEncoding.EncodedLen(1610612736) still returns -2147483648. Make EncodedLen panic when the encoded length does not fit in an int, document the panic, and document the largest n that is guaranteed not to panic. DecodedLen needs no change: its result is at most n and cannot overflow. Fixes #20235 Change-Id: I963a55062a790017b42f49fc3a9178fc3bb024ef GitHub-Last-Rev: 2a7abbb35f7f5e3a2b74c379c29464e01e2206f5 GitHub-Pull-Request: golang/go#80373 Reviewed-on: https://go-review.googlesource.com/c/go/+/799800 Auto-Submit: Ian Lance Taylor Reviewed-by: Michael Pratt LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Russ Cox Reviewed-by: Ian Lance Taylor --- .../99-minor/encoding/base32/20235.md | 2 ++ .../99-minor/encoding/base64/20235.md | 2 ++ src/encoding/base32/base32.go | 9 +++++++++ src/encoding/base32/base32_test.go | 20 +++++++++++++++++++ src/encoding/base64/base64.go | 9 +++++++++ src/encoding/base64/base64_test.go | 20 +++++++++++++++++++ 6 files changed, 62 insertions(+) create mode 100644 doc/next/6-stdlib/99-minor/encoding/base32/20235.md create mode 100644 doc/next/6-stdlib/99-minor/encoding/base64/20235.md diff --git a/doc/next/6-stdlib/99-minor/encoding/base32/20235.md b/doc/next/6-stdlib/99-minor/encoding/base32/20235.md new file mode 100644 index 00000000000000..2408795607f5d8 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/encoding/base32/20235.md @@ -0,0 +1,2 @@ +[Encoding.EncodedLen] now panics if the encoded length overflows int, +rather than returning a negative number. diff --git a/doc/next/6-stdlib/99-minor/encoding/base64/20235.md b/doc/next/6-stdlib/99-minor/encoding/base64/20235.md new file mode 100644 index 00000000000000..2408795607f5d8 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/encoding/base64/20235.md @@ -0,0 +1,2 @@ +[Encoding.EncodedLen] now panics if the encoded length overflows int, +rather than returning a negative number. diff --git a/src/encoding/base32/base32.go b/src/encoding/base32/base32.go index 7a3221aea2497c..acc729802aab6d 100644 --- a/src/encoding/base32/base32.go +++ b/src/encoding/base32/base32.go @@ -7,6 +7,7 @@ package base32 import ( "io" + "math" "slices" "strconv" ) @@ -279,10 +280,18 @@ func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser { // EncodedLen returns the length in bytes of the base32 encoding // of an input buffer of length n. +// It panics if the encoded length overflows int, +// which can happen only if n > [math.MaxInt]/8*5. func (enc *Encoding) EncodedLen(n int) int { if enc.padChar == NoPadding { + if n > math.MaxInt/8*5+4 { + panic("encoded length overflows int") + } return n/5*8 + (n%5*8+4)/5 } + if n > math.MaxInt/8*5 { + panic("encoded length overflows int") + } return (n + 4) / 5 * 8 } diff --git a/src/encoding/base32/base32_test.go b/src/encoding/base32/base32_test.go index 6f8d564def3c86..c115984f4e2b3e 100644 --- a/src/encoding/base32/base32_test.go +++ b/src/encoding/base32/base32_test.go @@ -765,6 +765,7 @@ func TestEncodedLen(t *testing.T) { tests = append(tests, test{rawStdEncoding, (math.MaxInt-4)/8 + 1, 1844674407370955162}) tests = append(tests, test{rawStdEncoding, math.MaxInt/8*5 + 4, math.MaxInt}) } + tests = append(tests, test{StdEncoding, math.MaxInt / 8 * 5, math.MaxInt - 7}) for _, tt := range tests { if got := tt.enc.EncodedLen(tt.n); int64(got) != tt.want { t.Errorf("EncodedLen(%d): got %d, want %d", tt.n, got, tt.want) @@ -772,6 +773,25 @@ func TestEncodedLen(t *testing.T) { } } +func TestEncodedLenOverflow(t *testing.T) { + for _, tt := range []struct { + enc *Encoding + n int + }{ + {StdEncoding, math.MaxInt/8*5 + 1}, + {StdEncoding.WithPadding(NoPadding), math.MaxInt/8*5 + 5}, + } { + func() { + defer func() { + if recover() == nil { + t.Errorf("EncodedLen(%d) did not panic", tt.n) + } + }() + tt.enc.EncodedLen(tt.n) + }() + } +} + func TestDecodedLen(t *testing.T) { var rawStdEncoding = StdEncoding.WithPadding(NoPadding) type test struct { diff --git a/src/encoding/base64/base64.go b/src/encoding/base64/base64.go index 32014f45bbc146..952cf0c6f859a3 100644 --- a/src/encoding/base64/base64.go +++ b/src/encoding/base64/base64.go @@ -8,6 +8,7 @@ package base64 import ( "internal/byteorder" "io" + "math" "slices" "strconv" ) @@ -282,10 +283,18 @@ func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser { // EncodedLen returns the length in bytes of the base64 encoding // of an input buffer of length n. +// It panics if the encoded length overflows int, +// which can happen only if n > [math.MaxInt]/4*3. func (enc *Encoding) EncodedLen(n int) int { if enc.padChar == NoPadding { + if n > math.MaxInt/4*3+2 { + panic("encoded length overflows int") + } return n/3*4 + (n%3*8+5)/6 // minimum # chars at 6 bits per char } + if n > math.MaxInt/4*3 { + panic("encoded length overflows int") + } return (n + 2) / 3 * 4 // minimum # 4-char quanta, 3 bytes each } diff --git a/src/encoding/base64/base64_test.go b/src/encoding/base64/base64_test.go index 7f5ebd8085de56..bc67413cc7ca85 100644 --- a/src/encoding/base64/base64_test.go +++ b/src/encoding/base64/base64_test.go @@ -305,6 +305,7 @@ func TestEncodedLen(t *testing.T) { tests = append(tests, test{RawStdEncoding, (math.MaxInt-5)/8 + 1, 1537228672809129302}) tests = append(tests, test{RawStdEncoding, math.MaxInt/4*3 + 2, math.MaxInt}) } + tests = append(tests, test{StdEncoding, math.MaxInt / 4 * 3, math.MaxInt - 3}) for _, tt := range tests { if got := tt.enc.EncodedLen(tt.n); int64(got) != tt.want { t.Errorf("EncodedLen(%d): got %d, want %d", tt.n, got, tt.want) @@ -312,6 +313,25 @@ func TestEncodedLen(t *testing.T) { } } +func TestEncodedLenOverflow(t *testing.T) { + for _, tt := range []struct { + enc *Encoding + n int + }{ + {StdEncoding, math.MaxInt/4*3 + 1}, + {RawStdEncoding, math.MaxInt/4*3 + 3}, + } { + func() { + defer func() { + if recover() == nil { + t.Errorf("EncodedLen(%d) did not panic", tt.n) + } + }() + tt.enc.EncodedLen(tt.n) + }() + } +} + func TestDecodedLen(t *testing.T) { type test struct { enc *Encoding From 10981ef1378830f88ff1efba68a98c56a1a33d6f Mon Sep 17 00:00:00 2001 From: Julian Soreavis Date: Sat, 11 Jul 2026 10:18:56 +0000 Subject: [PATCH 29/38] cmd/go: avoid a blank line before ok when discarded output lacks a newline When a passing test in package list mode writes output that does not end in a newline, and that output is discarded (the default non-verbose, non-JSON case), cmd/go printed a spurious blank line before the "ok" summary line. The newline-before-"ok" guard tested out, a snapshot of the captured output taken before buf.Reset() discards it, but wrote the newline into buf, which Reset had already emptied. Test buf itself, so no newline is added when the captured output was discarded. Fixes #79786 Change-Id: Iab32de2d8fa9db85681e2e4f01d7af3f08a0f626 GitHub-Last-Rev: 86aaa23984a33a4057e755467156234d35fb24cd GitHub-Pull-Request: golang/go#80352 Reviewed-on: https://go-review.googlesource.com/c/go/+/799182 Reviewed-by: Russ Cox LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Sean Liao Reviewed-by: Michael Pratt Auto-Submit: Sean Liao --- src/cmd/go/internal/test/test.go | 8 +++++--- src/cmd/go/testdata/script/test_fail_newline.txt | 8 ++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index 2473c243e31159..2084bc68a91217 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -1733,9 +1733,11 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action) if bytes.HasPrefix(out, tooManyFuzzTestsToFuzz[1:]) || bytes.Contains(out, tooManyFuzzTestsToFuzz) { norun = "[-fuzz matches more than one fuzz test, won't fuzz]" } - if len(out) > 0 && !bytes.HasSuffix(out, []byte("\n")) { - // Ensure that the output ends with a newline before the "ok" - // line we're about to print (https://golang.org/issue/49317). + // Ensure the output ends with a newline before the "ok" line + // (https://golang.org/issue/49317). Check buf, which holds the + // output still to be printed; out may include output that was + // discarded above (https://go.dev/issue/79786). + if buf.Len() > 0 && !bytes.HasSuffix(buf.Bytes(), []byte("\n")) { cmd.Stdout.Write([]byte("\n")) } fmt.Fprintf(cmd.Stdout, "ok \t%s\t%s%s%s\n", a.Package.ImportPath, t, coveragePercentage(out), norun) diff --git a/src/cmd/go/testdata/script/test_fail_newline.txt b/src/cmd/go/testdata/script/test_fail_newline.txt index 43cee565a19c1e..8de503b5d6911a 100644 --- a/src/cmd/go/testdata/script/test_fail_newline.txt +++ b/src/cmd/go/testdata/script/test_fail_newline.txt @@ -25,6 +25,14 @@ go test -v . stdout '^skipping\n' stdout '^ok\s+example/skip' +# In package list mode without -v, a passing test's output is discarded, so the +# 'ok' line must not be preceded by a spurious blank line, even when the +# discarded output ended without a newline (https://go.dev/issue/79786). +go test -count=1 . +! stderr . +! stdout '^\n' +stdout '^ok\s+example/skip' + # If the output is streamed and the test passes, we can't tell whether it ended # in a partial line, and don't want to emit any extra output in the # overwhelmingly common case that it did not. From 940c49c26ace3f5e87547ff8cf3e67e69b66e0d8 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 11 Jun 2026 15:02:25 +0200 Subject: [PATCH 30/38] debug/elf: use bytes.IndexByte in getString Use bytes.IndexByte insted of open-coding it in getString. Change-Id: I306074b092c879cc1f20cae2acb236d2e9775b59 Reviewed-on: https://go-review.googlesource.com/c/go/+/789540 Reviewed-by: Ian Lance Taylor Reviewed-by: Russ Cox Auto-Submit: Tobias Klauser LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Pratt Reviewed-by: Florian Lehner --- src/debug/elf/file.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/debug/elf/file.go b/src/debug/elf/file.go index a9c13212284ebe..f979c97c2f0d65 100644 --- a/src/debug/elf/file.go +++ b/src/debug/elf/file.go @@ -763,12 +763,11 @@ func getString(section []byte, start int) (string, bool) { return "", false } - for end := start; end < len(section); end++ { - if section[end] == 0 { - return string(section[start:end]), true - } + end := bytes.IndexByte(section[start:], 0) + if end < 0 { + return "", false } - return "", false + return string(section[start : start+end]), true } // Section returns a section with the given name, or nil if no such From f28114cca04cf1b65924f9f64ebf0f8de5e72747 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Wed, 15 Apr 2026 16:31:52 +0800 Subject: [PATCH 31/38] bytes: add example for Buffer.Peek Change-Id: I173f165dea17d21cb344a208bce9b70e6dd6a35a Reviewed-on: https://go-review.googlesource.com/c/go/+/767160 Reviewed-by: Russ Cox Reviewed-by: Sean Liao Reviewed-by: Michael Pratt LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Sean Liao --- src/bytes/example_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/bytes/example_test.go b/src/bytes/example_test.go index c489b950e59a91..ab2538c95355ad 100644 --- a/src/bytes/example_test.go +++ b/src/bytes/example_test.go @@ -123,6 +123,35 @@ func ExampleBuffer_ReadByte() { // bcde } +func ExampleBuffer_Peek() { + var b bytes.Buffer + b.WriteString("Hello, Gophers!") + + data, err := b.Peek(5) + if err != nil { + panic(err) + } + fmt.Printf("First peek: %s\n", data) + + fmt.Printf("Buffer: %s\n", b.String()) + + // Advance past "Hello, ". + if _, err := b.Read(make([]byte, 7)); err != nil { + panic(err) + } + + data, err = b.Peek(7) + if err != nil { + panic(err) + } + fmt.Printf("Second peek: %s\n", data) + + // Output: + // First peek: Hello + // Buffer: Hello, Gophers! + // Second peek: Gophers +} + func ExampleClone() { b := []byte("abc") clone := bytes.Clone(b) From dd9e8d9bbd27ad6f7614959965e213b680d8ef24 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Tue, 14 Jul 2026 18:12:29 +0800 Subject: [PATCH 32/38] reflect: add example for TypeAssert Change-Id: I619224cb379d917a2766dfce5ada9f9bf4617d81 Reviewed-on: https://go-review.googlesource.com/c/go/+/800500 Auto-Submit: Sean Liao Reviewed-by: Sean Liao Reviewed-by: Michael Pratt Reviewed-by: Russ Cox LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/reflect/example_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/reflect/example_test.go b/src/reflect/example_test.go index 1df524758ab66f..a37ce462ea0550 100644 --- a/src/reflect/example_test.go +++ b/src/reflect/example_test.go @@ -260,3 +260,29 @@ func ExampleValue_Methods() { // UnreadRune // WriteTo } + +func ExampleTypeAssert() { + reader := bytes.NewReader([]byte("Hello, Gophers!")) + v := reflect.ValueOf(reader) + + if br, ok := reflect.TypeAssert[*bytes.Reader](v); ok { + fmt.Printf("Remaining bytes: %d\n", br.Len()) + } + + if _, ok := reflect.TypeAssert[*os.File](v); !ok { + fmt.Println("Cannot assert to *os.File") + } + + if r, ok := reflect.TypeAssert[io.Reader](v); ok { + data, err := io.ReadAll(r) + if err != nil { + panic(err) + } + fmt.Printf("Read through io.Reader: %s\n", data) + } + + // Output: + // Remaining bytes: 15 + // Cannot assert to *os.File + // Read through io.Reader: Hello, Gophers! +} From 6bb1bf78909098767491a0b42443962debce2723 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Sat, 13 Jun 2026 20:57:18 +0800 Subject: [PATCH 33/38] bytes, strings: add example for CutLast Change-Id: I7eb441028af0f3e579c87f63458029d8125c4046 Reviewed-on: https://go-review.googlesource.com/c/go/+/790102 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Sean Liao Reviewed-by: Michael Pratt Auto-Submit: Sean Liao Reviewed-by: Russ Cox --- src/bytes/example_test.go | 12 ++++++++++++ src/strings/example_test.go | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/bytes/example_test.go b/src/bytes/example_test.go index ab2538c95355ad..253ea5bcddca0c 100644 --- a/src/bytes/example_test.go +++ b/src/bytes/example_test.go @@ -273,6 +273,18 @@ func ExampleCut() { // Cut("Gopher", "Badger") = "Gopher", "", false } +func ExampleCutLast() { + show := func(s, sep string) { + before, after, found := bytes.CutLast([]byte(s), []byte(sep)) + fmt.Printf("CutLast(%q, %q) = %q, %q, %v\n", s, sep, before, after, found) + } + show("root/user/docs", "/") + show("Gopher", "/") + // Output: + // CutLast("root/user/docs", "/") = "root/user", "docs", true + // CutLast("Gopher", "/") = "Gopher", "", false +} + func ExampleCutPrefix() { show := func(s, prefix string) { after, found := bytes.CutPrefix([]byte(s), []byte(prefix)) diff --git a/src/strings/example_test.go b/src/strings/example_test.go index 72adbae5f26a31..1865ca9d50c57c 100644 --- a/src/strings/example_test.go +++ b/src/strings/example_test.go @@ -115,6 +115,18 @@ func ExampleCut() { // Cut("Gopher", "Badger") = "Gopher", "", false } +func ExampleCutLast() { + show := func(s, sep string) { + before, after, found := strings.CutLast(s, sep) + fmt.Printf("CutLast(%q, %q) = %q, %q, %v\n", s, sep, before, after, found) + } + show("root/user/docs", "/") + show("Gopher", "/") + // Output: + // CutLast("root/user/docs", "/") = "root/user", "docs", true + // CutLast("Gopher", "/") = "Gopher", "", false +} + func ExampleCutPrefix() { show := func(s, prefix string) { after, found := strings.CutPrefix(s, prefix) From 9b1a726059b198487470f257913fea6bcedaf469 Mon Sep 17 00:00:00 2001 From: Julian Soreavis Date: Sat, 11 Jul 2026 18:32:52 +0000 Subject: [PATCH 34/38] text/template, html/template: correct the documentation for IsTrue The IsTrue comment says a value is 'true' when it is not the zero value of its type. That contradicts the implementation for structs, which are always true, and is wrong for a negative floating-point zero, which is untruthy without being 'the' zero of its type. Describe the rules the implementation actually applies instead. The comment is duplicated in html/template; update both copies. Fixes #28394 Change-Id: Idbbabec39ce46bddbbdd4877ac8cfe2b5d4a08dc GitHub-Last-Rev: 435105c83ac18f4cb64cd8cbc3cc4e00a91f476f GitHub-Pull-Request: golang/go#80338 Reviewed-on: https://go-review.googlesource.com/c/go/+/799063 Auto-Submit: Sean Liao LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Sean Liao Reviewed-by: Michael Pratt Reviewed-by: Rob Pike Reviewed-by: Russ Cox --- src/html/template/template.go | 14 +++++++++++--- src/text/template/exec.go | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/html/template/template.go b/src/html/template/template.go index 097f3dfa2d5e77..c4244fb11ef00f 100644 --- a/src/html/template/template.go +++ b/src/html/template/template.go @@ -480,9 +480,17 @@ func parseGlob(t *Template, pattern string) (*Template, error) { return parseFiles(t, readFileOS, filenames...) } -// IsTrue reports whether the value is 'true', in the sense of not the zero of its type, -// and whether the value has a meaningful truth value. This is the definition of -// truth used by if and other such actions. +// IsTrue reports whether the value is true, in the sense of being nonzero, +// nonempty, or non-nil, and whether the value has a meaningful truth value. +// This is the definition of truth used in "if" actions and elsewhere in +// templates: +// +// - A boolean value is true if it is true. +// - A numeric value is true if it is nonzero. +// - An array, map, slice, or string value is true if its length is +// greater than zero. +// - Any other value is true if it is non-nil; struct values are +// never nil, and therefore always true. func IsTrue(val any) (truth, ok bool) { return template.IsTrue(val) } diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 7a67ec6824f5e5..2b7146b94f63c4 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -314,9 +314,17 @@ func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse. } } -// IsTrue reports whether the value is 'true', in the sense of not the zero of its type, -// and whether the value has a meaningful truth value. This is the definition of -// truth used by if and other such actions. +// IsTrue reports whether the value is true, in the sense of being nonzero, +// nonempty, or non-nil, and whether the value has a meaningful truth value. +// This is the definition of truth used in "if" actions and elsewhere in +// templates: +// +// - A boolean value is true if it is true. +// - A numeric value is true if it is nonzero. +// - An array, map, slice, or string value is true if its length is +// greater than zero. +// - Any other value is true if it is non-nil; struct values are +// never nil, and therefore always true. func IsTrue(val any) (truth, ok bool) { return isTrue(reflect.ValueOf(val)) } From e7d8668a6a0c01538dda0e24c3c7bf4f027d22be Mon Sep 17 00:00:00 2001 From: Simon Li Date: Mon, 15 Jun 2026 20:14:44 +0100 Subject: [PATCH 35/38] cmd/compile: use a value slice for SSA Func.Names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Func.Names was []*LocalSlot, so every entry was a separately heap-allocated LocalSlot. Most named values are scalars that are never decomposed, yet each one still allocated a LocalSlot when it was first recorded in addNamedValue. Change Func.Names to []LocalSlot. addNamedValue now appends the slot by value and no longer eagerly fills CanonicalLocalSlots. The canonical *LocalSlot used for SplitOf chains and split deduplication is created lazily by localSlotAddr, so only slots that are actually decomposed pay for it. The names stay contiguous and the garbage collector has one fewer pointer per name to scan. This is a follow-up to CL 790300, the addNamedValue escape fix for issue #79990. That change stopped the slot escaping; this one removes the remaining per-name allocation for slots that are never decomposed. Passes toolstash -cmp; std and cmd build byte-identical. allocs/op via compilebench -alloc, linux/amd64 (-count 20); all packages ±0%, p<=0.001. "vs parent" is the win over CL 790300, "vs master" folds in CL 790300 too: allocs/op vs parent vs master Template 814.0k -0.16% -0.76% Unicode 573.7k -0.03% -0.10% GoTypes 4.956M -0.26% -1.08% SSA 46.32M -0.37% -1.51% Flate 811.6k -0.30% -1.45% GoParser 785.8k -0.23% -0.78% Reflect 2.293M -0.41% -1.11% Tar 927.5k -0.34% -1.02% XML 1.088M -0.32% -0.92% geomean 1.755M -0.27% -0.97% Updates #79990 Change-Id: Ib277056a47585d7805438955062c2f59bd77d42f Reviewed-on: https://go-review.googlesource.com/c/go/+/790920 Reviewed-by: Michael Podtserkovskii LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Keith Randall Reviewed-by: Michael Pratt Reviewed-by: Keith Randall Auto-Submit: Michael Pratt --- src/cmd/compile/internal/ssa/copyelim.go | 2 +- src/cmd/compile/internal/ssa/deadcode.go | 6 +-- src/cmd/compile/internal/ssa/debug.go | 11 +++-- src/cmd/compile/internal/ssa/decompose.go | 52 +++++++++++----------- src/cmd/compile/internal/ssa/func.go | 4 +- src/cmd/compile/internal/ssa/print.go | 2 +- src/cmd/compile/internal/ssa/stackalloc.go | 6 +-- src/cmd/compile/internal/ssagen/ssa.go | 5 +-- 8 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/cmd/compile/internal/ssa/copyelim.go b/src/cmd/compile/internal/ssa/copyelim.go index 762ffe1bd2d84c..10abb8c2149df1 100644 --- a/src/cmd/compile/internal/ssa/copyelim.go +++ b/src/cmd/compile/internal/ssa/copyelim.go @@ -22,7 +22,7 @@ func copyelim(f *Func) { // Update named values. for _, name := range f.Names { - values := f.NamedValues[*name] + values := f.NamedValues[name] for i, v := range values { if v.Op == OpCopy { values[i] = v.Args[0] diff --git a/src/cmd/compile/internal/ssa/deadcode.go b/src/cmd/compile/internal/ssa/deadcode.go index aa85097c29d63e..b2d59579cd1ea0 100644 --- a/src/cmd/compile/internal/ssa/deadcode.go +++ b/src/cmd/compile/internal/ssa/deadcode.go @@ -213,7 +213,7 @@ func deadcode(f *Func) { for _, name := range f.Names { j := 0 s.clear() - values := f.NamedValues[*name] + values := f.NamedValues[name] for _, v := range values { if live[v.ID] && !s.contains(v.ID) { values[j] = v @@ -222,14 +222,14 @@ func deadcode(f *Func) { } } if j == 0 { - delete(f.NamedValues, *name) + delete(f.NamedValues, name) } else { f.Names[i] = name i++ for k := len(values) - 1; k >= j; k-- { values[k] = nil } - f.NamedValues[*name] = values[:j] + f.NamedValues[name] = values[:j] } } clear(f.Names[i:]) diff --git a/src/cmd/compile/internal/ssa/debug.go b/src/cmd/compile/internal/ssa/debug.go index f45bb4d388922b..d1c736af32a804 100644 --- a/src/cmd/compile/internal/ssa/debug.go +++ b/src/cmd/compile/internal/ssa/debug.go @@ -441,7 +441,7 @@ func PopulateABIInRegArgOps(f *Func) { // slots that is type-insenstitive. sc := newSlotCanonicalizer() for _, sl := range f.Names { - sc.lookup(*sl) + sc.lookup(sl) } // Add slot -> value entry to f.NamedValues if not already present. @@ -449,8 +449,7 @@ func PopulateABIInRegArgOps(f *Func) { values, ok := f.NamedValues[sl] if !ok { // Haven't seen this slot yet. - sla := f.localSlotAddr(sl) - f.Names = append(f.Names, sla) + f.Names = append(f.Names, sl) } else { for _, ev := range values { if v == ev { @@ -592,14 +591,14 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingLevel int, stackOffset func( state.slots = state.slots[:0] state.vars = state.vars[:0] for i, slot := range f.Names { - state.slots = append(state.slots, *slot) + state.slots = append(state.slots, slot) if ir.IsSynthetic(slot.N) || !IsVarWantedForDebug(slot.N) { continue } topSlot := slot for topSlot.SplitOf != nil { - topSlot = topSlot.SplitOf + topSlot = *topSlot.SplitOf } if _, ok := state.varParts[topSlot.N]; !ok { state.vars = append(state.vars, topSlot.N) @@ -660,7 +659,7 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingLevel int, stackOffset func( if ir.IsSynthetic(slot.N) || !IsVarWantedForDebug(slot.N) { continue } - for _, value := range f.NamedValues[*slot] { + for _, value := range f.NamedValues[slot] { state.valueNames[value.ID] = append(state.valueNames[value.ID], SlotID(i)) } } diff --git a/src/cmd/compile/internal/ssa/decompose.go b/src/cmd/compile/internal/ssa/decompose.go index 6ea69da0169fa3..ee1f1401e28a56 100644 --- a/src/cmd/compile/internal/ssa/decompose.go +++ b/src/cmd/compile/internal/ssa/decompose.go @@ -37,14 +37,14 @@ func decomposeBuiltin(f *Func) { // accumulate new LocalSlots in newNames for addition after the iteration. This decomposition is for // builtin types with leaf components, and thus there is no need to reprocess the newly create LocalSlots. var toDelete []namedVal - var newNames []*LocalSlot + var newNames []LocalSlot for i, name := range f.Names { t := name.Type switch { case t.IsInteger() && t.Size() > f.Config.RegSize: - hiName, loName := f.SplitInt64(name) + hiName, loName := f.SplitInt64(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, hiName, loName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpInt64Make { continue } @@ -53,9 +53,9 @@ func decomposeBuiltin(f *Func) { toDelete = append(toDelete, namedVal{i, j}) } case t.IsComplex(): - rName, iName := f.SplitComplex(name) + rName, iName := f.SplitComplex(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, rName, iName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpComplexMake { continue } @@ -64,9 +64,9 @@ func decomposeBuiltin(f *Func) { toDelete = append(toDelete, namedVal{i, j}) } case t.IsString(): - ptrName, lenName := f.SplitString(name) + ptrName, lenName := f.SplitString(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, ptrName, lenName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpStringMake { continue } @@ -75,10 +75,10 @@ func decomposeBuiltin(f *Func) { toDelete = append(toDelete, namedVal{i, j}) } case t.IsSlice(): - ptrName, lenName, capName := f.SplitSlice(name) + ptrName, lenName, capName := f.SplitSlice(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, ptrName, lenName) newNames = maybeAppend(f, newNames, capName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpSliceMake { continue } @@ -88,9 +88,9 @@ func decomposeBuiltin(f *Func) { toDelete = append(toDelete, namedVal{i, j}) } case t.IsInterface(): - typeName, dataName := f.SplitInterface(name) + typeName, dataName := f.SplitInterface(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, typeName, dataName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpIMake { continue } @@ -109,15 +109,15 @@ func decomposeBuiltin(f *Func) { f.Names = append(f.Names, newNames...) } -func maybeAppend(f *Func, ss []*LocalSlot, s *LocalSlot) []*LocalSlot { +func maybeAppend(f *Func, ss []LocalSlot, s *LocalSlot) []LocalSlot { if _, ok := f.NamedValues[*s]; !ok { f.NamedValues[*s] = nil - return append(ss, s) + return append(ss, *s) } return ss } -func maybeAppend2(f *Func, ss []*LocalSlot, s1, s2 *LocalSlot) []*LocalSlot { +func maybeAppend2(f *Func, ss []LocalSlot, s1, s2 *LocalSlot) []LocalSlot { return maybeAppend(f, maybeAppend(f, ss, s1), s2) } @@ -244,14 +244,14 @@ func decomposeUser(f *Func) { } // Split up named values into their components. i := 0 - var newNames []*LocalSlot + var newNames []LocalSlot for _, name := range f.Names { t := name.Type switch { case isStructNotSIMD(t): - newNames = decomposeUserStructInto(f, name, newNames) + newNames = decomposeUserStructInto(f, f.localSlotAddr(name), newNames) case t.IsArray(): - newNames = decomposeUserArrayInto(f, name, newNames) + newNames = decomposeUserArrayInto(f, f.localSlotAddr(name), newNames) default: f.Names[i] = name i++ @@ -264,7 +264,7 @@ func decomposeUser(f *Func) { // decomposeUserArrayInto creates names for the element(s) of arrays referenced // by name where possible, and appends those new names to slots, which is then // returned. -func decomposeUserArrayInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*LocalSlot { +func decomposeUserArrayInto(f *Func, name *LocalSlot, slots []LocalSlot) []LocalSlot { t := name.Type if t.Size() == 0 { // TODO(khr): Not sure what to do here. Probably nothing. @@ -297,13 +297,13 @@ func decomposeUserArrayInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*Loc return decomposeUserStructInto(f, elemName, slots) } - return append(slots, elemName) + return append(slots, *elemName) } // decomposeUserStructInto creates names for the fields(s) of structs referenced // by name where possible, and appends those new names to slots, which is then // returned. -func decomposeUserStructInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*LocalSlot { +func decomposeUserStructInto(f *Func, name *LocalSlot, slots []LocalSlot) []LocalSlot { fnames := []*LocalSlot{} // slots for struct in name t := name.Type n := t.NumFields() @@ -429,19 +429,19 @@ func deleteNamedVals(f *Func, toDelete []namedVal) { // Get rid of obsolete names for _, d := range toDelete { loc := f.Names[d.locIndex] - vals := f.NamedValues[*loc] + vals := f.NamedValues[loc] l := len(vals) - 1 if l > 0 { vals[d.valIndex] = vals[l] } vals[l] = nil - f.NamedValues[*loc] = vals[:l] + f.NamedValues[loc] = vals[:l] } // Delete locations with no values attached. end := len(f.Names) for i := len(f.Names) - 1; i >= 0; i-- { loc := f.Names[i] - vals := f.NamedValues[*loc] + vals := f.NamedValues[loc] last := len(vals) for j := len(vals) - 1; j >= 0; j-- { if vals[j].Op == OpInvalid { @@ -451,13 +451,13 @@ func deleteNamedVals(f *Func, toDelete []namedVal) { } } if last < len(vals) { - f.NamedValues[*loc] = vals[:last] + f.NamedValues[loc] = vals[:last] } if len(vals) == 0 { - delete(f.NamedValues, *loc) + delete(f.NamedValues, loc) end-- f.Names[i] = f.Names[end] - f.Names[end] = nil + f.Names[end] = LocalSlot{} } } f.Names = f.Names[:end] diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 1c67691547a99c..0b4c26c2dc0b52 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -60,7 +60,7 @@ type Func struct { NamedValues map[LocalSlot][]*Value // Names is a copy of NamedValues.Keys. We keep a separate list // of keys to make iteration order deterministic. - Names []*LocalSlot + Names []LocalSlot // Canonicalize root/top-level local slots, and canonicalize their pieces. // Because LocalSlot pieces refer to their parents with a pointer, this ensures that equivalent slots really are equal. CanonicalLocalSlots map[LocalSlot]*LocalSlot @@ -181,6 +181,8 @@ func (f *Func) retPoset(po *poset) { f.Cache.scrPoset = append(f.Cache.scrPoset, po) } +// localSlotAddr returns a stable canonical *LocalSlot for slot, created on +// first use. SplitOf parents need it: f.Names holds values, not pointers. func (f *Func) localSlotAddr(slot LocalSlot) *LocalSlot { a, ok := f.CanonicalLocalSlots[slot] if !ok { diff --git a/src/cmd/compile/internal/ssa/print.go b/src/cmd/compile/internal/ssa/print.go index ed7f1542497cf3..4dd869124dedaa 100644 --- a/src/cmd/compile/internal/ssa/print.go +++ b/src/cmd/compile/internal/ssa/print.go @@ -187,6 +187,6 @@ func fprintFunc(p funcPrinter, f *Func) { p.endBlock(b, reachable[b.ID]) } for _, name := range f.Names { - p.named(*name, f.NamedValues[*name]) + p.named(name, f.NamedValues[name]) } } diff --git a/src/cmd/compile/internal/ssa/stackalloc.go b/src/cmd/compile/internal/ssa/stackalloc.go index 06097e95da0bc1..8b3d613d5a76b4 100644 --- a/src/cmd/compile/internal/ssa/stackalloc.go +++ b/src/cmd/compile/internal/ssa/stackalloc.go @@ -157,7 +157,7 @@ func (s *stackAllocState) stackalloc() { for _, name := range f.Names { // Note: not "range f.NamedValues" above, because // that would be nondeterministic. - for _, v := range f.NamedValues[*name] { + for _, v := range f.NamedValues[name] { if v.Op == OpArgIntReg || v.Op == OpArgFloatReg { aux := v.Aux.(*AuxNameOffset) // Never let an arg be bound to a differently named thing. @@ -177,9 +177,9 @@ func (s *stackAllocState) stackalloc() { if names[v.ID] == empty { if f.pass.debug > stackDebug { - fmt.Printf("stackalloc value %s to name %s\n", v, *name) + fmt.Printf("stackalloc value %s to name %s\n", v, name) } - names[v.ID] = *name + names[v.ID] = name } } } diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 809c8b86e53bbb..0deb520471dde6 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -6688,10 +6688,7 @@ func (s *state) addNamedValue(n *ir.Name, v *ssa.Value) { loc := ssa.LocalSlot{N: n, Type: n.Type(), Off: 0} values, ok := s.f.NamedValues[loc] if !ok { - locp := new(ssa.LocalSlot) - *locp = loc - s.f.Names = append(s.f.Names, locp) - s.f.CanonicalLocalSlots[loc] = locp + s.f.Names = append(s.f.Names, loc) } s.f.NamedValues[loc] = append(values, v) } From 4bbe2752b3645574c30042f45715ac5453ee6014 Mon Sep 17 00:00:00 2001 From: Neitrino Photonov Date: Mon, 15 Jun 2026 18:08:02 +0300 Subject: [PATCH 36/38] cmd/go: retry ETXTBSY when running a cached builtin tool go test -cover runs "go tool covdata", which links covdata, caches the executable into $GOCACHE (CacheExecutable), and execs it from there via runBuiltTool. When many packages are tested at once (go test ./...), the covdata invocations share one build cache. A concurrent go process can still hold a writable descriptor to the freshly written cached file when this process execs it, so the exec fails with ETXTBSY ("text file busy"): go tool covdata: fork/exec $GOCACHE//covdata: text file busy Every other exec-from-cache path already retries this race: base.RunStdin and (*runTestActor).Act in cmd/go/internal/test both loop on ETXTBSY with backoff (see #22220, #22315). runBuiltTool was the one path that did not. The race became observable in 1.26 after CL 668035 (test barrier actions) reordered the scheduling of test/coverage run actions; the underlying missing-retry bug was latent before that. Wrap toolCmd.Start() in the same bounded retry. Retrying Start() is safe: once it succeeds the exec has happened (no ETXTBSY is possible after), and on failure the child never ran, so re-execing has no side effects. Fixes #78204 Change-Id: I77d0a5804e69d4fb386bbdb4aa9c5f3b179261b6 Reviewed-on: https://go-review.googlesource.com/c/go/+/790840 Reviewed-by: Michael Matloob LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Russ Cox Reviewed-by: Michael Pratt Reviewed-by: Sean Liao Auto-Submit: Michael Pratt --- src/cmd/go/internal/tool/tool.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/cmd/go/internal/tool/tool.go b/src/cmd/go/internal/tool/tool.go index 97c27c8caa44d6..afc50d9b2d9177 100644 --- a/src/cmd/go/internal/tool/tool.go +++ b/src/cmd/go/internal/tool/tool.go @@ -22,6 +22,7 @@ import ( "slices" "sort" "strings" + "time" "cmd/go/internal/base" "cmd/go/internal/cfg" @@ -392,15 +393,30 @@ func runBuiltTool(toolName string, env, cmdline []string) error { return nil } - toolCmd := &exec.Cmd{ - Path: cmdline[0], - Args: cmdline, - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, - Env: env, + // The tool was just linked and cached into $GOCACHE (CacheExecutable), and + // is executed from there. A concurrent go process may still hold a writable + // descriptor to the same cached file, so the exec can fail with ETXTBSY + // ("text file busy"). Retry a few times with backoff, matching base.RunStdin + // and (*runTestActor).Act in cmd/go/internal/test. See #22220, #22315, #78204. + var toolCmd *exec.Cmd + var err error + for try := range 3 { + toolCmd = &exec.Cmd{ + Path: cmdline[0], + Args: cmdline, + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + Env: env, + } + err = toolCmd.Start() + if err == nil || !base.IsETXTBSY(err) { + break + } + // Another go process likely still has the cached file open for + // writing; it will close it shortly. Sleep and retry. + time.Sleep(100 * time.Millisecond << uint(try)) } - err := toolCmd.Start() if err == nil { c := make(chan os.Signal, 100) signal.Notify(c, signalsToForward...) From 9f1012d9a1aa0831ff44ac9c767e96f9943d13fe Mon Sep 17 00:00:00 2001 From: Sean Liao Date: Tue, 23 Dec 2025 20:38:45 +0000 Subject: [PATCH 37/38] go/doc/comment: never rewrite into smart quotes Since Go source code is in UTF-8, smart quotes should be part of the original source if they are desired. Characters important to programming languages should not be mangled. Fixes #54312 Fixes #76975 Change-Id: I20331669bb691eafe66ebcc3f8513c4f6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/732420 Reviewed-by: Michael Pratt Reviewed-by: Russ Cox LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: kortschak Reviewed-by: Rob Pike --- src/go/doc/comment/parse.go | 23 +---------------------- src/go/doc/comment/testdata/quote.txt | 16 ++++++++-------- src/go/doc/synopsis_test.go | 2 +- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index 4d5784a8b4c35d..eb675628b37da4 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -939,28 +939,7 @@ func (d *parseDoc) parseText(out []Text, s string, autoLink bool) []Text { continue } } - switch { - case strings.HasPrefix(t, "``"): - if len(t) >= 3 && t[2] == '`' { - // Do not convert `` inside ```, in case people are mistakenly writing Markdown. - i += 3 - for i < len(t) && t[i] == '`' { - i++ - } - break - } - writeUntil(i) - w.WriteRune('“') - i += 2 - wrote = i - case strings.HasPrefix(t, "''"): - writeUntil(i) - w.WriteRune('”') - i += 2 - wrote = i - default: - i++ - } + i++ } flush(len(s)) return out diff --git a/src/go/doc/comment/testdata/quote.txt b/src/go/doc/comment/testdata/quote.txt index b64adae0b36680..cfe945a9fa354b 100644 --- a/src/go/doc/comment/testdata/quote.txt +++ b/src/go/doc/comment/testdata/quote.txt @@ -1,15 +1,15 @@ -- input -- -Doubled single quotes like `` and '' turn into Unicode double quotes, -but single quotes ` and ' do not. +Doubled single quotes like `` and '' don't turn into Unicode double quotes, +and neither do single quotes ` and '. Misplaced markdown fences ``` do not either. -- gofmt -- -Doubled single quotes like “ and ” turn into Unicode double quotes, -but single quotes ` and ' do not. +Doubled single quotes like `` and '' don't turn into Unicode double quotes, +and neither do single quotes ` and '. Misplaced markdown fences ``` do not either. -- text -- -Doubled single quotes like “ and ” turn into Unicode double quotes, but single -quotes ` and ' do not. Misplaced markdown fences ``` do not either. +Doubled single quotes like `` and '' don't turn into Unicode double quotes, and +neither do single quotes ` and '. Misplaced markdown fences ``` do not either. -- html -- -

Doubled single quotes like “ and ” turn into Unicode double quotes, -but single quotes ` and ' do not. +

Doubled single quotes like `` and '' don't turn into Unicode double quotes, +and neither do single quotes ` and '. Misplaced markdown fences ``` do not either. diff --git a/src/go/doc/synopsis_test.go b/src/go/doc/synopsis_test.go index 158c734bf02bbf..949859c87c160f 100644 --- a/src/go/doc/synopsis_test.go +++ b/src/go/doc/synopsis_test.go @@ -35,7 +35,7 @@ var tests = []struct { {"All Rights reserved. Package foo does bar.", 20, ""}, {"All rights reserved. Package foo does bar.", 20, ""}, {"Authors: foo@bar.com. Package foo does bar.", 21, ""}, - {"typically invoked as ``go tool asm'',", 37, "typically invoked as “go tool asm”,"}, + {"typically invoked as ``go tool asm'',", 37, "typically invoked as ``go tool asm'',"}, } func TestSynopsis(t *testing.T) { From d31e2b6711ba9cb528f7f8d0f0431b0dd7ba21b7 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Tue, 14 Jul 2026 16:52:43 -0400 Subject: [PATCH 38/38] cmd/go/internal/doc: always return cached executable On a clean GOCACHE, buildPkgsite returned a temporary path deleted by b.Close() before the command execution. Instead, this CL always returns the cached executable path even it is fresh built ensuring the existence of the doc executable. +test Fixes golang/go#80287 Change-Id: I949d01dceac817082fa5e38450d0b0b39fc325a3 Reviewed-on: https://go-review.googlesource.com/c/go/+/800720 Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob --- src/cmd/go/internal/doc/pkgsite.go | 9 +++++++++ src/cmd/go/testdata/script/doc_http_clean_cache.txt | 11 +++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/cmd/go/testdata/script/doc_http_clean_cache.txt diff --git a/src/cmd/go/internal/doc/pkgsite.go b/src/cmd/go/internal/doc/pkgsite.go index 8bfae93029bdc5..bb006607824e8d 100644 --- a/src/cmd/go/internal/doc/pkgsite.go +++ b/src/cmd/go/internal/doc/pkgsite.go @@ -93,6 +93,12 @@ func buildPkgsite(ctx context.Context) string { a := b.LinkAction(loader, work.ModeBuild, work.ModeBuild, p) a.CacheExecutable = true b.Do(ctx, a) + + // Both paths return an executable in GOCACHE: CachedExecutable is set on + // fresh builds, while BuiltTarget is set on cache hits. + if cached := a.CachedExecutable(); cached != "" { + return cached + } return a.BuiltTarget() } @@ -139,6 +145,9 @@ func doPkgsite(ctx context.Context, urlPath, fragment string) error { pkgsite := buildPkgsite(ctx) if os.Getenv("TEST_GODOC_BUILD_ONLY") != "" { + if _, err := os.Stat(pkgsite); err != nil { + return fmt.Errorf("built pkgsite binary does not exist: %w", err) + } return nil } cmd := exec.Command(pkgsite, "-gorepo", cfg.GOROOT, "-http", addr, "-open", path) diff --git a/src/cmd/go/testdata/script/doc_http_clean_cache.txt b/src/cmd/go/testdata/script/doc_http_clean_cache.txt new file mode 100644 index 00000000000000..0c44326a3f16eb --- /dev/null +++ b/src/cmd/go/testdata/script/doc_http_clean_cache.txt @@ -0,0 +1,11 @@ +[short] skip 'builds pkgsite on clean GOCACHE' + +# Test that go doc -http succeeds on a clean GOCACHE (golang/go#80287). +env TEST_GODOC_BUILD_ONLY=1 +env GOCACHE=$WORK/clean-gocache + +# First run with clean GOCACHE +go doc -http fmt + +# Second run with existing GOCACHE +go doc -http fmt