From 6a08e80399bd65b95e60e3c74b7e1f86754752a7 Mon Sep 17 00:00:00 2001 From: Nicholas Husin Date: Fri, 29 Aug 2025 10:34:10 -0400 Subject: [PATCH 01/40] net/http: skip redirecting in ServeMux when URL path for CONNECT is empty In 1.21 ServeMux, we had a special-case to skip redirection when a given path is empty for CONNECT requests: https://go.googlesource.com/go/+/refs/tags/go1.24.4/src/net/http/servemux121.go#205. This special case seems to not have been carried over to 1.22 ServeMux. This causes needless redirection, which this CL fixes. Fixes #74422 Change-Id: I3cc5b4d195ab0591a9139225b632cbe17f4290db Reviewed-on: https://go-review.googlesource.com/c/go/+/699915 Reviewed-by: Sean Liao LUCI-TryBot-Result: Go LUCI Auto-Submit: Sean Liao Reviewed-by: Damien Neil --- src/net/http/server.go | 9 ++++++--- src/net/http/server_test.go | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/net/http/server.go b/src/net/http/server.go index cf0bd0a91d7624..6fdcd51c0a6777 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -2759,9 +2759,12 @@ func (mux *ServeMux) matchOrRedirect(host, method, path string, u *url.URL) (_ * defer mux.mu.RUnlock() n, matches := mux.tree.match(host, method, path) - // If we have an exact match, or we were asked not to try trailing-slash redirection, - // or the URL already has a trailing slash, then we're done. - if !exactMatch(n, path) && u != nil && !strings.HasSuffix(path, "/") { + // We can terminate here if any of the following is true: + // - We have an exact match already. + // - We were asked not to try trailing slash redirection. + // - The URL already has a trailing slash. + // - The URL is an empty string. + if !exactMatch(n, path) && u != nil && !strings.HasSuffix(path, "/") && path != "" { // If there is an exact match with a trailing slash, then redirect. path += "/" n2, _ := mux.tree.match(host, method, path) diff --git a/src/net/http/server_test.go b/src/net/http/server_test.go index f4aafc853bd5d6..832f9688b63d9c 100644 --- a/src/net/http/server_test.go +++ b/src/net/http/server_test.go @@ -97,6 +97,7 @@ func TestFindHandler(t *testing.T) { {"GET", "/foo/x", "&http.handler{i:2}"}, {"GET", "/bar/x", "&http.handler{i:4}"}, {"GET", "/bar", `&http.redirectHandler{url:"/bar/", code:301}`}, + {"CONNECT", "", "(http.HandlerFunc)(.*)"}, {"CONNECT", "/", "&http.handler{i:1}"}, {"CONNECT", "//", "&http.handler{i:1}"}, {"CONNECT", "//foo", "&http.handler{i:5}"}, @@ -112,7 +113,7 @@ func TestFindHandler(t *testing.T) { r.URL = &url.URL{Path: test.path} gotH, _, _, _ := mux.findHandler(&r) got := fmt.Sprintf("%#v", gotH) - if got != test.wantHandler { + if !regexp.MustCompile(test.wantHandler).MatchString(got) { t.Errorf("%s %q: got %q, want %q", test.method, test.path, got, test.wantHandler) } } From d4b17f58695337c7eefa9d066cc51a425842e491 Mon Sep 17 00:00:00 2001 From: Guoqi Chen Date: Wed, 27 Aug 2025 14:45:58 +0800 Subject: [PATCH 02/40] internal/runtime/atomic: reset wrong jump target in Cas{,64} on loong64 The implementation here needs to be consistent with ssa.OpLOONG64LoweredAtomicCas{32,64}, which was ignored in CL 613396. Change-Id: I72e8d2318e0c1935cc3a35ab5098f8a84e48bcd5 Reviewed-on: https://go-review.googlesource.com/c/go/+/699395 Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Reviewed-by: sophie zhao Reviewed-by: Meidan Li --- src/internal/runtime/atomic/atomic_loong64.s | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/internal/runtime/atomic/atomic_loong64.s b/src/internal/runtime/atomic/atomic_loong64.s index 95d3e2bdab8c86..4215af24febaa2 100644 --- a/src/internal/runtime/atomic/atomic_loong64.s +++ b/src/internal/runtime/atomic/atomic_loong64.s @@ -19,7 +19,7 @@ TEXT ·Cas(SB), NOSPLIT, $0-17 MOVW new+12(FP), R6 MOVBU internal∕cpu·Loong64+const_offsetLOONG64HasLAMCAS(SB), R8 - BEQ R8, cas_again + BEQ R8, ll_sc MOVV R5, R7 // backup old value AMCASDBW R6, (R4), R5 BNE R7, R5, cas_fail0 @@ -30,6 +30,7 @@ cas_fail0: MOVB R0, ret+16(FP) RET +ll_sc: // Implemented using the ll-sc instruction pair DBAR $0x14 // LoadAcquire barrier cas_again: @@ -60,7 +61,7 @@ TEXT ·Cas64(SB), NOSPLIT, $0-25 MOVV new+16(FP), R6 MOVBU internal∕cpu·Loong64+const_offsetLOONG64HasLAMCAS(SB), R8 - BEQ R8, cas64_again + BEQ R8, ll_sc_64 MOVV R5, R7 // backup old value AMCASDBV R6, (R4), R5 BNE R7, R5, cas64_fail0 @@ -71,6 +72,7 @@ cas64_fail0: MOVB R0, ret+24(FP) RET +ll_sc_64: // Implemented using the ll-sc instruction pair DBAR $0x14 cas64_again: From 882335e2cbe9b123ba5fa4ee7544e7283e41d07c Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Mon, 25 Aug 2025 15:22:09 +0800 Subject: [PATCH 03/40] cmd/internal/obj/loong64: add LDPTR.{W/D} and STPTR.{W/D} instructions support Go asm syntax: MOVWP 4(R4), R5 MOVVP 8(R4), R5 MOVWP R4, 12(R5) MOVVP R4, 16(R5) Equivalent platform assembler syntax: ldptr.w r5, r4, $1 ldptr.d r5, r4, $2 stptr.w r4, r5, $3 stptr.d r4, r5, $4 Change-Id: I50a341cee2d875cb7c5da9db08b23799c9dc6c64 Reviewed-on: https://go-review.googlesource.com/c/go/+/699055 Reviewed-by: abner chenc Reviewed-by: Meidan Li LUCI-TryBot-Result: Go LUCI Reviewed-by: Carlos Amedee Reviewed-by: Cherry Mui --- .../asm/internal/asm/testdata/loong64enc1.s | 22 +++++++++++++ src/cmd/internal/obj/loong64/a.out.go | 4 +++ src/cmd/internal/obj/loong64/anames.go | 2 ++ src/cmd/internal/obj/loong64/asm.go | 33 +++++++++++++++++++ src/cmd/internal/obj/loong64/doc.go | 28 ++++++++++++++++ 5 files changed, 89 insertions(+) diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index 72e65734666c2a..63676cc785967c 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -260,6 +260,28 @@ lable2: MOVV FCC0, R4 // 04dc1401 MOVV R4, FCC0 // 80d81401 + // LDPTR.{W/D} and STPTR.{W/D} instructions + MOVWP R5, -32768(R4) // 85008025 + MOVWP R5, 32764(R4) // 85fc7f25 + MOVWP R5, 32(R4) // 85200025 + MOVWP R5, 4(R4) // 85040025 + MOVWP R5, (R4) // 85000025 + MOVVP R5, -32768(R4) // 85008027 + MOVVP R5, 32764(R4) // 85fc7f27 + MOVVP R5, 32(R4) // 85200027 + MOVVP R5, 4(R4) // 85040027 + MOVVP R5, (R4) // 85000027 + MOVWP -32768(R5), R4 // a4008024 + MOVWP 32764(R5), R4 // a4fc7f24 + MOVWP 32(R5), R4 // a4200024 + MOVWP 4(R5), R4 // a4040024 + MOVWP (R5), R4 // a4000024 + MOVVP -32768(R5), R4 // a4008026 + MOVVP 32764(R5), R4 // a4fc7f26 + MOVVP 32(R5), R4 // a4200026 + MOVVP 4(R5), R4 // a4040026 + MOVVP (R5), R4 // a4000026 + // Loong64 atomic memory access instructions AMSWAPB R14, (R13), R12 // ac395c38 AMSWAPH R14, (R13), R12 // acb95c38 diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index f5d20cfabe76d5..8e651cdfef0e21 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -666,6 +666,10 @@ const ( ABSTRPICKW ABSTRPICKV + // 2.2.5.3 + AMOVWP + AMOVVP + // 2.2.5.4. Prefetch Instructions APRELD APRELDX diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go index 67b5f2fc809927..c629553d5598af 100644 --- a/src/cmd/internal/obj/loong64/anames.go +++ b/src/cmd/internal/obj/loong64/anames.go @@ -202,6 +202,8 @@ var Anames = []string{ "BSTRINSV", "BSTRPICKW", "BSTRPICKV", + "MOVWP", + "MOVVP", "PRELD", "PRELDX", "CRCWBW", diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 5d85585ebec11e..1b982f6c86fa53 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -212,6 +212,8 @@ var optab = []Optab{ {AMOVV, C_REG, C_NONE, C_NONE, C_TLS_LE, C_NONE, 53, 16, 0, 0}, {AMOVB, C_REG, C_NONE, C_NONE, C_TLS_LE, C_NONE, 53, 16, 0, 0}, {AMOVBU, C_REG, C_NONE, C_NONE, C_TLS_LE, C_NONE, 53, 16, 0, 0}, + {AMOVWP, C_REG, C_NONE, C_NONE, C_SOREG, C_NONE, 73, 4, 0, 0}, + {AMOVWP, C_REG, C_NONE, C_NONE, C_LOREG, C_NONE, 73, 4, 0, 0}, {AMOVW, C_LAUTO, C_NONE, C_NONE, C_REG, C_NONE, 36, 12, REGSP, 0}, {AMOVWU, C_LAUTO, C_NONE, C_NONE, C_REG, C_NONE, 36, 12, REGSP, 0}, @@ -233,6 +235,8 @@ var optab = []Optab{ {AMOVV, C_TLS_LE, C_NONE, C_NONE, C_REG, C_NONE, 54, 16, 0, 0}, {AMOVB, C_TLS_LE, C_NONE, C_NONE, C_REG, C_NONE, 54, 16, 0, 0}, {AMOVBU, C_TLS_LE, C_NONE, C_NONE, C_REG, C_NONE, 54, 16, 0, 0}, + {AMOVWP, C_SOREG, C_NONE, C_NONE, C_REG, C_NONE, 74, 4, 0, 0}, + {AMOVWP, C_LOREG, C_NONE, C_NONE, C_REG, C_NONE, 74, 4, 0, 0}, {AMOVW, C_SACON, C_NONE, C_NONE, C_REG, C_NONE, 3, 4, REGSP, 0}, {AMOVV, C_SACON, C_NONE, C_NONE, C_REG, C_NONE, 3, 4, REGSP, 0}, @@ -1437,6 +1441,9 @@ func buildop(ctxt *obj.Link) { case AMOVBU: opset(AMOVHU, r0) + case AMOVWP: + opset(AMOVVP, r0) + case AMUL: opset(AMULU, r0) opset(AMULH, r0) @@ -1964,6 +1971,10 @@ func OP_16IRR(op uint32, i uint32, r2 uint32, r3 uint32) uint32 { return op | (i&0xFFFF)<<10 | (r2&0x1F)<<5 | (r3&0x1F)<<0 } +func OP_14IRR(op uint32, i uint32, r2 uint32, r3 uint32) uint32 { + return op | (i&0x3FFF)<<10 | (r2&0x1F)<<5 | (r3&0x1F)<<0 +} + func OP_12IR_5I(op uint32, i1 uint32, r2 uint32, i2 uint32) uint32 { return op | (i1&0xFFF)<<10 | (r2&0x1F)<<5 | (i2&0x1F)<<0 } @@ -2893,6 +2904,20 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { o3 = OP_12IRR(c.opirr(ALU52ID), uint32(v>>52), uint32(REGTMP), uint32(REGTMP)) } o4 = OP_RRR(c.oprrr(p.As), uint32(REGTMP), uint32(r), uint32(p.To.Reg)) + + case 73: + v := c.regoff(&p.To) + if v&3 != 0 { + c.ctxt.Diag("%v: offset must be a multiple of 4.\n", p) + } + o1 = OP_14IRR(c.opirr(p.As), uint32(v>>2), uint32(p.To.Reg), uint32(p.From.Reg)) + + case 74: + v := c.regoff(&p.From) + if v&3 != 0 { + c.ctxt.Diag("%v: offset must be a multiple of 4.\n", p) + } + o1 = OP_14IRR(c.opirr(-p.As), uint32(v>>2), uint32(p.From.Reg), uint32(p.To.Reg)) } out[0] = o1 @@ -4026,6 +4051,10 @@ func (c *ctxt0) opirr(a obj.As) uint32 { return 0x0ad << 22 case AMOVD: return 0x0af << 22 + case AMOVVP: + return 0x27 << 24 // stptr.d + case AMOVWP: + return 0x25 << 24 // stptr.w case -AMOVB: return 0x0a0 << 22 case -AMOVBU: @@ -4044,6 +4073,10 @@ func (c *ctxt0) opirr(a obj.As) uint32 { return 0x0ac << 22 case -AMOVD: return 0x0ae << 22 + case -AMOVVP: + return 0x26 << 24 // ldptr.d + case -AMOVWP: + return 0x24 << 24 // ldptr.w case -AVMOVQ: return 0x0b0 << 22 // vld case -AXVMOVQ: diff --git a/src/cmd/internal/obj/loong64/doc.go b/src/cmd/internal/obj/loong64/doc.go index 64bb41ae5a2219..6c8f2618a2cb73 100644 --- a/src/cmd/internal/obj/loong64/doc.go +++ b/src/cmd/internal/obj/loong64/doc.go @@ -289,6 +289,34 @@ Note: In the following sections 3.1 to 3.6, "ui4" (4-bit unsigned int immediate) Go assembly | instruction Encoding ALSLV $4, r4, r5, R6 | 002d9486 + +5. Note of special memory access instructions + Instruction format: + MOVWP offset(Rj), Rd + MOVVP offset(Rj), Rd + MOVWP Rd, offset(Rj) + MOVVP Rd, offset(Rj) + + Mapping between Go and platform assembly: + Go assembly | platform assembly + MOVWP offset(Rj), Rd | ldptr.w rd, rj, si14 + MOVVP offset(Rj), Rd | ldptr.d rd, rj, si14 + MOVWP Rd, offset(Rj) | stptr.w rd, rj, si14 + MOVVP Rd, offset(Rj) | stptr.d rd, rj, si14 + + note: In Go assembly, for ease of understanding, offset is a 16-bit immediate number representing + the actual address offset, but in platform assembly, it need a 14-bit immediate number. + si14 = offset>>2 + + The addressing calculation for the above instruction involves logically left-shifting the 14-bit + immediate number si14 by 2 bits, then sign-extending it, and finally adding it to the value in the + general-purpose register rj to obtain the sum. + + For example: + + Go assembly | platform assembly + MOVWP 8(R4), R5 | ldptr.w r5, r4, $2 + */ package loong64 From 7bba745820b771307593b7278ce17464eeda2f3d Mon Sep 17 00:00:00 2001 From: limeidan Date: Thu, 28 Aug 2025 19:22:51 +0800 Subject: [PATCH 04/40] cmd/compile: use generated loops instead of DUFFZERO on loong64 Change-Id: Id43ee4353d4bac96627f8b0f54545cdd3d2a1d1b Reviewed-on: https://go-review.googlesource.com/c/go/+/699695 Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI Reviewed-by: Carlos Amedee Reviewed-by: abner chenc --- src/cmd/compile/internal/loong64/ssa.go | 120 +++++++++++++++--- .../compile/internal/ssa/_gen/LOONG64.rules | 20 +-- .../compile/internal/ssa/_gen/LOONG64Ops.go | 37 ++++-- src/cmd/compile/internal/ssa/opGen.go | 24 +++- .../compile/internal/ssa/rewriteLOONG64.go | 45 ++----- 5 files changed, 155 insertions(+), 91 deletions(-) diff --git a/src/cmd/compile/internal/loong64/ssa.go b/src/cmd/compile/internal/loong64/ssa.go index 895eadd07261d6..bdc8e37b04fe9d 100644 --- a/src/cmd/compile/internal/loong64/ssa.go +++ b/src/cmd/compile/internal/loong64/ssa.go @@ -560,28 +560,97 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Sym = ir.Syms.Duffzero p.To.Offset = v.AuxInt case ssa.OpLOONG64LoweredZero: - // MOVx R0, (Rarg0) - // ADDV $sz, Rarg0 - // BGEU Rarg1, Rarg0, -2(PC) - mov, sz := largestMove(v.AuxInt) - p := s.Prog(mov) - p.From.Type = obj.TYPE_REG - p.From.Reg = loong64.REGZERO - p.To.Type = obj.TYPE_MEM - p.To.Reg = v.Args[0].Reg() + ptrReg := v.Args[0].Reg() + n := v.AuxInt + if n < 16 { + v.Fatalf("Zero too small %d", n) + } - p2 := s.Prog(loong64.AADDVU) - p2.From.Type = obj.TYPE_CONST - p2.From.Offset = sz - p2.To.Type = obj.TYPE_REG - p2.To.Reg = v.Args[0].Reg() + // Generate Zeroing instructions. + var off int64 + for n >= 8 { + // MOVV ZR, off(ptrReg) + zero8(s, ptrReg, off) + off += 8 + n -= 8 + } + if n != 0 { + // MOVV ZR, off+n-8(ptrReg) + zero8(s, ptrReg, off+n-8) + } + case ssa.OpLOONG64LoweredZeroLoop: + ptrReg := v.Args[0].Reg() + countReg := v.RegTmp() + var off int64 + n := v.AuxInt + loopSize := int64(64) + if n < 3*loopSize { + // - a loop count of 0 won't work. + // - a loop count of 1 is useless. + // - a loop count of 2 is a code size ~tie + // 4 instructions to implement the loop + // 8 instructions in the loop body + // vs + // 16 instuctions in the straightline code + // Might as well use straightline code. + v.Fatalf("ZeroLoop size tool small %d", n) + } - p3 := s.Prog(loong64.ABGEU) - p3.From.Type = obj.TYPE_REG - p3.From.Reg = v.Args[1].Reg() - p3.Reg = v.Args[0].Reg() - p3.To.Type = obj.TYPE_BRANCH - p3.To.SetTarget(p) + // Put iteration count in a register. + // MOVV $n/loopSize, countReg + p := s.Prog(loong64.AMOVV) + p.From.Type = obj.TYPE_CONST + p.From.Offset = n / loopSize + p.To.Type = obj.TYPE_REG + p.To.Reg = countReg + cntInit := p + + // Zero loopSize bytes starting at ptrReg. + for range loopSize / 8 { + // MOVV ZR, off(ptrReg) + zero8(s, ptrReg, off) + off += 8 + } + + // Increment ptrReg by loopSize. + // ADDV $loopSize, ptrReg + p = s.Prog(loong64.AADDV) + p.From.Type = obj.TYPE_CONST + p.From.Offset = loopSize + p.To.Type = obj.TYPE_REG + p.To.Reg = ptrReg + + // Decrement loop count. + // SUBV $1, countReg + p = s.Prog(loong64.ASUBV) + p.From.Type = obj.TYPE_CONST + p.From.Offset = 1 + p.To.Type = obj.TYPE_REG + p.To.Reg = countReg + + // Jump to loop header if we're not done yet. + // BNE countReg, loop header + p = s.Prog(loong64.ABNE) + p.From.Type = obj.TYPE_REG + p.From.Reg = countReg + p.To.Type = obj.TYPE_BRANCH + p.To.SetTarget(cntInit.Link) + + // Multiples of the loop size are now done. + n %= loopSize + + off = 0 + // Write any fractional portion. + for n >= 8 { + // MOVV ZR, off(ptrReg) + zero8(s, ptrReg, off) + off += 8 + n -= 8 + } + + if n != 0 { + zero8(s, ptrReg, off+n-8) + } case ssa.OpLOONG64DUFFCOPY: p := s.Prog(obj.ADUFFCOPY) @@ -1155,3 +1224,14 @@ func spillArgReg(pp *objw.Progs, p *obj.Prog, f *ssa.Func, t *types.Type, reg in p.Pos = p.Pos.WithNotStmt() return p } + +// zero8 zeroes 8 bytes at reg+off. +func zero8(s *ssagen.State, reg int16, off int64) { + // MOVV ZR, off(reg) + p := s.Prog(loong64.AMOVV) + p.From.Type = obj.TYPE_REG + p.From.Reg = loong64.REGZERO + p.To.Type = obj.TYPE_MEM + p.To.Reg = reg + p.To.Offset = off +} diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules index ca04bdcd42307d..6dd28c6d4544e6 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules @@ -373,24 +373,8 @@ (MOVVstore [8] ptr (MOVVconst [0]) (MOVVstore ptr (MOVVconst [0]) mem)) -// strip off fractional word zeroing -(Zero [s] ptr mem) && s%8 != 0 && s > 16 => - (Zero [s%8] - (OffPtr ptr [s-s%8]) - (Zero [s-s%8] ptr mem)) - -// medium zeroing uses a duff device -(Zero [s] ptr mem) - && s%8 == 0 && s > 16 && s <= 8*128 => - (DUFFZERO [8 * (128 - s/8)] ptr mem) - -// large zeroing uses a loop -(Zero [s] ptr mem) - && s%8 == 0 && s > 8*128 => - (LoweredZero - ptr - (ADDVconst ptr [s-8]) - mem) +(Zero [s] ptr mem) && s > 16 && s < 192 => (LoweredZero [s] ptr mem) +(Zero [s] ptr mem) && s >= 192 => (LoweredZeroLoop [s] ptr mem) // moves (Move [0] _ _ mem) => mem diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go index ccd9721498232a..ed635bfd977eb4 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go @@ -376,6 +376,21 @@ func init() { faultOnNilArg0: true, }, + // medium zeroing + // arg0 = address of memory to zero + // arg1 = mem + // auxint = number of bytes to zero + // returns mem + { + name: "LoweredZero", + aux: "Int64", + argLength: 2, + reg: regInfo{ + inputs: []regMask{gp}, + }, + faultOnNilArg0: true, + }, + // duffcopy // arg0 = address of dst memory (in R21, changed as side effect) // arg1 = address of src memory (in R20, changed as side effect) @@ -395,25 +410,21 @@ func init() { faultOnNilArg1: true, }, - // large or unaligned zeroing - // arg0 = address of memory to zero (in R20, changed as side effect) - // arg1 = address of the last element to zero - // arg2 = mem - // auxint = alignment + // large zeroing + // arg0 = address of memory to zero + // arg1 = mem + // auxint = number of bytes to zero // returns mem - // MOVx R0, (R20) - // ADDV $sz, R20 - // BGEU Rarg1, R20, -2(PC) { - name: "LoweredZero", + name: "LoweredZeroLoop", aux: "Int64", - argLength: 3, + argLength: 2, reg: regInfo{ - inputs: []regMask{buildReg("R20"), gp}, - clobbers: buildReg("R20"), + inputs: []regMask{gp}, + clobbersArg0: true, }, - typ: "Mem", faultOnNilArg0: true, + needIntTemp: true, }, // large or unaligned move diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 126682b9866849..7bdb14cec9a5de 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1923,8 +1923,9 @@ const ( OpLOONG64CALLclosure OpLOONG64CALLinter OpLOONG64DUFFZERO - OpLOONG64DUFFCOPY OpLOONG64LoweredZero + OpLOONG64DUFFCOPY + OpLOONG64LoweredZeroLoop OpLOONG64LoweredMove OpLOONG64LoweredAtomicLoad8 OpLOONG64LoweredAtomicLoad32 @@ -25912,6 +25913,17 @@ var opcodeTable = [...]opInfo{ clobbers: 524290, // R1 R20 }, }, + { + name: "LoweredZero", + auxType: auxInt64, + argLen: 2, + faultOnNilArg0: true, + reg: regInfo{ + inputs: []inputInfo{ + {0, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31 + }, + }, + }, { name: "DUFFCOPY", auxType: auxInt64, @@ -25927,16 +25939,16 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "LoweredZero", + name: "LoweredZeroLoop", auxType: auxInt64, - argLen: 3, + argLen: 2, + needIntTemp: true, faultOnNilArg0: true, reg: regInfo{ inputs: []inputInfo{ - {0, 524288}, // R20 - {1, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31 + {0, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31 }, - clobbers: 524288, // R20 + clobbersArg0: true, }, }, { diff --git a/src/cmd/compile/internal/ssa/rewriteLOONG64.go b/src/cmd/compile/internal/ssa/rewriteLOONG64.go index eb134789f74131..6407628a8b1082 100644 --- a/src/cmd/compile/internal/ssa/rewriteLOONG64.go +++ b/src/cmd/compile/internal/ssa/rewriteLOONG64.go @@ -11497,56 +11497,33 @@ func rewriteValueLOONG64_OpZero(v *Value) bool { return true } // match: (Zero [s] ptr mem) - // cond: s%8 != 0 && s > 16 - // result: (Zero [s%8] (OffPtr ptr [s-s%8]) (Zero [s-s%8] ptr mem)) - for { - s := auxIntToInt64(v.AuxInt) - ptr := v_0 - mem := v_1 - if !(s%8 != 0 && s > 16) { - break - } - v.reset(OpZero) - v.AuxInt = int64ToAuxInt(s % 8) - v0 := b.NewValue0(v.Pos, OpOffPtr, ptr.Type) - v0.AuxInt = int64ToAuxInt(s - s%8) - v0.AddArg(ptr) - v1 := b.NewValue0(v.Pos, OpZero, types.TypeMem) - v1.AuxInt = int64ToAuxInt(s - s%8) - v1.AddArg2(ptr, mem) - v.AddArg2(v0, v1) - return true - } - // match: (Zero [s] ptr mem) - // cond: s%8 == 0 && s > 16 && s <= 8*128 - // result: (DUFFZERO [8 * (128 - s/8)] ptr mem) + // cond: s > 16 && s < 192 + // result: (LoweredZero [s] ptr mem) for { s := auxIntToInt64(v.AuxInt) ptr := v_0 mem := v_1 - if !(s%8 == 0 && s > 16 && s <= 8*128) { + if !(s > 16 && s < 192) { break } - v.reset(OpLOONG64DUFFZERO) - v.AuxInt = int64ToAuxInt(8 * (128 - s/8)) + v.reset(OpLOONG64LoweredZero) + v.AuxInt = int64ToAuxInt(s) v.AddArg2(ptr, mem) return true } // match: (Zero [s] ptr mem) - // cond: s%8 == 0 && s > 8*128 - // result: (LoweredZero ptr (ADDVconst ptr [s-8]) mem) + // cond: s >= 192 + // result: (LoweredZeroLoop [s] ptr mem) for { s := auxIntToInt64(v.AuxInt) ptr := v_0 mem := v_1 - if !(s%8 == 0 && s > 8*128) { + if !(s >= 192) { break } - v.reset(OpLOONG64LoweredZero) - v0 := b.NewValue0(v.Pos, OpLOONG64ADDVconst, ptr.Type) - v0.AuxInt = int64ToAuxInt(s - 8) - v0.AddArg(ptr) - v.AddArg3(ptr, v0, mem) + v.reset(OpLOONG64LoweredZeroLoop) + v.AuxInt = int64ToAuxInt(s) + v.AddArg2(ptr, mem) return true } return false From 1eec830f545ae9c75f143d7d5c757013d6d229be Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 10 Jul 2025 15:43:43 -0700 Subject: [PATCH 05/40] go/doc: linkify interface methods Create doc links for references to interface methods, such as "[Context.Err]". This does not attempt to handle embedded interfaces: The linked-to method must be declared within the named type. Fixes #54033 Change-Id: I4d7a132affe682c85958ca785bcc96571404e6c1 Reviewed-on: https://go-review.googlesource.com/c/go/+/687395 Reviewed-by: Robert Findley Auto-Submit: Damien Neil Reviewed-by: Sean Liao LUCI-TryBot-Result: Go LUCI --- src/go/doc/comment_test.go | 12 ++++++------ src/go/doc/doc.go | 28 ++++++++++++++++++++++++++++ src/go/doc/testdata/pkgdoc/doc.go | 6 +++++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/go/doc/comment_test.go b/src/go/doc/comment_test.go index 004ae9d13d6de6..0e7de3eb78f38f 100644 --- a/src/go/doc/comment_test.go +++ b/src/go/doc/comment_test.go @@ -24,12 +24,12 @@ func TestComment(t *testing.T) { pkg := New(pkgs["pkgdoc"], "testdata/pkgdoc", 0) var ( - input = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods.\n" - wantHTML = `

T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and crand.Reader are things. G.M1 and G.M2 are generic methods.` + "\n" - wantOldHTML = "

[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods.\n" - wantMarkdown = "[T](#T) and [U](#U) are types, and [T.M](#T.M) is a method, but \\[V] is a broken link. [rand.Int](/math/rand#Int) and [crand.Reader](/crypto/rand#Reader) are things. [G.M1](#G.M1) and [G.M2](#G.M2) are generic methods.\n" - wantText = "T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and\ncrand.Reader are things. G.M1 and G.M2 are generic methods.\n" - wantOldText = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link.\n[rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods.\n" + input = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods. [I.F] is an interface method and [I.V] is a broken link.\n" + wantHTML = `

T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and crand.Reader are things. G.M1 and G.M2 are generic methods. I.F is an interface method and [I.V] is a broken link.` + "\n" + wantOldHTML = "

[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods. [I.F] is an interface method and [I.V] is a broken link.\n" + wantMarkdown = "[T](#T) and [U](#U) are types, and [T.M](#T.M) is a method, but \\[V] is a broken link. [rand.Int](/math/rand#Int) and [crand.Reader](/crypto/rand#Reader) are things. [G.M1](#G.M1) and [G.M2](#G.M2) are generic methods. [I.F](#I.F) is an interface method and \\[I.V] is a broken link.\n" + wantText = "T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and\ncrand.Reader are things. G.M1 and G.M2 are generic methods. I.F is an interface\nmethod and [I.V] is a broken link.\n" + wantOldText = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link.\n[rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods.\n[I.F] is an interface method and [I.V] is a broken link.\n" wantSynopsis = "T and U are types, and T.M is a method, but [V] is a broken link." wantOldSynopsis = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link." ) diff --git a/src/go/doc/doc.go b/src/go/doc/doc.go index f7e3c1bad8207b..0c23f1a46c87fd 100644 --- a/src/go/doc/doc.go +++ b/src/go/doc/doc.go @@ -167,6 +167,7 @@ func (p *Package) collectTypes(types []*Type) { p.collectValues(t.Vars) p.collectFuncs(t.Funcs) p.collectFuncs(t.Methods) + p.collectInterfaceMethods(t) } } @@ -184,6 +185,33 @@ func (p *Package) collectFuncs(funcs []*Func) { } } +// collectInterfaceMethods adds methods of interface types within t to p.syms. +// Note that t.Methods will contain methods of non-interface types, but not interface types. +// Adding interface methods to t.Methods might make sense, but would cause us to +// include those methods in the documentation index. Adding interface methods to p.syms +// here allows us to linkify references like [io.Reader.Read] without making any other +// changes to the documentation formatting at this time. +// +// If we do start adding interface methods to t.Methods in the future, +// collectInterfaceMethods can be dropped as redundant with collectFuncs(t.Methods). +func (p *Package) collectInterfaceMethods(t *Type) { + for _, s := range t.Decl.Specs { + spec, ok := s.(*ast.TypeSpec) + if !ok { + continue + } + list, isStruct := fields(spec.Type) + if isStruct { + continue + } + for _, field := range list { + for _, name := range field.Names { + p.syms[t.Name+"."+name.Name] = true + } + } + } +} + // NewFromFiles computes documentation for a package. // // The package is specified by a list of *ast.Files and corresponding diff --git a/src/go/doc/testdata/pkgdoc/doc.go b/src/go/doc/testdata/pkgdoc/doc.go index 3f822c75546c63..d542dc2cdd0cb6 100644 --- a/src/go/doc/testdata/pkgdoc/doc.go +++ b/src/go/doc/testdata/pkgdoc/doc.go @@ -20,5 +20,9 @@ var _ = crand.Reader type G[T any] struct{ x T } -func (g G[T]) M1() {} +func (g G[T]) M1() {} func (g *G[T]) M2() {} + +type I interface { + F() +} From 355370ac52962a82a292492fdcbda4a52c9f6e7e Mon Sep 17 00:00:00 2001 From: Youlin Feng Date: Sat, 30 Aug 2025 17:18:10 +0800 Subject: [PATCH 06/40] runtime: add comment for concatstring2 People always want to remove concatstring{2,3,4,5} for performance, but we keep them to make the binary smaller. So, add a comment to document why. Updates #65020 Change-Id: I819976b700d45ce4d0846bf4481b2654b85708da Reviewed-on: https://go-review.googlesource.com/c/go/+/700095 Auto-Submit: Keith Randall Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall --- src/runtime/string.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/runtime/string.go b/src/runtime/string.go index 44d586bc53ee7d..3726d9235bfa4b 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -59,6 +59,9 @@ func concatstrings(buf *tmpBuf, a []string) string { return s } +// concatstring2 helps make the callsite smaller (compared to concatstrings), +// and we think this is currently more valuable than omitting one call in the +// chain, the same goes for concatstring{3,4,5}. func concatstring2(buf *tmpBuf, a0, a1 string) string { return concatstrings(buf, []string{a0, a1}) } @@ -108,6 +111,9 @@ func concatbytes(buf *tmpBuf, a []string) []byte { return b } +// concatbyte2 helps make the callsite smaller (compared to concatbytes), +// and we think this is currently more valuable than omitting one call in +// the chain, the same goes for concatbyte{3,4,5}. func concatbyte2(buf *tmpBuf, a0, a1 string) []byte { return concatbytes(buf, []string{a0, a1}) } From b09068041a20ed3cd44685a1d7f4dccbabfc2e94 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Tue, 2 Sep 2025 18:35:21 +0000 Subject: [PATCH 07/40] cmd/dist: run racebench tests only in longtest mode The racebench tests represent a significant portion of the race builders' runtimes, but these tests aren't providing a lot of value. Disable them and run them only on -longtest-race builders. For #32032. Change-Id: Ic4383c3f3b51d123ae9f5c377bc0e2ec02f2ddd7 Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-race,gotip-linux-amd64-longtest-race Reviewed-on: https://go-review.googlesource.com/c/go/+/700455 Reviewed-by: Dmitri Shuralyov Reviewed-by: Dmitri Shuralyov TryBot-Bypass: Michael Knyszek --- src/cmd/dist/test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index dfa4ffb522fa65..91e3716f07b1c5 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -677,7 +677,7 @@ func (t *tester) registerTests() { } t.registerStdTest(pkg) } - if t.race { + if t.race && !t.short { for _, pkg := range pkgs { if t.packageHasBenchmarks(pkg) { t.registerRaceBenchTest(pkg) From 2a7f1d47b0650c92b47f0cd5bc3536d438e4bbbe Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 2 Sep 2025 15:46:11 -0700 Subject: [PATCH 08/40] runtime: use one more address bit for tagged pointers We use one extra bit to placate systems which simulate amd64 binaries on an arm64 host. Allocated arm64 addresses could be as high as 1<<48-1, which would be invalid if we assumed 48-bit sign-extended addresses. (Note that this does not help the other way around, simluating arm64 on amd64, but we don't have that problem at the moment.) Fixes #69255 Change-Id: Iace17a5d41a65e34abf201d03d8b0ff6f7bf1150 Reviewed-on: https://go-review.googlesource.com/c/go/+/700515 Reviewed-by: Keith Randall Auto-Submit: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek --- src/runtime/tagptr_64bit.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/runtime/tagptr_64bit.go b/src/runtime/tagptr_64bit.go index 3d79332e2dcaff..76733cc1d64630 100644 --- a/src/runtime/tagptr_64bit.go +++ b/src/runtime/tagptr_64bit.go @@ -22,10 +22,17 @@ const ( // On AMD64, virtual addresses are 48-bit (or 57-bit) sign-extended. // Other archs are 48-bit zero-extended. // + // We use one extra bit to placate systems which simulate amd64 binaries on + // an arm64 host. Allocated arm64 addresses could be as high as 1<<48-1, + // which would be invalid if we assumed 48-bit sign-extended addresses. + // See issue 69255. + // (Note that this does not help the other way around, simluating arm64 + // on amd64, but we don't have that problem at the moment.) + // // On s390x, virtual addresses are 64-bit. There's not much we // can do about this, so we just hope that the kernel doesn't // get to really high addresses and panic if it does. - defaultAddrBits = 48 + defaultAddrBits = 48 + 1 // On AIX, 64-bit addresses are split into 36-bit segment number and 28-bit // offset in segment. Segment numbers in the range 0x0A0000000-0x0AFFFFFFF(LSA) From 3e596d448fb6b9668d144cffaa65e1d12a5fdce8 Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Mon, 25 Aug 2025 22:52:02 +0100 Subject: [PATCH 09/40] math: rename Modf parameter int to integer Avoid using int as a parameter name. Also, rename frac to fractional for consistency. Addresses comment on CL 694896: https://go-review.googlesource.com/c/go/+/694896/comment/a9723a07_8352e3aa/ Change-Id: Icedeecf65ad2f51d4e8d5bcf6e64c0eae9885dec Reviewed-on: https://go-review.googlesource.com/c/go/+/699035 Auto-Submit: Sean Liao Reviewed-by: Cherry Mui Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Sean Liao Reviewed-by: Joel Sing --- src/math/modf.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/math/modf.go b/src/math/modf.go index ab73e2dc36831e..12630958e969b7 100644 --- a/src/math/modf.go +++ b/src/math/modf.go @@ -11,8 +11,8 @@ package math // // Modf(±Inf) = ±Inf, NaN // Modf(NaN) = NaN, NaN -func Modf(f float64) (int float64, frac float64) { - int = Trunc(f) - frac = Copysign(f-int, f) +func Modf(f float64) (integer float64, fractional float64) { + integer = Trunc(f) + fractional = Copysign(f-integer, f) return } From 925a3cdcd13472c8f78d51c9ce99a59e77d46eb4 Mon Sep 17 00:00:00 2001 From: Julien Cretel Date: Tue, 2 Sep 2025 22:10:40 +0000 Subject: [PATCH 10/40] unicode/utf8: make DecodeRune{,InString} inlineable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change makes the fast path for ASCII characters inlineable in DecodeRune and DecodeRuneInString and removes most instances of manual inlining at call sites. Here are some benchmark results (no change to allocations): goos: darwin goarch: amd64 pkg: unicode/utf8 cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz │ old │ new │ │ sec/op │ sec/op vs base │ DecodeASCIIRune-8 2.4545n ± 2% 0.6253n ± 2% -74.52% (p=0.000 n=20) DecodeJapaneseRune-8 3.988n ± 1% 4.023n ± 1% +0.86% (p=0.050 n=20) DecodeASCIIRuneInString-8 2.4675n ± 1% 0.6264n ± 2% -74.61% (p=0.000 n=20) DecodeJapaneseRuneInString-8 3.992n ± 1% 4.001n ± 1% ~ (p=0.625 n=20) geomean 3.134n 1.585n -49.43% Note: when #61502 gets resolved, DecodeRune and DecodeRuneInString should be reverted to their idiomatic implementations. Fixes #31666 Updates #48195 Change-Id: I4be25c4f52417dc28b3a7bd72f1b04018470f39d GitHub-Last-Rev: 2e352a0045027e059be79cdb60241b5cf35fec71 GitHub-Pull-Request: golang/go#75181 Reviewed-on: https://go-review.googlesource.com/c/go/+/699675 Reviewed-by: Sean Liao LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Reviewed-by: Michael Pratt --- src/bufio/bufio.go | 5 +-- src/bytes/bytes.go | 40 +++++------------------ src/bytes/iter.go | 6 +--- src/cmd/compile/internal/test/inl_test.go | 2 ++ src/encoding/json/decode.go | 4 --- src/fmt/format.go | 5 +-- src/fmt/print.go | 5 +-- src/regexp/regexp.go | 28 +++------------- src/strconv/quote.go | 8 ++--- src/strings/iter.go | 6 +--- src/strings/reader.go | 4 --- src/strings/strings.go | 16 +++------ src/unicode/utf8/utf8.go | 26 +++++++++++++++ src/unicode/utf8/utf8_test.go | 27 ++++++++++++--- 14 files changed, 74 insertions(+), 108 deletions(-) diff --git a/src/bufio/bufio.go b/src/bufio/bufio.go index 5244ce2e0ca943..141a9a1a2a2305 100644 --- a/src/bufio/bufio.go +++ b/src/bufio/bufio.go @@ -311,10 +311,7 @@ func (b *Reader) ReadRune() (r rune, size int, err error) { if b.r == b.w { return 0, 0, b.readErr() } - r, size = rune(b.buf[b.r]), 1 - if r >= utf8.RuneSelf { - r, size = utf8.DecodeRune(b.buf[b.r:b.w]) - } + r, size = utf8.DecodeRune(b.buf[b.r:b.w]) b.r += size b.lastByte = int(b.buf[b.r-1]) b.lastRuneSize = size diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go index ce2e0049102234..9a7f4ee3c93afb 100644 --- a/src/bytes/bytes.go +++ b/src/bytes/bytes.go @@ -528,11 +528,7 @@ func FieldsFunc(s []byte, f func(rune) bool) [][]byte { // more efficient, possibly due to cache effects. start := -1 // valid span start if >= 0 for i := 0; i < len(s); { - size := 1 - r := rune(s[i]) - if r >= utf8.RuneSelf { - r, size = utf8.DecodeRune(s[i:]) - } + r, size := utf8.DecodeRune(s[i:]) if f(r) { if start >= 0 { spans = append(spans, span{start, i}) @@ -614,11 +610,7 @@ func Map(mapping func(r rune) rune, s []byte) []byte { // fine. It could also shrink but that falls out naturally. b := make([]byte, 0, len(s)) for i := 0; i < len(s); { - wid := 1 - r := rune(s[i]) - if r >= utf8.RuneSelf { - r, wid = utf8.DecodeRune(s[i:]) - } + r, wid := utf8.DecodeRune(s[i:]) r = mapping(r) if r >= 0 { b = utf8.AppendRune(b, r) @@ -917,11 +909,7 @@ func LastIndexFunc(s []byte, f func(r rune) bool) int { func indexFunc(s []byte, f func(r rune) bool, truth bool) int { start := 0 for start < len(s) { - wid := 1 - r := rune(s[start]) - if r >= utf8.RuneSelf { - r, wid = utf8.DecodeRune(s[start:]) - } + r, wid := utf8.DecodeRune(s[start:]) if f(r) == truth { return start } @@ -1052,10 +1040,7 @@ func trimLeftASCII(s []byte, as *asciiSet) []byte { func trimLeftUnicode(s []byte, cutset string) []byte { for len(s) > 0 { - r, n := rune(s[0]), 1 - if r >= utf8.RuneSelf { - r, n = utf8.DecodeRune(s) - } + r, n := utf8.DecodeRune(s) if !containsRune(cutset, r) { break } @@ -1251,19 +1236,10 @@ hasUnicode: t = t[i:] for len(s) != 0 && len(t) != 0 { // Extract first rune from each. - var sr, tr rune - if s[0] < utf8.RuneSelf { - sr, s = rune(s[0]), s[1:] - } else { - r, size := utf8.DecodeRune(s) - sr, s = r, s[size:] - } - if t[0] < utf8.RuneSelf { - tr, t = rune(t[0]), t[1:] - } else { - r, size := utf8.DecodeRune(t) - tr, t = r, t[size:] - } + sr, size := utf8.DecodeRune(s) + s = s[size:] + tr, size := utf8.DecodeRune(t) + t = t[size:] // If they match, keep going; if not, return false. diff --git a/src/bytes/iter.go b/src/bytes/iter.go index b2abb2c9ba3dc6..a4ece881d20fa1 100644 --- a/src/bytes/iter.go +++ b/src/bytes/iter.go @@ -117,11 +117,7 @@ func FieldsFuncSeq(s []byte, f func(rune) bool) iter.Seq[[]byte] { return func(yield func([]byte) bool) { start := -1 for i := 0; i < len(s); { - size := 1 - r := rune(s[i]) - if r >= utf8.RuneSelf { - r, size = utf8.DecodeRune(s[i:]) - } + r, size := utf8.DecodeRune(s[i:]) if f(r) { if start >= 0 { if !yield(s[start:i:i]) { diff --git a/src/cmd/compile/internal/test/inl_test.go b/src/cmd/compile/internal/test/inl_test.go index eda6084b48e7cc..a49cd767db43d8 100644 --- a/src/cmd/compile/internal/test/inl_test.go +++ b/src/cmd/compile/internal/test/inl_test.go @@ -125,6 +125,8 @@ func TestIntendedInlining(t *testing.T) { "assemble64", }, "unicode/utf8": { + "DecodeRune", + "DecodeRuneInString", "FullRune", "FullRuneInString", "RuneLen", diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 70885a517e1876..fc29296c0f464f 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -1214,10 +1214,6 @@ func unquoteBytes(s []byte) (t []byte, ok bool) { if c == '\\' || c == '"' || c < ' ' { break } - if c < utf8.RuneSelf { - r++ - continue - } rr, size := utf8.DecodeRune(s[r:]) if rr == utf8.RuneError && size == 1 { break diff --git a/src/fmt/format.go b/src/fmt/format.go index 90e18cd696375f..334a94e2983e63 100644 --- a/src/fmt/format.go +++ b/src/fmt/format.go @@ -346,10 +346,7 @@ func (f *fmt) truncate(b []byte) []byte { if n < 0 { return b[:i] } - wid := 1 - if b[i] >= utf8.RuneSelf { - _, wid = utf8.DecodeRune(b[i:]) - } + _, wid := utf8.DecodeRune(b[i:]) i += wid } } diff --git a/src/fmt/print.go b/src/fmt/print.go index 155218046f47ce..01cfa1a1c7d7b4 100644 --- a/src/fmt/print.go +++ b/src/fmt/print.go @@ -1145,10 +1145,7 @@ formatLoop: break } - verb, size := rune(format[i]), 1 - if verb >= utf8.RuneSelf { - verb, size = utf8.DecodeRuneInString(format[i:]) - } + verb, size := utf8.DecodeRuneInString(format[i:]) i += size switch { diff --git a/src/regexp/regexp.go b/src/regexp/regexp.go index 253415fb6a44c6..66c73693995a42 100644 --- a/src/regexp/regexp.go +++ b/src/regexp/regexp.go @@ -384,10 +384,6 @@ type inputString struct { func (i *inputString) step(pos int) (rune, int) { if pos < len(i.str) { - c := i.str[pos] - if c < utf8.RuneSelf { - return rune(c), 1 - } return utf8.DecodeRuneInString(i.str[pos:]) } return endOfText, 0 @@ -409,17 +405,11 @@ func (i *inputString) context(pos int) lazyFlag { r1, r2 := endOfText, endOfText // 0 < pos && pos <= len(i.str) if uint(pos-1) < uint(len(i.str)) { - r1 = rune(i.str[pos-1]) - if r1 >= utf8.RuneSelf { - r1, _ = utf8.DecodeLastRuneInString(i.str[:pos]) - } + r1, _ = utf8.DecodeLastRuneInString(i.str[:pos]) } // 0 <= pos && pos < len(i.str) if uint(pos) < uint(len(i.str)) { - r2 = rune(i.str[pos]) - if r2 >= utf8.RuneSelf { - r2, _ = utf8.DecodeRuneInString(i.str[pos:]) - } + r2, _ = utf8.DecodeRuneInString(i.str[pos:]) } return newLazyFlag(r1, r2) } @@ -431,10 +421,6 @@ type inputBytes struct { func (i *inputBytes) step(pos int) (rune, int) { if pos < len(i.str) { - c := i.str[pos] - if c < utf8.RuneSelf { - return rune(c), 1 - } return utf8.DecodeRune(i.str[pos:]) } return endOfText, 0 @@ -456,17 +442,11 @@ func (i *inputBytes) context(pos int) lazyFlag { r1, r2 := endOfText, endOfText // 0 < pos && pos <= len(i.str) if uint(pos-1) < uint(len(i.str)) { - r1 = rune(i.str[pos-1]) - if r1 >= utf8.RuneSelf { - r1, _ = utf8.DecodeLastRune(i.str[:pos]) - } + r1, _ = utf8.DecodeLastRune(i.str[:pos]) } // 0 <= pos && pos < len(i.str) if uint(pos) < uint(len(i.str)) { - r2 = rune(i.str[pos]) - if r2 >= utf8.RuneSelf { - r2, _ = utf8.DecodeRune(i.str[pos:]) - } + r2, _ = utf8.DecodeRune(i.str[pos:]) } return newLazyFlag(r1, r2) } diff --git a/src/strconv/quote.go b/src/strconv/quote.go index 99c292a8ed5884..da2325647d3817 100644 --- a/src/strconv/quote.go +++ b/src/strconv/quote.go @@ -37,12 +37,8 @@ func appendQuotedWith(buf []byte, s string, quote byte, ASCIIonly, graphicOnly b buf = nBuf } buf = append(buf, quote) - for width := 0; len(s) > 0; s = s[width:] { - r := rune(s[0]) - width = 1 - if r >= utf8.RuneSelf { - r, width = utf8.DecodeRuneInString(s) - } + for r, width := rune(0), 0; len(s) > 0; s = s[width:] { + r, width = utf8.DecodeRuneInString(s) if width == 1 && r == utf8.RuneError { buf = append(buf, `\x`...) buf = append(buf, lowerhex[s[0]>>4]) diff --git a/src/strings/iter.go b/src/strings/iter.go index 69fe031739628c..84e763a8343df4 100644 --- a/src/strings/iter.go +++ b/src/strings/iter.go @@ -117,11 +117,7 @@ func FieldsFuncSeq(s string, f func(rune) bool) iter.Seq[string] { return func(yield func(string) bool) { start := -1 for i := 0; i < len(s); { - size := 1 - r := rune(s[i]) - if r >= utf8.RuneSelf { - r, size = utf8.DecodeRuneInString(s[i:]) - } + r, size := utf8.DecodeRuneInString(s[i:]) if f(r) { if start >= 0 { if !yield(s[start:i]) { diff --git a/src/strings/reader.go b/src/strings/reader.go index 497ffb7a39c635..f12c9b18b36d43 100644 --- a/src/strings/reader.go +++ b/src/strings/reader.go @@ -90,10 +90,6 @@ func (r *Reader) ReadRune() (ch rune, size int, err error) { return 0, 0, io.EOF } r.prevRune = int(r.i) - if c := r.s[r.i]; c < utf8.RuneSelf { - r.i++ - return rune(c), 1, nil - } ch, size = utf8.DecodeRuneInString(r.s[r.i:]) r.i += int64(size) return diff --git a/src/strings/strings.go b/src/strings/strings.go index 74007977d911f0..3cc3e79f982248 100644 --- a/src/strings/strings.go +++ b/src/strings/strings.go @@ -896,7 +896,7 @@ func TrimLeftFunc(s string, f func(rune) bool) string { // Unicode code points c satisfying f(c) removed. func TrimRightFunc(s string, f func(rune) bool) string { i := lastIndexFunc(s, f, false) - if i >= 0 && s[i] >= utf8.RuneSelf { + if i >= 0 { _, wid := utf8.DecodeRuneInString(s[i:]) i += wid } else { @@ -1028,10 +1028,7 @@ func trimLeftASCII(s string, as *asciiSet) string { func trimLeftUnicode(s, cutset string) string { for len(s) > 0 { - r, n := rune(s[0]), 1 - if r >= utf8.RuneSelf { - r, n = utf8.DecodeRuneInString(s) - } + r, n := utf8.DecodeRuneInString(s) if !ContainsRune(cutset, r) { break } @@ -1224,13 +1221,8 @@ hasUnicode: } // Extract first rune from second string. - var tr rune - if t[0] < utf8.RuneSelf { - tr, t = rune(t[0]), t[1:] - } else { - r, size := utf8.DecodeRuneInString(t) - tr, t = r, t[size:] - } + tr, size := utf8.DecodeRuneInString(t) + t = t[size:] // If they match, keep going; if not, return false. diff --git a/src/unicode/utf8/utf8.go b/src/unicode/utf8/utf8.go index 01cad1cc81f880..68283341d92ace 100644 --- a/src/unicode/utf8/utf8.go +++ b/src/unicode/utf8/utf8.go @@ -155,6 +155,20 @@ func FullRuneInString(s string) bool { // out of range, or is not the shortest possible UTF-8 encoding for the // value. No other validation is performed. func DecodeRune(p []byte) (r rune, size int) { + // Inlineable fast path for ASCII characters; see #48195. + // This implementation is weird but effective at rendering the + // function inlineable. + for _, b := range p { + if b < RuneSelf { + return rune(b), 1 + } + break + } + r, size = decodeRuneSlow(p) + return +} + +func decodeRuneSlow(p []byte) (r rune, size int) { n := len(p) if n < 1 { return RuneError, 0 @@ -203,6 +217,18 @@ func DecodeRune(p []byte) (r rune, size int) { // out of range, or is not the shortest possible UTF-8 encoding for the // value. No other validation is performed. func DecodeRuneInString(s string) (r rune, size int) { + // Inlineable fast path for ASCII characters; see #48195. + // This implementation is a bit weird but effective at rendering the + // function inlineable. + if s != "" && s[0] < RuneSelf { + return rune(s[0]), 1 + } else { + r, size = decodeRuneInStringSlow(s) + } + return +} + +func decodeRuneInStringSlow(s string) (rune, int) { n := len(s) if n < 1 { return RuneError, 0 diff --git a/src/unicode/utf8/utf8_test.go b/src/unicode/utf8/utf8_test.go index aece0fab731f41..bf4f074ffd0f5f 100644 --- a/src/unicode/utf8/utf8_test.go +++ b/src/unicode/utf8/utf8_test.go @@ -747,18 +747,37 @@ func BenchmarkAppendInvalidRuneNegative(b *testing.B) { func BenchmarkDecodeASCIIRune(b *testing.B) { a := []byte{'a'} - for i := 0; i < b.N; i++ { - DecodeRune(a) + for range b.N { + runeSink, sizeSink = DecodeRune(a) } } func BenchmarkDecodeJapaneseRune(b *testing.B) { nihon := []byte("本") - for i := 0; i < b.N; i++ { - DecodeRune(nihon) + for range b.N { + runeSink, sizeSink = DecodeRune(nihon) + } +} + +func BenchmarkDecodeASCIIRuneInString(b *testing.B) { + a := "a" + for range b.N { + runeSink, sizeSink = DecodeRuneInString(a) } } +func BenchmarkDecodeJapaneseRuneInString(b *testing.B) { + nihon := "本" + for range b.N { + runeSink, sizeSink = DecodeRuneInString(nihon) + } +} + +var ( + runeSink rune + sizeSink int +) + // boolSink is used to reference the return value of benchmarked // functions to avoid dead code elimination. var boolSink bool From 4c4cefc19a16924f3aa7135d3fdc6d1687fe26c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Sat, 30 Aug 2025 18:54:43 +0100 Subject: [PATCH 11/40] cmd/gofmt: simplify logic to process arguments Rather than stat-ing each argument and taking different code paths depending on whether it's a directory or not, we can leverage the fact that filepath.WalkDir works on regular files and already has to figure out whether each file it walks is a directory or not. We can then implement "always format non-directory arguments" by looking at whether the path we are walking is the original argument, meaning we are walking the top file. For full clarity, we expand the skipping logic with a switch, as before it was a bit confusing how we could `return err` on directories and other non-Go files. Given that we discard directories separately now, simplify isGoFile to just be about filenames. While here, also note that we called AddReport inside WalkDir; this is unnecessary, as we can return the error for the same effect. Change-Id: I50ab94710143f19bd8dd95a69e01a3dd228e397e Reviewed-on: https://go-review.googlesource.com/c/go/+/700115 Reviewed-by: Sean Liao Reviewed-by: Michael Pratt Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- src/cmd/gofmt/gofmt.go | 54 +++++++++++++++++--------------------- src/cmd/gofmt/long_test.go | 2 +- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/cmd/gofmt/gofmt.go b/src/cmd/gofmt/gofmt.go index d91a75b1050e20..bbb8b4fd15c2f7 100644 --- a/src/cmd/gofmt/gofmt.go +++ b/src/cmd/gofmt/gofmt.go @@ -87,10 +87,8 @@ func initParserMode() { } } -func isGoFile(f fs.DirEntry) bool { - // ignore non-Go files - name := f.Name() - return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && !f.IsDir() +func isGoFilename(name string) bool { + return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") } // A sequencer performs concurrent tasks that may write output, but emits that @@ -411,34 +409,30 @@ func gofmtMain(s *sequencer) { } for _, arg := range args { - switch info, err := os.Stat(arg); { - case err != nil: - s.AddReport(err) - case !info.IsDir(): - // Non-directory arguments are always formatted. - arg := arg - s.Add(fileWeight(arg, info), func(r *reporter) error { - return processFile(arg, info, nil, r) - }) - default: - // Directories are walked, ignoring non-Go files. - err := filepath.WalkDir(arg, func(path string, f fs.DirEntry, err error) error { - if err != nil || !isGoFile(f) { - return err - } - info, err := f.Info() - if err != nil { - s.AddReport(err) - return nil - } - s.Add(fileWeight(path, info), func(r *reporter) error { - return processFile(path, info, nil, r) - }) - return nil - }) + // Walk each given argument as a directory tree. + // If the argument is not a directory, it's always formatted as a Go file. + // If the argument is a directory, we walk it, ignoring non-Go files. + if err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error { + switch { + case err != nil: + return err + case d.IsDir(): + return nil // simply recurse into directories + case path == arg: + // non-directories given as explicit arguments are always formatted + case !isGoFilename(d.Name()): + return nil // skip walked non-Go files + } + info, err := d.Info() if err != nil { - s.AddReport(err) + return err } + s.Add(fileWeight(path, info), func(r *reporter) error { + return processFile(path, info, nil, r) + }) + return nil + }); err != nil { + s.AddReport(err) } } } diff --git a/src/cmd/gofmt/long_test.go b/src/cmd/gofmt/long_test.go index 21a01196cf6cc2..372e324387843d 100644 --- a/src/cmd/gofmt/long_test.go +++ b/src/cmd/gofmt/long_test.go @@ -115,7 +115,7 @@ func genFilenames(t *testing.T, filenames chan<- string) { return nil } // don't descend into testdata directories - if isGoFile(d) && !strings.Contains(filepath.ToSlash(filename), "/testdata/") { + if !d.IsDir() && isGoFilename(d.Name()) && !strings.Contains(filepath.ToSlash(filename), "/testdata/") { filenames <- filename nfiles++ } From 731e54616686889146c579317c7d1715c85a8505 Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Mon, 1 Sep 2025 15:49:34 +0800 Subject: [PATCH 12/40] cmd/compile: simplify the support for 32bit high multiply on loong64 Removes 152 instructions from the Go binary on loong64. Change-Id: Icf8ead4f4ca965f51add85ac5e45c3cca8916401 Reviewed-on: https://go-review.googlesource.com/c/go/+/700335 Reviewed-by: Keith Randall Auto-Submit: Michael Pratt Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Meidan Li Reviewed-by: abner chenc --- src/cmd/compile/internal/loong64/ssa.go | 2 +- .../compile/internal/ssa/_gen/LOONG64.rules | 4 +- .../compile/internal/ssa/_gen/LOONG64Ops.go | 2 + src/cmd/compile/internal/ssa/opGen.go | 32 ++++++++++++ .../compile/internal/ssa/rewriteLOONG64.go | 50 ++----------------- 5 files changed, 41 insertions(+), 49 deletions(-) diff --git a/src/cmd/compile/internal/loong64/ssa.go b/src/cmd/compile/internal/loong64/ssa.go index bdc8e37b04fe9d..c917d14298d425 100644 --- a/src/cmd/compile/internal/loong64/ssa.go +++ b/src/cmd/compile/internal/loong64/ssa.go @@ -185,7 +185,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { ssa.OpLOONG64MULD, ssa.OpLOONG64DIVF, ssa.OpLOONG64DIVD, - ssa.OpLOONG64MULV, ssa.OpLOONG64MULHV, ssa.OpLOONG64MULHVU, + ssa.OpLOONG64MULV, ssa.OpLOONG64MULHV, ssa.OpLOONG64MULHVU, ssa.OpLOONG64MULH, ssa.OpLOONG64MULHU, ssa.OpLOONG64DIVV, ssa.OpLOONG64REMV, ssa.OpLOONG64DIVVU, ssa.OpLOONG64REMVU, ssa.OpLOONG64FCOPYSGD: p := s.Prog(v.Op.Asm()) diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules index 6dd28c6d4544e6..a4a6e901e32960 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules @@ -17,8 +17,8 @@ (Hmul64 ...) => (MULHV ...) (Hmul64u ...) => (MULHVU ...) -(Hmul32 x y) => (SRAVconst (MULV (SignExt32to64 x) (SignExt32to64 y)) [32]) -(Hmul32u x y) => (SRLVconst (MULV (ZeroExt32to64 x) (ZeroExt32to64 y)) [32]) +(Hmul32 ...) => (MULH ...) +(Hmul32u ...) => (MULHU ...) (Div64 x y) => (DIVV x y) (Div64u ...) => (DIVVU ...) diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go index ed635bfd977eb4..359cb42056a61c 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go @@ -197,6 +197,8 @@ func init() { {name: "MULV", argLength: 2, reg: gp21, asm: "MULV", commutative: true, typ: "Int64"}, // arg0 * arg1 {name: "MULHV", argLength: 2, reg: gp21, asm: "MULHV", commutative: true, typ: "Int64"}, // (arg0 * arg1) >> 64, signed {name: "MULHVU", argLength: 2, reg: gp21, asm: "MULHVU", commutative: true, typ: "UInt64"}, // (arg0 * arg1) >> 64, unsigned + {name: "MULH", argLength: 2, reg: gp21, asm: "MULH", commutative: true, typ: "Int32"}, // (arg0 * arg1) >> 32, signed + {name: "MULHU", argLength: 2, reg: gp21, asm: "MULHU", commutative: true, typ: "UInt32"}, // (arg0 * arg1) >> 32, unsigned {name: "DIVV", argLength: 2, reg: gp21, asm: "DIVV", typ: "Int64"}, // arg0 / arg1, signed {name: "DIVVU", argLength: 2, reg: gp21, asm: "DIVVU", typ: "UInt64"}, // arg0 / arg1, unsigned {name: "REMV", argLength: 2, reg: gp21, asm: "REMV", typ: "Int64"}, // arg0 / arg1, signed diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 7bdb14cec9a5de..fca7d810175491 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1795,6 +1795,8 @@ const ( OpLOONG64MULV OpLOONG64MULHV OpLOONG64MULHVU + OpLOONG64MULH + OpLOONG64MULHU OpLOONG64DIVV OpLOONG64DIVVU OpLOONG64REMV @@ -24139,6 +24141,36 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "MULH", + argLen: 2, + commutative: true, + asm: loong64.AMULH, + reg: regInfo{ + inputs: []inputInfo{ + {0, 1073741816}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 g R23 R24 R25 R26 R27 R28 R29 R31 + {1, 1073741817}, // ZERO R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 g R23 R24 R25 R26 R27 R28 R29 R31 + }, + outputs: []outputInfo{ + {0, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31 + }, + }, + }, + { + name: "MULHU", + argLen: 2, + commutative: true, + asm: loong64.AMULHU, + reg: regInfo{ + inputs: []inputInfo{ + {0, 1073741816}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 g R23 R24 R25 R26 R27 R28 R29 R31 + {1, 1073741817}, // ZERO R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 g R23 R24 R25 R26 R27 R28 R29 R31 + }, + outputs: []outputInfo{ + {0, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31 + }, + }, + }, { name: "DIVV", argLen: 2, diff --git a/src/cmd/compile/internal/ssa/rewriteLOONG64.go b/src/cmd/compile/internal/ssa/rewriteLOONG64.go index 6407628a8b1082..00731f4b51c938 100644 --- a/src/cmd/compile/internal/ssa/rewriteLOONG64.go +++ b/src/cmd/compile/internal/ssa/rewriteLOONG64.go @@ -296,9 +296,11 @@ func rewriteValueLOONG64(v *Value) bool { v.Op = OpLOONG64LoweredGetClosurePtr return true case OpHmul32: - return rewriteValueLOONG64_OpHmul32(v) + v.Op = OpLOONG64MULH + return true case OpHmul32u: - return rewriteValueLOONG64_OpHmul32u(v) + v.Op = OpLOONG64MULHU + return true case OpHmul64: v.Op = OpLOONG64MULHV return true @@ -1576,50 +1578,6 @@ func rewriteValueLOONG64_OpEqPtr(v *Value) bool { return true } } -func rewriteValueLOONG64_OpHmul32(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - b := v.Block - typ := &b.Func.Config.Types - // match: (Hmul32 x y) - // result: (SRAVconst (MULV (SignExt32to64 x) (SignExt32to64 y)) [32]) - for { - x := v_0 - y := v_1 - v.reset(OpLOONG64SRAVconst) - v.AuxInt = int64ToAuxInt(32) - v0 := b.NewValue0(v.Pos, OpLOONG64MULV, typ.Int64) - v1 := b.NewValue0(v.Pos, OpSignExt32to64, typ.Int64) - v1.AddArg(x) - v2 := b.NewValue0(v.Pos, OpSignExt32to64, typ.Int64) - v2.AddArg(y) - v0.AddArg2(v1, v2) - v.AddArg(v0) - return true - } -} -func rewriteValueLOONG64_OpHmul32u(v *Value) bool { - v_1 := v.Args[1] - v_0 := v.Args[0] - b := v.Block - typ := &b.Func.Config.Types - // match: (Hmul32u x y) - // result: (SRLVconst (MULV (ZeroExt32to64 x) (ZeroExt32to64 y)) [32]) - for { - x := v_0 - y := v_1 - v.reset(OpLOONG64SRLVconst) - v.AuxInt = int64ToAuxInt(32) - v0 := b.NewValue0(v.Pos, OpLOONG64MULV, typ.Int64) - v1 := b.NewValue0(v.Pos, OpZeroExt32to64, typ.UInt64) - v1.AddArg(x) - v2 := b.NewValue0(v.Pos, OpZeroExt32to64, typ.UInt64) - v2.AddArg(y) - v0.AddArg2(v1, v2) - v.AddArg(v0) - return true - } -} func rewriteValueLOONG64_OpIsInBounds(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] From e8f9127d1f47ea9cf252237d387ea61d17651c3e Mon Sep 17 00:00:00 2001 From: database64128 Date: Tue, 2 Sep 2025 15:44:42 +0800 Subject: [PATCH 13/40] net/netip: export Prefix.Compare, fix ordering Fixes #61642 Co-authored-by: David Anderson Change-Id: I54795763bdc5f62da469c2ae20618c36b64396f3 Reviewed-on: https://go-review.googlesource.com/c/go/+/700355 LUCI-TryBot-Result: Go LUCI Reviewed-by: Damien Neil Auto-Submit: Michael Pratt Reviewed-by: Michael Pratt --- api/next/61642.txt | 1 + doc/next/6-stdlib/99-minor/net/netip/61642.md | 1 + src/net/netip/export_test.go | 2 - src/net/netip/netip.go | 18 ++--- src/net/netip/netip_test.go | 65 ++++++++++++++++++- 5 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 api/next/61642.txt create mode 100644 doc/next/6-stdlib/99-minor/net/netip/61642.md diff --git a/api/next/61642.txt b/api/next/61642.txt new file mode 100644 index 00000000000000..dd67874ab9fba4 --- /dev/null +++ b/api/next/61642.txt @@ -0,0 +1 @@ +pkg net/netip, method (Prefix) Compare(Prefix) int #61642 diff --git a/doc/next/6-stdlib/99-minor/net/netip/61642.md b/doc/next/6-stdlib/99-minor/net/netip/61642.md new file mode 100644 index 00000000000000..3d79f2e76aee04 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/net/netip/61642.md @@ -0,0 +1 @@ +The new [Prefix.Compare] method compares two prefixes. diff --git a/src/net/netip/export_test.go b/src/net/netip/export_test.go index b2fae1aa47eedc..777a76a6b26401 100644 --- a/src/net/netip/export_test.go +++ b/src/net/netip/export_test.go @@ -34,5 +34,3 @@ var TestAppendToMarshal = testAppendToMarshal func (a Addr) IsZero() bool { return a.isZero() } func (p Prefix) IsZero() bool { return p.isZero() } - -func (p Prefix) Compare(p2 Prefix) int { return p.compare(p2) } diff --git a/src/net/netip/netip.go b/src/net/netip/netip.go index 35abfd3241bc13..b1b15b47287de2 100644 --- a/src/net/netip/netip.go +++ b/src/net/netip/netip.go @@ -1330,21 +1330,23 @@ func (p Prefix) isZero() bool { return p == Prefix{} } // IsSingleIP reports whether p contains exactly one IP. func (p Prefix) IsSingleIP() bool { return p.IsValid() && p.Bits() == p.ip.BitLen() } -// compare returns an integer comparing two prefixes. +// Compare returns an integer comparing two prefixes. // The result will be 0 if p == p2, -1 if p < p2, and +1 if p > p2. // Prefixes sort first by validity (invalid before valid), then -// address family (IPv4 before IPv6), then prefix length, then -// address. -// -// Unexported for Go 1.22 because we may want to compare by p.Addr first. -// See post-acceptance discussion on go.dev/issue/61642. -func (p Prefix) compare(p2 Prefix) int { - if c := cmp.Compare(p.Addr().BitLen(), p2.Addr().BitLen()); c != 0 { +// address family (IPv4 before IPv6), then masked prefix address, then +// prefix length, then unmasked address. +func (p Prefix) Compare(p2 Prefix) int { + // Aside from sorting based on the masked address, this use of + // Addr.Compare also enforces the valid vs. invalid and address + // family ordering for the prefix. + if c := p.Masked().Addr().Compare(p2.Masked().Addr()); c != 0 { return c } + if c := cmp.Compare(p.Bits(), p2.Bits()); c != 0 { return c } + return p.Addr().Compare(p2.Addr()) } diff --git a/src/net/netip/netip_test.go b/src/net/netip/netip_test.go index ea03f9a9e72473..71e39021ca8969 100644 --- a/src/net/netip/netip_test.go +++ b/src/net/netip/netip_test.go @@ -1123,6 +1123,9 @@ func TestPrefixCompare(t *testing.T) { {mustPrefix("fe80::/48"), mustPrefix("fe80::/64"), -1}, {mustPrefix("1.2.3.0/24"), mustPrefix("fe80::/8"), -1}, + + {mustPrefix("1.2.3.0/24"), mustPrefix("1.2.3.4/24"), -1}, + {mustPrefix("1.2.3.0/24"), mustPrefix("1.2.3.0/28"), -1}, } for _, tt := range tests { got := tt.a.Compare(tt.b) @@ -1148,10 +1151,70 @@ func TestPrefixCompare(t *testing.T) { Prefix{}, mustPrefix("fe80::/48"), mustPrefix("1.2.0.0/24"), + mustPrefix("1.2.3.4/24"), + mustPrefix("1.2.3.0/28"), } slices.SortFunc(values, Prefix.Compare) got := fmt.Sprintf("%s", values) - want := `[invalid Prefix 1.2.0.0/16 1.2.0.0/24 1.2.3.0/24 fe80::/48 fe80::/64 fe90::/64]` + want := `[invalid Prefix 1.2.0.0/16 1.2.0.0/24 1.2.3.0/24 1.2.3.4/24 1.2.3.0/28 fe80::/48 fe80::/64 fe90::/64]` + if got != want { + t.Errorf("unexpected sort\n got: %s\nwant: %s\n", got, want) + } + + // Lists from + // https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml and + // https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml, + // to verify that the sort order matches IANA's conventional + // ordering. + values = []Prefix{ + mustPrefix("0.0.0.0/8"), + mustPrefix("127.0.0.0/8"), + mustPrefix("10.0.0.0/8"), + mustPrefix("203.0.113.0/24"), + mustPrefix("169.254.0.0/16"), + mustPrefix("192.0.0.0/24"), + mustPrefix("240.0.0.0/4"), + mustPrefix("192.0.2.0/24"), + mustPrefix("192.0.0.170/32"), + mustPrefix("198.18.0.0/15"), + mustPrefix("192.0.0.8/32"), + mustPrefix("0.0.0.0/32"), + mustPrefix("192.0.0.9/32"), + mustPrefix("198.51.100.0/24"), + mustPrefix("192.168.0.0/16"), + mustPrefix("192.0.0.10/32"), + mustPrefix("192.175.48.0/24"), + mustPrefix("192.52.193.0/24"), + mustPrefix("100.64.0.0/10"), + mustPrefix("255.255.255.255/32"), + mustPrefix("192.31.196.0/24"), + mustPrefix("172.16.0.0/12"), + mustPrefix("192.0.0.0/29"), + mustPrefix("192.88.99.0/24"), + mustPrefix("fec0::/10"), + mustPrefix("6000::/3"), + mustPrefix("fe00::/9"), + mustPrefix("8000::/3"), + mustPrefix("0000::/8"), + mustPrefix("0400::/6"), + mustPrefix("f800::/6"), + mustPrefix("e000::/4"), + mustPrefix("ff00::/8"), + mustPrefix("a000::/3"), + mustPrefix("fc00::/7"), + mustPrefix("1000::/4"), + mustPrefix("0800::/5"), + mustPrefix("4000::/3"), + mustPrefix("0100::/8"), + mustPrefix("c000::/3"), + mustPrefix("fe80::/10"), + mustPrefix("0200::/7"), + mustPrefix("f000::/5"), + mustPrefix("2000::/3"), + } + slices.SortFunc(values, func(a, b Prefix) int { return a.Compare(b) }) + got = fmt.Sprintf("%s", values) + want = `[0.0.0.0/8 0.0.0.0/32 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24 192.0.0.0/29 192.0.0.8/32 192.0.0.9/32 192.0.0.10/32 192.0.0.170/32 192.0.2.0/24 192.31.196.0/24 192.52.193.0/24 192.88.99.0/24 192.168.0.0/16 192.175.48.0/24 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 240.0.0.0/4 255.255.255.255/32 ::/8 100::/8 200::/7 400::/6 800::/5 1000::/4 2000::/3 4000::/3 6000::/3 8000::/3 a000::/3 c000::/3 e000::/4 f000::/5 f800::/6 fc00::/7 fe00::/9 fe80::/10 fec0::/10 ff00::/8]` if got != want { t.Errorf("unexpected sort\n got: %s\nwant: %s\n", got, want) } From c552ad913fd7117a70cc3abee6b44a15aac060e2 Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Wed, 3 Sep 2025 09:41:08 +0800 Subject: [PATCH 14/40] cmd/compile: simplify memory load and store operations on loong64 If the memory load and store operations use the same ptr, they are combined into a direct move operation between registers, like riscv64. Change-Id: I889e51a5146aee7f15340114bc4abd12eb6b8a1f Reviewed-on: https://go-review.googlesource.com/c/go/+/700535 Reviewed-by: Keith Randall Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: abner chenc Reviewed-by: Michael Pratt Auto-Submit: Keith Randall --- .../compile/internal/ssa/_gen/LOONG64.rules | 4 + .../compile/internal/ssa/rewriteLOONG64.go | 119 ++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules index a4a6e901e32960..cb55c16c3e7abf 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules @@ -455,6 +455,10 @@ (MOVWUload [off] {sym} ptr (MOVFstore [off] {sym} ptr val _)) => (ZeroExt32to64 (MOVWfpgp val)) (MOVFload [off] {sym} ptr (MOVWstore [off] {sym} ptr val _)) => (MOVWgpfp val) +// If the memory load and store operations use the same ptr, they are combined into a direct move operation between registers. +(MOV(V|W|H|B)load [off] {sym} ptr (MOV(V|W|H|B)store [off] {sym} ptr x _)) => (MOV(V|W|H|B)reg x) +(MOV(W|H|B)Uload [off] {sym} ptr (MOV(W|H|B)store [off] {sym} ptr x _)) => (MOV(W|H|B)Ureg x) + // Similarly for stores, if we see a store after FPR <=> GPR move, then redirect store to use the other register set. (MOVVstore [off] {sym} ptr (MOVVfpgp val) mem) => (MOVDstore [off] {sym} ptr val mem) (MOVDstore [off] {sym} ptr (MOVVgpfp val) mem) => (MOVVstore [off] {sym} ptr val mem) diff --git a/src/cmd/compile/internal/ssa/rewriteLOONG64.go b/src/cmd/compile/internal/ssa/rewriteLOONG64.go index 00731f4b51c938..ae3358e5e519d9 100644 --- a/src/cmd/compile/internal/ssa/rewriteLOONG64.go +++ b/src/cmd/compile/internal/ssa/rewriteLOONG64.go @@ -2329,6 +2329,23 @@ func rewriteValueLOONG64_OpLOONG64MOVBUload(v *Value) bool { v_0 := v.Args[0] b := v.Block config := b.Func.Config + // match: (MOVBUload [off] {sym} ptr (MOVBstore [off] {sym} ptr x _)) + // result: (MOVBUreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpLOONG64MOVBstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + if ptr != v_1.Args[0] { + break + } + v.reset(OpLOONG64MOVBUreg) + v.AddArg(x) + return true + } // match: (MOVBUload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) && (ptr.Op != OpSB || !config.ctxt.Flag_dynlink) // result: (MOVBUload [off1+int32(off2)] {sym} ptr mem) @@ -2606,6 +2623,23 @@ func rewriteValueLOONG64_OpLOONG64MOVBload(v *Value) bool { v_0 := v.Args[0] b := v.Block config := b.Func.Config + // match: (MOVBload [off] {sym} ptr (MOVBstore [off] {sym} ptr x _)) + // result: (MOVBreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpLOONG64MOVBstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + if ptr != v_1.Args[0] { + break + } + v.reset(OpLOONG64MOVBreg) + v.AddArg(x) + return true + } // match: (MOVBload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) && (ptr.Op != OpSB || !config.ctxt.Flag_dynlink) // result: (MOVBload [off1+int32(off2)] {sym} ptr mem) @@ -3526,6 +3560,23 @@ func rewriteValueLOONG64_OpLOONG64MOVHUload(v *Value) bool { v_0 := v.Args[0] b := v.Block config := b.Func.Config + // match: (MOVHUload [off] {sym} ptr (MOVHstore [off] {sym} ptr x _)) + // result: (MOVHUreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpLOONG64MOVHstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + if ptr != v_1.Args[0] { + break + } + v.reset(OpLOONG64MOVHUreg) + v.AddArg(x) + return true + } // match: (MOVHUload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) && (ptr.Op != OpSB || !config.ctxt.Flag_dynlink) // result: (MOVHUload [off1+int32(off2)] {sym} ptr mem) @@ -3765,6 +3816,23 @@ func rewriteValueLOONG64_OpLOONG64MOVHload(v *Value) bool { v_0 := v.Args[0] b := v.Block config := b.Func.Config + // match: (MOVHload [off] {sym} ptr (MOVHstore [off] {sym} ptr x _)) + // result: (MOVHreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpLOONG64MOVHstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + if ptr != v_1.Args[0] { + break + } + v.reset(OpLOONG64MOVHreg) + v.AddArg(x) + return true + } // match: (MOVHload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) && (ptr.Op != OpSB || !config.ctxt.Flag_dynlink) // result: (MOVHload [off1+int32(off2)] {sym} ptr mem) @@ -4208,6 +4276,23 @@ func rewriteValueLOONG64_OpLOONG64MOVVload(v *Value) bool { v.AddArg(val) return true } + // match: (MOVVload [off] {sym} ptr (MOVVstore [off] {sym} ptr x _)) + // result: (MOVVreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpLOONG64MOVVstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + if ptr != v_1.Args[0] { + break + } + v.reset(OpLOONG64MOVVreg) + v.AddArg(x) + return true + } // match: (MOVVload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) && (ptr.Op != OpSB || !config.ctxt.Flag_dynlink) // result: (MOVVload [off1+int32(off2)] {sym} ptr mem) @@ -4516,6 +4601,23 @@ func rewriteValueLOONG64_OpLOONG64MOVWUload(v *Value) bool { v.AddArg(v0) return true } + // match: (MOVWUload [off] {sym} ptr (MOVWstore [off] {sym} ptr x _)) + // result: (MOVWUreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpLOONG64MOVWstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + if ptr != v_1.Args[0] { + break + } + v.reset(OpLOONG64MOVWUreg) + v.AddArg(x) + return true + } // match: (MOVWUload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) && (ptr.Op != OpSB || !config.ctxt.Flag_dynlink) // result: (MOVWUload [off1+int32(off2)] {sym} ptr mem) @@ -4788,6 +4890,23 @@ func rewriteValueLOONG64_OpLOONG64MOVWload(v *Value) bool { v_0 := v.Args[0] b := v.Block config := b.Func.Config + // match: (MOVWload [off] {sym} ptr (MOVWstore [off] {sym} ptr x _)) + // result: (MOVWreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpLOONG64MOVWstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + if ptr != v_1.Args[0] { + break + } + v.reset(OpLOONG64MOVWreg) + v.AddArg(x) + return true + } // match: (MOVWload [off1] {sym} (ADDVconst [off2] ptr) mem) // cond: is32Bit(int64(off1)+off2) && (ptr.Op != OpSB || !config.ctxt.Flag_dynlink) // result: (MOVWload [off1+int32(off2)] {sym} ptr mem) From 91e76a513bdfa4159ea0aa65a01f89e006e6ead3 Mon Sep 17 00:00:00 2001 From: limeidan Date: Fri, 29 Aug 2025 15:40:31 +0800 Subject: [PATCH 15/40] cmd/compile: use generated loops instead of DUFFCOPY on loong64 Change-Id: If9da2b5681e5d05d7c3d51f003f1fe662d3feaec Reviewed-on: https://go-review.googlesource.com/c/go/+/699855 Reviewed-by: Keith Randall Reviewed-by: sophie zhao LUCI-TryBot-Result: Go LUCI Reviewed-by: abner chenc Reviewed-by: Keith Randall Auto-Submit: Michael Pratt Reviewed-by: Michael Pratt --- src/cmd/compile/internal/loong64/ssa.go | 157 ++++++++++++++---- .../compile/internal/ssa/_gen/LOONG64.rules | 30 +--- .../compile/internal/ssa/_gen/LOONG64Ops.go | 43 +++-- src/cmd/compile/internal/ssa/opGen.go | 26 ++- .../compile/internal/ssa/rewriteLOONG64.go | 49 ++---- 5 files changed, 188 insertions(+), 117 deletions(-) diff --git a/src/cmd/compile/internal/loong64/ssa.go b/src/cmd/compile/internal/loong64/ssa.go index c917d14298d425..3959f8a7c11eb9 100644 --- a/src/cmd/compile/internal/loong64/ssa.go +++ b/src/cmd/compile/internal/loong64/ssa.go @@ -659,42 +659,119 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Sym = ir.Syms.Duffcopy p.To.Offset = v.AuxInt case ssa.OpLOONG64LoweredMove: - // MOVx (Rarg1), Rtmp - // MOVx Rtmp, (Rarg0) - // ADDV $sz, Rarg1 - // ADDV $sz, Rarg0 - // BGEU Rarg2, Rarg0, -4(PC) - mov, sz := largestMove(v.AuxInt) - p := s.Prog(mov) - p.From.Type = obj.TYPE_MEM - p.From.Reg = v.Args[1].Reg() + dstReg := v.Args[0].Reg() + srcReg := v.Args[1].Reg() + if dstReg == srcReg { + break + } + tmpReg := int16(loong64.REG_R20) + n := v.AuxInt + if n < 16 { + v.Fatalf("Move too small %d", n) + } + + var off int64 + for n >= 8 { + // MOVV off(srcReg), tmpReg + // MOVV tmpReg, off(dstReg) + move8(s, srcReg, dstReg, tmpReg, off) + off += 8 + n -= 8 + } + + if n != 0 { + // MOVV off+n-8(srcReg), tmpReg + // MOVV tmpReg, off+n-8(srcReg) + move8(s, srcReg, dstReg, tmpReg, off+n-8) + } + case ssa.OpLOONG64LoweredMoveLoop: + dstReg := v.Args[0].Reg() + srcReg := v.Args[1].Reg() + if dstReg == srcReg { + break + } + countReg := int16(loong64.REG_R20) + tmpReg := int16(loong64.REG_R21) + var off int64 + n := v.AuxInt + loopSize := int64(64) + if n < 3*loopSize { + // - a loop count of 0 won't work. + // - a loop count of 1 is useless. + // - a loop count of 2 is a code size ~tie + // 4 instructions to implement the loop + // 8 instructions in the loop body + // vs + // 16 instructions in the straightline code + // Might as well use straightline code. + v.Fatalf("ZeroLoop size too small %d", n) + } + + // Put iteration count in a register. + // MOVV $n/loopSize, countReg + p := s.Prog(loong64.AMOVV) + p.From.Type = obj.TYPE_CONST + p.From.Offset = n / loopSize p.To.Type = obj.TYPE_REG - p.To.Reg = loong64.REGTMP + p.To.Reg = countReg + cntInit := p - p2 := s.Prog(mov) - p2.From.Type = obj.TYPE_REG - p2.From.Reg = loong64.REGTMP - p2.To.Type = obj.TYPE_MEM - p2.To.Reg = v.Args[0].Reg() + // Move loopSize bytes starting at srcReg to dstReg. + for range loopSize / 8 { + // MOVV off(srcReg), tmpReg + // MOVV tmpReg, off(dstReg) + move8(s, srcReg, dstReg, tmpReg, off) + off += 8 + } - p3 := s.Prog(loong64.AADDVU) - p3.From.Type = obj.TYPE_CONST - p3.From.Offset = sz - p3.To.Type = obj.TYPE_REG - p3.To.Reg = v.Args[1].Reg() + // Increment srcReg and destReg by loopSize. + // ADDV $loopSize, srcReg + p = s.Prog(loong64.AADDV) + p.From.Type = obj.TYPE_CONST + p.From.Offset = loopSize + p.To.Type = obj.TYPE_REG + p.To.Reg = srcReg + // ADDV $loopSize, dstReg + p = s.Prog(loong64.AADDV) + p.From.Type = obj.TYPE_CONST + p.From.Offset = loopSize + p.To.Type = obj.TYPE_REG + p.To.Reg = dstReg - p4 := s.Prog(loong64.AADDVU) - p4.From.Type = obj.TYPE_CONST - p4.From.Offset = sz - p4.To.Type = obj.TYPE_REG - p4.To.Reg = v.Args[0].Reg() + // Decrement loop count. + // SUBV $1, countReg + p = s.Prog(loong64.ASUBV) + p.From.Type = obj.TYPE_CONST + p.From.Offset = 1 + p.To.Type = obj.TYPE_REG + p.To.Reg = countReg - p5 := s.Prog(loong64.ABGEU) - p5.From.Type = obj.TYPE_REG - p5.From.Reg = v.Args[2].Reg() - p5.Reg = v.Args[1].Reg() - p5.To.Type = obj.TYPE_BRANCH - p5.To.SetTarget(p) + // Jump to loop header if we're not done yet. + // BNE countReg, loop header + p = s.Prog(loong64.ABNE) + p.From.Type = obj.TYPE_REG + p.From.Reg = countReg + p.To.Type = obj.TYPE_BRANCH + p.To.SetTarget(cntInit.Link) + + // Multiples of the loop size are now done. + n %= loopSize + + off = 0 + // Copy any fractional portion. + for n >= 8 { + // MOVV off(srcReg), tmpReg + // MOVV tmpReg, off(dstReg) + move8(s, srcReg, dstReg, tmpReg, off) + off += 8 + n -= 8 + } + + if n != 0 { + // MOVV off+n-8(srcReg), tmpReg + // MOVV tmpReg, off+n-8(srcReg) + move8(s, srcReg, dstReg, tmpReg, off+n-8) + } case ssa.OpLOONG64CALLstatic, ssa.OpLOONG64CALLclosure, ssa.OpLOONG64CALLinter: s.Call(v) @@ -1225,6 +1302,24 @@ func spillArgReg(pp *objw.Progs, p *obj.Prog, f *ssa.Func, t *types.Type, reg in return p } +// move8 copies 8 bytes at src+off to dst+off. +func move8(s *ssagen.State, src, dst, tmp int16, off int64) { + // MOVV off(src), tmp + ld := s.Prog(loong64.AMOVV) + ld.From.Type = obj.TYPE_MEM + ld.From.Reg = src + ld.From.Offset = off + ld.To.Type = obj.TYPE_REG + ld.To.Reg = tmp + // MOVV tmp, off(dst) + st := s.Prog(loong64.AMOVV) + st.From.Type = obj.TYPE_REG + st.From.Reg = tmp + st.To.Type = obj.TYPE_MEM + st.To.Reg = dst + st.To.Offset = off +} + // zero8 zeroes 8 bytes at reg+off. func zero8(s *ssagen.State, reg int16, off int64) { // MOVV ZR, off(reg) diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules index cb55c16c3e7abf..3fa4f363f65515 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules @@ -419,34 +419,8 @@ (MOVVstore [8] dst (MOVVload [8] src mem) (MOVVstore dst (MOVVload src mem) mem)) -// strip off fractional word move -(Move [s] dst src mem) && s%8 != 0 && s > 16 => - (Move [s%8] - (OffPtr dst [s-s%8]) - (OffPtr src [s-s%8]) - (Move [s-s%8] dst src mem)) - -// medium move uses a duff device -(Move [s] dst src mem) - && s%8 == 0 && s > 16 && s <= 8*128 - && logLargeCopy(v, s) => - (DUFFCOPY [16 * (128 - s/8)] dst src mem) -// 16 and 128 are magic constants. 16 is the number of bytes to encode: -// MOVV (R20), R30 -// ADDV $8, R20 -// MOVV R30, (R21) -// ADDV $8, R21 -// and 128 is the number of such blocks. See runtime/duff_loong64.s:duffcopy. - -// large move uses a loop -(Move [s] dst src mem) - && s%8 == 0 && s > 1024 && logLargeCopy(v, s) => - (LoweredMove - dst - src - (ADDVconst src [s-8]) - mem) - +(Move [s] dst src mem) && s > 16 && s < 192 && logLargeCopy(v, s) => (LoweredMove [s] dst src mem) +(Move [s] dst src mem) && s >= 192 && logLargeCopy(v, s) => (LoweredMoveLoop [s] dst src mem) // float <=> int register moves, with no conversion. // These come up when compiling math.{Float64bits, Float64frombits, Float32bits, Float32frombits}. diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go index 359cb42056a61c..cc6ae8fb8e65de 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go @@ -429,27 +429,40 @@ func init() { needIntTemp: true, }, - // large or unaligned move - // arg0 = address of dst memory (in R21, changed as side effect) - // arg1 = address of src memory (in R20, changed as side effect) - // arg2 = address of the last element of src - // arg3 = mem - // auxint = alignment + // medium copying + // arg0 = address of dst memory + // arg1 = address of src memory + // arg2 = mem + // auxint = number of bytes to copy // returns mem - // MOVx (R20), Rtmp - // MOVx Rtmp, (R21) - // ADDV $sz, R20 - // ADDV $sz, R21 - // BGEU Rarg2, R20, -4(PC) { name: "LoweredMove", aux: "Int64", - argLength: 4, + argLength: 3, reg: regInfo{ - inputs: []regMask{buildReg("R21"), buildReg("R20"), gp}, - clobbers: buildReg("R20 R21"), + inputs: []regMask{gp &^ buildReg("R20"), gp &^ buildReg("R20")}, + clobbers: buildReg("R20"), + }, + faultOnNilArg0: true, + faultOnNilArg1: true, + }, + + // large copying + // arg0 = address of dst memory + // arg1 = address of src memory + // arg2 = mem + // auxint = number of bytes to copy + // returns mem + { + name: "LoweredMoveLoop", + aux: "Int64", + argLength: 3, + reg: regInfo{ + inputs: []regMask{gp &^ buildReg("R20 R21"), gp &^ buildReg("R20 R21")}, + clobbers: buildReg("R20 R21"), + clobbersArg0: true, + clobbersArg1: true, }, - typ: "Mem", faultOnNilArg0: true, faultOnNilArg1: true, }, diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index fca7d810175491..f42d64228fae3a 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1929,6 +1929,7 @@ const ( OpLOONG64DUFFCOPY OpLOONG64LoweredZeroLoop OpLOONG64LoweredMove + OpLOONG64LoweredMoveLoop OpLOONG64LoweredAtomicLoad8 OpLOONG64LoweredAtomicLoad32 OpLOONG64LoweredAtomicLoad64 @@ -25986,16 +25987,31 @@ var opcodeTable = [...]opInfo{ { name: "LoweredMove", auxType: auxInt64, - argLen: 4, + argLen: 3, faultOnNilArg0: true, faultOnNilArg1: true, reg: regInfo{ inputs: []inputInfo{ - {0, 1048576}, // R21 - {1, 524288}, // R20 - {2, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31 + {0, 1071120376}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R21 R23 R24 R25 R26 R27 R28 R29 R31 + {1, 1071120376}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R21 R23 R24 R25 R26 R27 R28 R29 R31 }, - clobbers: 1572864, // R20 R21 + clobbers: 524288, // R20 + }, + }, + { + name: "LoweredMoveLoop", + auxType: auxInt64, + argLen: 3, + faultOnNilArg0: true, + faultOnNilArg1: true, + reg: regInfo{ + inputs: []inputInfo{ + {0, 1070071800}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R23 R24 R25 R26 R27 R28 R29 R31 + {1, 1070071800}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R23 R24 R25 R26 R27 R28 R29 R31 + }, + clobbers: 1572864, // R20 R21 + clobbersArg0: true, + clobbersArg1: true, }, }, { diff --git a/src/cmd/compile/internal/ssa/rewriteLOONG64.go b/src/cmd/compile/internal/ssa/rewriteLOONG64.go index ae3358e5e519d9..5890fe050a222b 100644 --- a/src/cmd/compile/internal/ssa/rewriteLOONG64.go +++ b/src/cmd/compile/internal/ssa/rewriteLOONG64.go @@ -9133,62 +9133,35 @@ func rewriteValueLOONG64_OpMove(v *Value) bool { return true } // match: (Move [s] dst src mem) - // cond: s%8 != 0 && s > 16 - // result: (Move [s%8] (OffPtr dst [s-s%8]) (OffPtr src [s-s%8]) (Move [s-s%8] dst src mem)) + // cond: s > 16 && s < 192 && logLargeCopy(v, s) + // result: (LoweredMove [s] dst src mem) for { s := auxIntToInt64(v.AuxInt) dst := v_0 src := v_1 mem := v_2 - if !(s%8 != 0 && s > 16) { + if !(s > 16 && s < 192 && logLargeCopy(v, s)) { break } - v.reset(OpMove) - v.AuxInt = int64ToAuxInt(s % 8) - v0 := b.NewValue0(v.Pos, OpOffPtr, dst.Type) - v0.AuxInt = int64ToAuxInt(s - s%8) - v0.AddArg(dst) - v1 := b.NewValue0(v.Pos, OpOffPtr, src.Type) - v1.AuxInt = int64ToAuxInt(s - s%8) - v1.AddArg(src) - v2 := b.NewValue0(v.Pos, OpMove, types.TypeMem) - v2.AuxInt = int64ToAuxInt(s - s%8) - v2.AddArg3(dst, src, mem) - v.AddArg3(v0, v1, v2) - return true - } - // match: (Move [s] dst src mem) - // cond: s%8 == 0 && s > 16 && s <= 8*128 && logLargeCopy(v, s) - // result: (DUFFCOPY [16 * (128 - s/8)] dst src mem) - for { - s := auxIntToInt64(v.AuxInt) - dst := v_0 - src := v_1 - mem := v_2 - if !(s%8 == 0 && s > 16 && s <= 8*128 && logLargeCopy(v, s)) { - break - } - v.reset(OpLOONG64DUFFCOPY) - v.AuxInt = int64ToAuxInt(16 * (128 - s/8)) + v.reset(OpLOONG64LoweredMove) + v.AuxInt = int64ToAuxInt(s) v.AddArg3(dst, src, mem) return true } // match: (Move [s] dst src mem) - // cond: s%8 == 0 && s > 1024 && logLargeCopy(v, s) - // result: (LoweredMove dst src (ADDVconst src [s-8]) mem) + // cond: s >= 192 && logLargeCopy(v, s) + // result: (LoweredMoveLoop [s] dst src mem) for { s := auxIntToInt64(v.AuxInt) dst := v_0 src := v_1 mem := v_2 - if !(s%8 == 0 && s > 1024 && logLargeCopy(v, s)) { + if !(s >= 192 && logLargeCopy(v, s)) { break } - v.reset(OpLOONG64LoweredMove) - v0 := b.NewValue0(v.Pos, OpLOONG64ADDVconst, src.Type) - v0.AuxInt = int64ToAuxInt(s - 8) - v0.AddArg(src) - v.AddArg4(dst, src, v0, mem) + v.reset(OpLOONG64LoweredMoveLoop) + v.AuxInt = int64ToAuxInt(s) + v.AddArg3(dst, src, mem) return true } return false From 80038586ed2814a03dcb95cd6f130766f8d803c3 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 18 Aug 2025 15:49:50 +0200 Subject: [PATCH 16/40] cmd/compile: export to DWARF types only referenced through interfaces Delve and viewcore use DWARF type DIEs to display and explore the runtime value of interface variables. This has always been slightly problematic since the runtime type of an interface variable might only be reachable through interfaces and thus be missing from debug_info (see issue #46670). Prior to commit f4de2ecf this was not a severe problem since a struct literal caused the allocation of a struct into an autotemp variable, which was then used by dwarfgen to make sure that the DIE for that type would be generated. After f4de2ecf such autotemps are no longer being generated and go1.25.0 ends up having many more instances of interfaces with unreadable runtime type (https://github.com/go-delve/delve/issues/4080). This commit fixes this problem by scanning the relocation of the function symbol and adding to the function's DIE symbol references to all types used by the function to create interfaces. Fixes go-delve/delve#4080 Updates #46670 Change-Id: I3e9db1c0d1662905373239816a72604ac533b09e Reviewed-on: https://go-review.googlesource.com/c/go/+/696955 Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Auto-Submit: Michael Pratt Reviewed-by: Keith Randall Reviewed-by: Than McIntosh Reviewed-by: Florian Lehner --- src/cmd/compile/internal/dwarfgen/dwarf.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go index 6ab39d2aaad1cf..9d975e0bc1ac7d 100644 --- a/src/cmd/compile/internal/dwarfgen/dwarf.go +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -128,14 +128,29 @@ func Info(ctxt *obj.Link, fnsym *obj.LSym, infosym *obj.LSym, curfn obj.Func) (s // already referenced by a dwarf var, attach an R_USETYPE relocation to // the function symbol to insure that the type included in DWARF // processing during linking. + // Do the same with R_USEIFACE relocations from the function symbol for the + // same reason. + // All these R_USETYPE relocations are only looked at if the function + // survives deadcode elimination in the linker. typesyms := []*obj.LSym{} for t := range fnsym.Func().Autot { typesyms = append(typesyms, t) } + for i := range fnsym.R { + if fnsym.R[i].Type == objabi.R_USEIFACE && !strings.HasPrefix(fnsym.R[i].Sym.Name, "go:itab.") { + // Types referenced through itab will be referenced from somewhere else + typesyms = append(typesyms, fnsym.R[i].Sym) + } + } slices.SortFunc(typesyms, func(a, b *obj.LSym) int { return strings.Compare(a.Name, b.Name) }) + var lastsym *obj.LSym for _, sym := range typesyms { + if sym == lastsym { + continue + } + lastsym = sym infosym.AddRel(ctxt, obj.Reloc{Type: objabi.R_USETYPE, Sym: sym}) } fnsym.Func().Autot = nil From 4373754bc990fc13b27ae823f977bc6328cc331e Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Sat, 30 Aug 2025 21:53:37 +0100 Subject: [PATCH 17/40] cmd/compile: add store to load forwarding rules on riscv64 Adds the equivalent integer variants of the floating point rules added in CL 599235. Change-Id: Ibe03c26383059821d99cea2337799e6416b4c581 Reviewed-on: https://go-review.googlesource.com/c/go/+/700175 Reviewed-by: Michael Pratt Auto-Submit: Keith Randall Reviewed-by: Julian Zhu LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall --- .../compile/internal/ssa/_gen/RISCV64.rules | 2 + .../compile/internal/ssa/rewriteRISCV64.go | 133 ++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/src/cmd/compile/internal/ssa/_gen/RISCV64.rules b/src/cmd/compile/internal/ssa/_gen/RISCV64.rules index 151f3412ce7c5a..821f822746ec5a 100644 --- a/src/cmd/compile/internal/ssa/_gen/RISCV64.rules +++ b/src/cmd/compile/internal/ssa/_gen/RISCV64.rules @@ -716,6 +716,8 @@ (MOVWUreg x:(MOVWload [off] {sym} ptr mem)) && x.Uses == 1 && clobber(x) => @x.Block (MOVWUload [off] {sym} ptr mem) // Replace load from same location as preceding store with copy. +(MOV(D|W|H|B)load [off] {sym} ptr1 (MOV(D|W|H|B)store [off] {sym} ptr2 x _)) && isSamePtr(ptr1, ptr2) => (MOV(D|W|H|B)reg x) +(MOV(W|H|B)Uload [off] {sym} ptr1 (MOV(W|H|B)store [off] {sym} ptr2 x _)) && isSamePtr(ptr1, ptr2) => (MOV(W|H|B)Ureg x) (MOVDload [off] {sym} ptr1 (FMOVDstore [off] {sym} ptr2 x _)) && isSamePtr(ptr1, ptr2) => (FMVXD x) (FMOVDload [off] {sym} ptr1 (MOVDstore [off] {sym} ptr2 x _)) && isSamePtr(ptr1, ptr2) => (FMVDX x) (MOVWload [off] {sym} ptr1 (FMOVWstore [off] {sym} ptr2 x _)) && isSamePtr(ptr1, ptr2) => (FMVXS x) diff --git a/src/cmd/compile/internal/ssa/rewriteRISCV64.go b/src/cmd/compile/internal/ssa/rewriteRISCV64.go index 1e80669672c2f4..e2c400b0c5e811 100644 --- a/src/cmd/compile/internal/ssa/rewriteRISCV64.go +++ b/src/cmd/compile/internal/ssa/rewriteRISCV64.go @@ -4740,6 +4740,25 @@ func rewriteValueRISCV64_OpRISCV64MOVBUload(v *Value) bool { v.AddArg2(base, mem) return true } + // match: (MOVBUload [off] {sym} ptr1 (MOVBstore [off] {sym} ptr2 x _)) + // cond: isSamePtr(ptr1, ptr2) + // result: (MOVBUreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr1 := v_0 + if v_1.Op != OpRISCV64MOVBstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + ptr2 := v_1.Args[0] + if !(isSamePtr(ptr1, ptr2)) { + break + } + v.reset(OpRISCV64MOVBUreg) + v.AddArg(x) + return true + } return false } func rewriteValueRISCV64_OpRISCV64MOVBUreg(v *Value) bool { @@ -5049,6 +5068,25 @@ func rewriteValueRISCV64_OpRISCV64MOVBload(v *Value) bool { v.AddArg2(base, mem) return true } + // match: (MOVBload [off] {sym} ptr1 (MOVBstore [off] {sym} ptr2 x _)) + // cond: isSamePtr(ptr1, ptr2) + // result: (MOVBreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr1 := v_0 + if v_1.Op != OpRISCV64MOVBstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + ptr2 := v_1.Args[0] + if !(isSamePtr(ptr1, ptr2)) { + break + } + v.reset(OpRISCV64MOVBreg) + v.AddArg(x) + return true + } return false } func rewriteValueRISCV64_OpRISCV64MOVBreg(v *Value) bool { @@ -5397,6 +5435,25 @@ func rewriteValueRISCV64_OpRISCV64MOVDload(v *Value) bool { v.AddArg2(base, mem) return true } + // match: (MOVDload [off] {sym} ptr1 (MOVDstore [off] {sym} ptr2 x _)) + // cond: isSamePtr(ptr1, ptr2) + // result: (MOVDreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr1 := v_0 + if v_1.Op != OpRISCV64MOVDstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + ptr2 := v_1.Args[0] + if !(isSamePtr(ptr1, ptr2)) { + break + } + v.reset(OpRISCV64MOVDreg) + v.AddArg(x) + return true + } // match: (MOVDload [off] {sym} ptr1 (FMOVDstore [off] {sym} ptr2 x _)) // cond: isSamePtr(ptr1, ptr2) // result: (FMVXD x) @@ -5616,6 +5673,25 @@ func rewriteValueRISCV64_OpRISCV64MOVHUload(v *Value) bool { v.AddArg2(base, mem) return true } + // match: (MOVHUload [off] {sym} ptr1 (MOVHstore [off] {sym} ptr2 x _)) + // cond: isSamePtr(ptr1, ptr2) + // result: (MOVHUreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr1 := v_0 + if v_1.Op != OpRISCV64MOVHstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + ptr2 := v_1.Args[0] + if !(isSamePtr(ptr1, ptr2)) { + break + } + v.reset(OpRISCV64MOVHUreg) + v.AddArg(x) + return true + } return false } func rewriteValueRISCV64_OpRISCV64MOVHUreg(v *Value) bool { @@ -5782,6 +5858,25 @@ func rewriteValueRISCV64_OpRISCV64MOVHload(v *Value) bool { v.AddArg2(base, mem) return true } + // match: (MOVHload [off] {sym} ptr1 (MOVHstore [off] {sym} ptr2 x _)) + // cond: isSamePtr(ptr1, ptr2) + // result: (MOVHreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr1 := v_0 + if v_1.Op != OpRISCV64MOVHstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + ptr2 := v_1.Args[0] + if !(isSamePtr(ptr1, ptr2)) { + break + } + v.reset(OpRISCV64MOVHreg) + v.AddArg(x) + return true + } return false } func rewriteValueRISCV64_OpRISCV64MOVHreg(v *Value) bool { @@ -6141,6 +6236,25 @@ func rewriteValueRISCV64_OpRISCV64MOVWUload(v *Value) bool { v.AddArg2(base, mem) return true } + // match: (MOVWUload [off] {sym} ptr1 (MOVWstore [off] {sym} ptr2 x _)) + // cond: isSamePtr(ptr1, ptr2) + // result: (MOVWUreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr1 := v_0 + if v_1.Op != OpRISCV64MOVWstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + ptr2 := v_1.Args[0] + if !(isSamePtr(ptr1, ptr2)) { + break + } + v.reset(OpRISCV64MOVWUreg) + v.AddArg(x) + return true + } // match: (MOVWUload [off] {sym} ptr1 (FMOVWstore [off] {sym} ptr2 x _)) // cond: isSamePtr(ptr1, ptr2) // result: (MOVWUreg (FMVXS x)) @@ -6352,6 +6466,25 @@ func rewriteValueRISCV64_OpRISCV64MOVWload(v *Value) bool { v.AddArg2(base, mem) return true } + // match: (MOVWload [off] {sym} ptr1 (MOVWstore [off] {sym} ptr2 x _)) + // cond: isSamePtr(ptr1, ptr2) + // result: (MOVWreg x) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr1 := v_0 + if v_1.Op != OpRISCV64MOVWstore || auxIntToInt32(v_1.AuxInt) != off || auxToSym(v_1.Aux) != sym { + break + } + x := v_1.Args[1] + ptr2 := v_1.Args[0] + if !(isSamePtr(ptr1, ptr2)) { + break + } + v.reset(OpRISCV64MOVWreg) + v.AddArg(x) + return true + } // match: (MOVWload [off] {sym} ptr1 (FMOVWstore [off] {sym} ptr2 x _)) // cond: isSamePtr(ptr1, ptr2) // result: (FMVXS x) From df290384864c0b3cbb557ef11fc95a29d52f6aca Mon Sep 17 00:00:00 2001 From: Youlin Feng Date: Mon, 1 Sep 2025 18:31:29 +0800 Subject: [PATCH 18/40] cmd/compile/internal/ssa: load constant values from abi.PtrType.Elem This CL makes the generated code for reflect.TypeFor as simple as an intrinsic function. Fixes #75203 Change-Id: I7bb48787101f07e77ab5c583292e834c28a028d6 Reviewed-on: https://go-review.googlesource.com/c/go/+/700336 LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Michael Pratt Auto-Submit: Keith Randall --- .../compile/internal/ssa/_gen/generic.rules | 16 + src/cmd/compile/internal/ssa/rewrite.go | 33 ++ .../compile/internal/ssa/rewritegeneric.go | 300 ++++++++++++++++++ test/codegen/issue75203.go | 22 ++ 4 files changed, 371 insertions(+) create mode 100644 test/codegen/issue75203.go diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules index da112abbf5677e..c5e2507a14fd68 100644 --- a/src/cmd/compile/internal/ssa/_gen/generic.rules +++ b/src/cmd/compile/internal/ssa/_gen/generic.rules @@ -2767,6 +2767,22 @@ (Load (OffPtr [off] (ITab (IMake (Addr {s} sb) _))) _) && isFixedSym(s, off) => (Addr {fixedSym(b.Func, s, off)} sb) (Load (OffPtr [off] (ITab (IMake (Convert (Addr {s} sb) _) _))) _) && isFixedSym(s, off) => (Addr {fixedSym(b.Func, s, off)} sb) +// Loading constant values from dictionaries and itabs. For offset 0. +(Load (Addr {s} sb) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) +(Load (Convert (Addr {s} sb) _) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) +(Load (ITab (IMake (Addr {s} sb) _)) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) +(Load (ITab (IMake (Convert (Addr {s} sb) _) _)) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) +(Load (Addr {s} sb) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) +(Load (Convert (Addr {s} sb) _) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) +(Load (ITab (IMake (Addr {s} sb) _)) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) +(Load (ITab (IMake (Convert (Addr {s} sb) _) _)) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) + +// Loading constant values from abi.PtrType.Elem. +(Load (OffPtr [off] (Addr {s} sb) ) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(s, off)} sb) +(Load (OffPtr [off] (Convert (Addr {s} sb) _) ) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(s, off)} sb) +(Load (OffPtr [off] (ITab (IMake (Addr {s} sb) _))) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(s, off)} sb) +(Load (OffPtr [off] (ITab (IMake (Convert (Addr {s} sb) _) _))) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(s, off)} sb) + // Loading constant values from runtime._type.hash. (Load (OffPtr [off] (Addr {sym} _) ) _) && t.IsInteger() && t.Size() == 4 && isFixed32(config, sym, off) => (Const32 [fixed32(config, sym, off)]) (Load (OffPtr [off] (Convert (Addr {sym} _) _) ) _) && t.IsInteger() && t.Size() == 4 && isFixed32(config, sym, off) => (Const32 [fixed32(config, sym, off)]) diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index 6704c7d6e0294a..8f331c283a66e0 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -8,6 +8,7 @@ import ( "cmd/compile/internal/base" "cmd/compile/internal/logopt" "cmd/compile/internal/reflectdata" + "cmd/compile/internal/rttype" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/obj/s390x" @@ -2012,6 +2013,38 @@ func fixed32(c *Config, sym Sym, off int64) int32 { return 0 } +// isPtrElem returns true if sym is an instance of abi.PtrType and off +// is equal to the offset of its Elem field. +func isPtrElem(sym Sym, off int64) bool { + lsym := sym.(*obj.LSym) + if strings.HasPrefix(lsym.Name, "type:*") { + if ti, ok := (*lsym.Extra).(*obj.TypeInfo); ok { + t := ti.Type.(*types.Type) + if t.Kind() == types.TPTR { + if off == rttype.PtrType.OffsetOf("Elem") { + return true + } + } + } + } + return false +} +func ptrElem(sym Sym, off int64) Sym { + lsym := sym.(*obj.LSym) + if strings.HasPrefix(lsym.Name, "type:*") { + if ti, ok := (*lsym.Extra).(*obj.TypeInfo); ok { + t := ti.Type.(*types.Type) + if t.Kind() == types.TPTR { + if off == rttype.PtrType.OffsetOf("Elem") { + return reflectdata.TypeLinksym(t.Elem()) + } + } + } + } + base.Fatalf("ptrElem data not known for %s:%d", sym, off) + return nil +} + // isFixedSym returns true if the contents of sym at the given offset // is known and is the constant address of another symbol. func isFixedSym(sym Sym, off int64) bool { diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index 5394747ba57717..a0a4960397d809 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -14897,6 +14897,306 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { v.AddArg(sb) return true } + // match: (Load (Addr {s} sb) _) + // cond: isFixedSym(s, 0) + // result: (Addr {fixedSym(b.Func, s, 0)} sb) + for { + if v.Type != typ.BytePtr || v_0.Op != OpAddr { + break + } + s := auxToSym(v_0.Aux) + sb := v_0.Args[0] + if !(isFixedSym(s, 0)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(fixedSym(b.Func, s, 0)) + v.AddArg(sb) + return true + } + // match: (Load (Convert (Addr {s} sb) _) _) + // cond: isFixedSym(s, 0) + // result: (Addr {fixedSym(b.Func, s, 0)} sb) + for { + if v.Type != typ.BytePtr || v_0.Op != OpConvert { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0.Aux) + sb := v_0_0.Args[0] + if !(isFixedSym(s, 0)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(fixedSym(b.Func, s, 0)) + v.AddArg(sb) + return true + } + // match: (Load (ITab (IMake (Addr {s} sb) _)) _) + // cond: isFixedSym(s, 0) + // result: (Addr {fixedSym(b.Func, s, 0)} sb) + for { + if v.Type != typ.BytePtr || v_0.Op != OpITab { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpIMake { + break + } + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0_0.Aux) + sb := v_0_0_0.Args[0] + if !(isFixedSym(s, 0)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(fixedSym(b.Func, s, 0)) + v.AddArg(sb) + return true + } + // match: (Load (ITab (IMake (Convert (Addr {s} sb) _) _)) _) + // cond: isFixedSym(s, 0) + // result: (Addr {fixedSym(b.Func, s, 0)} sb) + for { + if v.Type != typ.BytePtr || v_0.Op != OpITab { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpIMake { + break + } + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpConvert { + break + } + v_0_0_0_0 := v_0_0_0.Args[0] + if v_0_0_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0_0_0.Aux) + sb := v_0_0_0_0.Args[0] + if !(isFixedSym(s, 0)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(fixedSym(b.Func, s, 0)) + v.AddArg(sb) + return true + } + // match: (Load (Addr {s} sb) _) + // cond: isFixedSym(s, 0) + // result: (Addr {fixedSym(b.Func, s, 0)} sb) + for { + if v.Type != typ.Uintptr || v_0.Op != OpAddr { + break + } + s := auxToSym(v_0.Aux) + sb := v_0.Args[0] + if !(isFixedSym(s, 0)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(fixedSym(b.Func, s, 0)) + v.AddArg(sb) + return true + } + // match: (Load (Convert (Addr {s} sb) _) _) + // cond: isFixedSym(s, 0) + // result: (Addr {fixedSym(b.Func, s, 0)} sb) + for { + if v.Type != typ.Uintptr || v_0.Op != OpConvert { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0.Aux) + sb := v_0_0.Args[0] + if !(isFixedSym(s, 0)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(fixedSym(b.Func, s, 0)) + v.AddArg(sb) + return true + } + // match: (Load (ITab (IMake (Addr {s} sb) _)) _) + // cond: isFixedSym(s, 0) + // result: (Addr {fixedSym(b.Func, s, 0)} sb) + for { + if v.Type != typ.Uintptr || v_0.Op != OpITab { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpIMake { + break + } + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0_0.Aux) + sb := v_0_0_0.Args[0] + if !(isFixedSym(s, 0)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(fixedSym(b.Func, s, 0)) + v.AddArg(sb) + return true + } + // match: (Load (ITab (IMake (Convert (Addr {s} sb) _) _)) _) + // cond: isFixedSym(s, 0) + // result: (Addr {fixedSym(b.Func, s, 0)} sb) + for { + if v.Type != typ.Uintptr || v_0.Op != OpITab { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpIMake { + break + } + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpConvert { + break + } + v_0_0_0_0 := v_0_0_0.Args[0] + if v_0_0_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0_0_0.Aux) + sb := v_0_0_0_0.Args[0] + if !(isFixedSym(s, 0)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(fixedSym(b.Func, s, 0)) + v.AddArg(sb) + return true + } + // match: (Load (OffPtr [off] (Addr {s} sb) ) _) + // cond: t.IsPtr() && isPtrElem(s, off) + // result: (Addr {ptrElem(s, off)} sb) + for { + t := v.Type + if v_0.Op != OpOffPtr { + break + } + off := auxIntToInt64(v_0.AuxInt) + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0.Aux) + sb := v_0_0.Args[0] + if !(t.IsPtr() && isPtrElem(s, off)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(ptrElem(s, off)) + v.AddArg(sb) + return true + } + // match: (Load (OffPtr [off] (Convert (Addr {s} sb) _) ) _) + // cond: t.IsPtr() && isPtrElem(s, off) + // result: (Addr {ptrElem(s, off)} sb) + for { + t := v.Type + if v_0.Op != OpOffPtr { + break + } + off := auxIntToInt64(v_0.AuxInt) + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpConvert { + break + } + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0_0.Aux) + sb := v_0_0_0.Args[0] + if !(t.IsPtr() && isPtrElem(s, off)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(ptrElem(s, off)) + v.AddArg(sb) + return true + } + // match: (Load (OffPtr [off] (ITab (IMake (Addr {s} sb) _))) _) + // cond: t.IsPtr() && isPtrElem(s, off) + // result: (Addr {ptrElem(s, off)} sb) + for { + t := v.Type + if v_0.Op != OpOffPtr { + break + } + off := auxIntToInt64(v_0.AuxInt) + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpITab { + break + } + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpIMake { + break + } + v_0_0_0_0 := v_0_0_0.Args[0] + if v_0_0_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0_0_0.Aux) + sb := v_0_0_0_0.Args[0] + if !(t.IsPtr() && isPtrElem(s, off)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(ptrElem(s, off)) + v.AddArg(sb) + return true + } + // match: (Load (OffPtr [off] (ITab (IMake (Convert (Addr {s} sb) _) _))) _) + // cond: t.IsPtr() && isPtrElem(s, off) + // result: (Addr {ptrElem(s, off)} sb) + for { + t := v.Type + if v_0.Op != OpOffPtr { + break + } + off := auxIntToInt64(v_0.AuxInt) + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpITab { + break + } + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpIMake { + break + } + v_0_0_0_0 := v_0_0_0.Args[0] + if v_0_0_0_0.Op != OpConvert { + break + } + v_0_0_0_0_0 := v_0_0_0_0.Args[0] + if v_0_0_0_0_0.Op != OpAddr { + break + } + s := auxToSym(v_0_0_0_0_0.Aux) + sb := v_0_0_0_0_0.Args[0] + if !(t.IsPtr() && isPtrElem(s, off)) { + break + } + v.reset(OpAddr) + v.Aux = symToAux(ptrElem(s, off)) + v.AddArg(sb) + return true + } // match: (Load (OffPtr [off] (Addr {sym} _) ) _) // cond: t.IsInteger() && t.Size() == 4 && isFixed32(config, sym, off) // result: (Const32 [fixed32(config, sym, off)]) diff --git a/test/codegen/issue75203.go b/test/codegen/issue75203.go new file mode 100644 index 00000000000000..68e1794120ae8e --- /dev/null +++ b/test/codegen/issue75203.go @@ -0,0 +1,22 @@ +// asmcheck + +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +import "reflect" + +func f() reflect.Type { + // amd64:`LEAQ\stype:\*int\(SB\)` + // arm64:`MOVD\s\$type:\*int\(SB\)` + return reflect.TypeFor[*int]() +} + +func g() reflect.Type { + // amd64:`LEAQ\stype:int\(SB\)` + // arm64:`MOVD\s\$type:int\(SB\)` + return reflect.TypeFor[int]() +} + From b7c20413c5b78b7dfc7f7de52f333a8ca85cd55b Mon Sep 17 00:00:00 2001 From: aimuz Date: Wed, 3 Sep 2025 14:15:06 +0000 Subject: [PATCH 19/40] runtime: remove obsolete osArchInit function The osArchInit function was introduced as a workaround for a Linux kernel bug that corrupted vector registers on x86 CPUs during signal delivery. The bug was introduced in Linux 5.2 and fixed in 5.3.15, 5.4.2, and all 5.5 and later kernels. The fix was also back-ported by major distros. Change-Id: I59990a7df104843955301c5cb8a547614eba145b GitHub-Last-Rev: 8425af458bfaad0d64d21ff3f3e0049d186f44ed GitHub-Pull-Request: golang/go#75246 Reviewed-on: https://go-review.googlesource.com/c/go/+/700555 Reviewed-by: Michael Knyszek Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Auto-Submit: Michael Pratt --- src/runtime/os_freebsd_riscv64.go | 7 ------- src/runtime/os_linux.go | 1 - src/runtime/os_linux_arm.go | 2 -- src/runtime/os_linux_arm64.go | 2 -- src/runtime/os_linux_loong64.go | 2 -- src/runtime/os_linux_mips64x.go | 2 -- src/runtime/os_linux_mipsx.go | 2 -- src/runtime/os_linux_ppc64x.go | 2 -- src/runtime/os_linux_riscv64.go | 2 -- src/runtime/os_linux_s390x.go | 2 -- src/runtime/os_linux_x86.go | 9 --------- 11 files changed, 33 deletions(-) delete mode 100644 src/runtime/os_freebsd_riscv64.go delete mode 100644 src/runtime/os_linux_x86.go diff --git a/src/runtime/os_freebsd_riscv64.go b/src/runtime/os_freebsd_riscv64.go deleted file mode 100644 index 0f2ed5096c8176..00000000000000 --- a/src/runtime/os_freebsd_riscv64.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package runtime - -func osArchInit() {} diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 0ec5e43007353d..c9d25a5be8dca4 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -356,7 +356,6 @@ func getHugePageSize() uintptr { func osinit() { numCPUStartup = getCPUCount() physHugePageSize = getHugePageSize() - osArchInit() vgetrandomInit() } diff --git a/src/runtime/os_linux_arm.go b/src/runtime/os_linux_arm.go index 5e1274ebab0fdf..46b2dc467cbb7a 100644 --- a/src/runtime/os_linux_arm.go +++ b/src/runtime/os_linux_arm.go @@ -48,8 +48,6 @@ func archauxv(tag, val uintptr) { } } -func osArchInit() {} - //go:nosplit func cputicks() int64 { // nanotime() is a poor approximation of CPU ticks that is enough for the profiler. diff --git a/src/runtime/os_linux_arm64.go b/src/runtime/os_linux_arm64.go index 62cead1d221c17..ccfb92f8ebdacd 100644 --- a/src/runtime/os_linux_arm64.go +++ b/src/runtime/os_linux_arm64.go @@ -15,8 +15,6 @@ func archauxv(tag, val uintptr) { } } -func osArchInit() {} - //go:nosplit func cputicks() int64 { // nanotime() is a poor approximation of CPU ticks that is enough for the profiler. diff --git a/src/runtime/os_linux_loong64.go b/src/runtime/os_linux_loong64.go index 03926feb8c5fc2..179ae08e54b37b 100644 --- a/src/runtime/os_linux_loong64.go +++ b/src/runtime/os_linux_loong64.go @@ -14,5 +14,3 @@ func archauxv(tag, val uintptr) { cpu.HWCap = uint(val) } } - -func osArchInit() {} diff --git a/src/runtime/os_linux_mips64x.go b/src/runtime/os_linux_mips64x.go index 770cc27ba78915..778db11221c9bc 100644 --- a/src/runtime/os_linux_mips64x.go +++ b/src/runtime/os_linux_mips64x.go @@ -15,8 +15,6 @@ func archauxv(tag, val uintptr) { } } -func osArchInit() {} - //go:nosplit func cputicks() int64 { // nanotime() is a poor approximation of CPU ticks that is enough for the profiler. diff --git a/src/runtime/os_linux_mipsx.go b/src/runtime/os_linux_mipsx.go index 3807e6d05103c8..ef5d0b867edab4 100644 --- a/src/runtime/os_linux_mipsx.go +++ b/src/runtime/os_linux_mipsx.go @@ -9,8 +9,6 @@ package runtime func archauxv(tag, val uintptr) { } -func osArchInit() {} - //go:nosplit func cputicks() int64 { // nanotime() is a poor approximation of CPU ticks that is enough for the profiler. diff --git a/src/runtime/os_linux_ppc64x.go b/src/runtime/os_linux_ppc64x.go index 25d7ccc0356c16..9abc9ec3d229b3 100644 --- a/src/runtime/os_linux_ppc64x.go +++ b/src/runtime/os_linux_ppc64x.go @@ -19,5 +19,3 @@ func archauxv(tag, val uintptr) { cpu.HWCap2 = uint(val) } } - -func osArchInit() {} diff --git a/src/runtime/os_linux_riscv64.go b/src/runtime/os_linux_riscv64.go index 65fa601a29962e..f4f528c736fe3b 100644 --- a/src/runtime/os_linux_riscv64.go +++ b/src/runtime/os_linux_riscv64.go @@ -9,8 +9,6 @@ import ( "unsafe" ) -func osArchInit() {} - type riscvHWProbePairs = struct { key int64 value uint64 diff --git a/src/runtime/os_linux_s390x.go b/src/runtime/os_linux_s390x.go index 0a1d95975edd67..c5c095593c1f65 100644 --- a/src/runtime/os_linux_s390x.go +++ b/src/runtime/os_linux_s390x.go @@ -17,8 +17,6 @@ func archauxv(tag, val uintptr) { } } -func osArchInit() {} - func checkS390xCPU() { // Check if the present z-system has the hardware capability to carryout // floating point operations. Check if hwcap reflects CPU capability for the diff --git a/src/runtime/os_linux_x86.go b/src/runtime/os_linux_x86.go deleted file mode 100644 index c88f61fa2e99e6..00000000000000 --- a/src/runtime/os_linux_x86.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build linux && (386 || amd64) - -package runtime - -func osArchInit() {} From 8c27a808905b0611b0a7b7bbff08819206be3b86 Mon Sep 17 00:00:00 2001 From: Julien Cretel Date: Sun, 31 Aug 2025 19:34:07 +0000 Subject: [PATCH 20/40] path{,/filepath}: speed up Match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change adds benchmarks for Match and speeds it up by simplifying scanChunk (to the point of making it inlineable) and eliminating some branches in matchChunk. Here are some benchmark results (no change to allocations): goos: darwin goarch: amd64 pkg: path cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz │ old │ new2 │ │ sec/op │ sec/op vs base │ Match/"abc"_"abc"-8 24.64n ± 6% 21.30n ± 1% -13.53% (p=0.000 n=10) Match/"*"_"abc"-8 9.117n ± 3% 8.345n ± 2% -8.47% (p=0.000 n=10) Match/"*c"_"abc"-8 29.59n ± 3% 29.86n ± 3% ~ (p=0.055 n=10) Match/"a*"_"a"-8 22.88n ± 2% 20.66n ± 1% -9.68% (p=0.000 n=10) Match/"a*"_"abc"-8 23.57n ± 1% 21.59n ± 0% -8.40% (p=0.000 n=10) Match/"a*"_"ab/c"-8 23.18n ± 1% 21.48n ± 1% -7.35% (p=0.000 n=10) Match/"a*/b"_"abc/b"-8 54.96n ± 1% 52.06n ± 0% -5.29% (p=0.000 n=10) Match/"a*/b"_"a/c/b"-8 34.50n ± 3% 31.42n ± 2% -8.91% (p=0.000 n=10) Match/"a*b*c*d*e*/f"_"axbxcxdxe/f"-8 125.4n ± 1% 114.9n ± 1% -8.45% (p=0.000 n=10) Match/"a*b*c*d*e*/f"_"axbxcxdxexxx/f"-8 151.1n ± 1% 149.4n ± 1% -1.09% (p=0.001 n=10) Match/"a*b*c*d*e*/f"_"axbxcxdxe/xxx/f"-8 127.8n ± 4% 115.6n ± 3% -9.51% (p=0.000 n=10) Match/"a*b*c*d*e*/f"_"axbxcxdxexxx/fff"-8 155.6n ± 1% 168.0n ± 11% ~ (p=0.143 n=10) Match/"a*b?c*x"_"abxbbxdbxebxczzx"-8 189.2n ± 1% 193.5n ± 6% ~ (p=1.000 n=10) Match/"a*b?c*x"_"abxbbxdbxebxczzy"-8 196.3n ± 1% 190.6n ± 2% -2.93% (p=0.001 n=10) Match/"ab[c]"_"abc"-8 39.50n ± 1% 37.84n ± 1% -4.20% (p=0.000 n=10) Match/"ab[b-d]"_"abc"-8 47.94n ± 1% 45.99n ± 1% -4.06% (p=0.000 n=10) Match/"ab[e-g]"_"abc"-8 49.48n ± 1% 47.57n ± 1% -3.85% (p=0.000 n=10) Match/"ab[^c]"_"abc"-8 41.55n ± 1% 38.01n ± 2% -8.51% (p=0.000 n=10) Match/"ab[^b-d]"_"abc"-8 50.48n ± 1% 46.35n ± 0% -8.17% (p=0.000 n=10) Match/"ab[^e-g]"_"abc"-8 50.12n ± 3% 47.37n ± 2% -5.47% (p=0.000 n=10) Match/"a\\*b"_"a*b"-8 24.59n ± 0% 21.77n ± 1% -11.47% (p=0.000 n=10) Match/"a\\*b"_"ab"-8 26.14n ± 1% 24.52n ± 3% -6.18% (p=0.000 n=10) Match/"a?b"_"a☺b"-8 25.66n ± 2% 23.40n ± 1% -8.81% (p=0.000 n=10) Match/"a[^a]b"_"a☺b"-8 41.14n ± 0% 38.97n ± 3% -5.29% (p=0.001 n=10) Match/"a???b"_"a☺b"-8 39.66n ± 6% 36.63n ± 6% -7.63% (p=0.003 n=10) Match/"a[^a][^a][^a]b"_"a☺b"-8 85.07n ± 8% 84.97n ± 1% ~ (p=0.971 n=10) Match/"[a-ζ]*"_"α"-8 49.45n ± 4% 48.34n ± 2% -2.23% (p=0.001 n=10) Match/"*[a-ζ]"_"A"-8 67.67n ± 3% 70.53n ± 2% +4.23% (p=0.000 n=10) Match/"a?b"_"a/b"-8 26.61n ± 5% 25.92n ± 1% -2.59% (p=0.000 n=10) Match/"a*b"_"a/b"-8 29.38n ± 3% 26.61n ± 2% -9.46% (p=0.000 n=10) Match/"[\\]a]"_"]"-8 40.75n ± 3% 39.31n ± 2% -3.52% (p=0.000 n=10) Match/"[\\-]"_"-"-8 30.58n ± 1% 30.53n ± 2% ~ (p=0.565 n=10) Match/"[x\\-]"_"x"-8 41.02n ± 2% 39.32n ± 1% -4.13% (p=0.000 n=10) Match/"[x\\-]"_"-"-8 40.74n ± 2% 39.82n ± 1% -2.25% (p=0.000 n=10) Match/"[x\\-]"_"z"-8 42.19n ± 1% 40.47n ± 1% -4.08% (p=0.001 n=10) Match/"[\\-x]"_"x"-8 40.77n ± 1% 39.61n ± 3% -2.86% (p=0.000 n=10) Match/"[\\-x]"_"-"-8 40.94n ± 2% 39.39n ± 0% -3.79% (p=0.000 n=10) Match/"[\\-x]"_"a"-8 42.12n ± 2% 42.11n ± 1% ~ (p=0.954 n=10) Match/"[]a]"_"]"-8 23.73n ± 2% 21.67n ± 2% -8.72% (p=0.000 n=10) Match/"[-]"_"-"-8 21.28n ± 1% 19.96n ± 1% -6.16% (p=0.000 n=10) Match/"[x-]"_"x"-8 28.96n ± 1% 27.98n ± 3% -3.37% (p=0.000 n=10) Match/"[x-]"_"-"-8 29.46n ± 2% 29.27n ± 7% ~ (p=0.796 n=10) Match/"[x-]"_"z"-8 29.14n ± 0% 30.78n ± 9% ~ (p=0.468 n=10) Match/"[-x]"_"x"-8 23.62n ± 3% 22.08n ± 1% -6.54% (p=0.000 n=10) Match/"[-x]"_"-"-8 23.70n ± 2% 22.11n ± 2% -6.69% (p=0.000 n=10) Match/"[-x]"_"a"-8 23.71n ± 0% 22.09n ± 3% -6.83% (p=0.000 n=10) Match/"\\"_"a"-8 11.845n ± 2% 9.496n ± 3% -19.83% (p=0.000 n=10) Match/"[a-b-c]"_"a"-8 39.85n ± 1% 40.69n ± 1% +2.10% (p=0.000 n=10) Match/"["_"a"-8 18.57n ± 3% 16.47n ± 3% -11.33% (p=0.000 n=10) Match/"[^"_"a"-8 20.30n ± 3% 17.96n ± 1% -11.53% (p=0.000 n=10) Match/"[^bc"_"a"-8 36.81n ± 7% 32.87n ± 2% -10.72% (p=0.000 n=10) Match/"a["_"a"-8 20.11n ± 1% 19.41n ± 1% -3.46% (p=0.000 n=10) Match/"a["_"ab"-8 23.19n ± 2% 21.80n ± 1% -6.02% (p=0.000 n=10) Match/"a["_"x"-8 20.79n ± 1% 19.51n ± 1% -6.13% (p=0.000 n=10) Match/"a/b["_"x"-8 29.80n ± 0% 28.36n ± 0% -4.82% (p=0.000 n=10) Match/"*x"_"xxx"-8 30.77n ± 2% 28.55n ± 2% -7.23% (p=0.000 n=10) geomean 37.11n 35.15n -5.29% pkg: path/filepath │ old │ new2 │ │ sec/op │ sec/op vs base │ Match/"abc"_"abc"-8 22.79n ± 3% 21.62n ± 1% -5.11% (p=0.000 n=10) Match/"*"_"abc"-8 11.410n ± 3% 9.595n ± 1% -15.91% (p=0.000 n=10) Match/"*c"_"abc"-8 29.69n ± 2% 29.09n ± 0% -2.00% (p=0.001 n=10) Match/"a*"_"a"-8 23.69n ± 1% 21.12n ± 1% -10.83% (p=0.000 n=10) Match/"a*"_"abc"-8 25.38n ± 4% 22.41n ± 5% -11.70% (p=0.000 n=10) Match/"a*"_"ab/c"-8 24.73n ± 1% 21.85n ± 3% -11.65% (p=0.000 n=10) Match/"a*/b"_"abc/b"-8 53.73n ± 2% 53.30n ± 2% -0.82% (p=0.037 n=10) Match/"a*/b"_"a/c/b"-8 32.97n ± 2% 31.64n ± 0% -4.02% (p=0.000 n=10) Match/"a*b*c*d*e*/f"_"axbxcxdxe/f"-8 120.7n ± 0% 124.0n ± 1% +2.82% (p=0.000 n=10) Match/"a*b*c*d*e*/f"_"axbxcxdxexxx/f"-8 149.4n ± 2% 160.0n ± 6% +7.13% (p=0.009 n=10) Match/"a*b*c*d*e*/f"_"axbxcxdxe/xxx/f"-8 121.8n ± 2% 122.8n ± 9% ~ (p=0.898 n=10) Match/"a*b*c*d*e*/f"_"axbxcxdxexxx/fff"-8 149.7n ± 1% 151.2n ± 2% +1.04% (p=0.005 n=10) Match/"a*b?c*x"_"abxbbxdbxebxczzx"-8 187.8n ± 1% 184.8n ± 1% -1.57% (p=0.001 n=10) Match/"a*b?c*x"_"abxbbxdbxebxczzy"-8 195.6n ± 1% 191.2n ± 2% -2.22% (p=0.018 n=10) Match/"ab[c]"_"abc"-8 40.30n ± 4% 37.31n ± 3% -7.41% (p=0.000 n=10) Match/"ab[b-d]"_"abc"-8 47.92n ± 4% 45.92n ± 4% -4.17% (p=0.002 n=10) Match/"ab[e-g]"_"abc"-8 47.58n ± 4% 45.22n ± 0% -4.98% (p=0.000 n=10) Match/"ab[^c]"_"abc"-8 40.33n ± 2% 36.95n ± 2% -8.38% (p=0.000 n=10) Match/"ab[^b-d]"_"abc"-8 48.68n ± 2% 46.49n ± 3% -4.50% (p=0.001 n=10) Match/"ab[^e-g]"_"abc"-8 49.11n ± 2% 48.42n ± 2% -1.42% (p=0.014 n=10) Match/"a\\*b"_"a*b"-8 26.79n ± 9% 23.70n ± 1% -11.53% (p=0.000 n=10) Match/"a\\*b"_"ab"-8 23.84n ± 11% 25.99n ± 1% +9.02% (p=0.015 n=10) Match/"a?b"_"a☺b"-8 25.72n ± 2% 23.70n ± 1% -7.87% (p=0.000 n=10) Match/"a[^a]b"_"a☺b"-8 41.72n ± 2% 39.24n ± 2% -5.94% (p=0.000 n=10) Match/"a???b"_"a☺b"-8 35.94n ± 1% 35.30n ± 3% -1.78% (p=0.009 n=10) Match/"a[^a][^a][^a]b"_"a☺b"-8 82.17n ± 0% 84.56n ± 3% +2.91% (p=0.000 n=10) Match/"[a-ζ]*"_"α"-8 52.01n ± 3% 49.78n ± 0% -4.30% (p=0.000 n=10) Match/"*[a-ζ]"_"A"-8 65.62n ± 1% 66.91n ± 3% +1.97% (p=0.000 n=10) Match/"a?b"_"a/b"-8 24.60n ± 2% 25.60n ± 1% +4.07% (p=0.001 n=10) Match/"a*b"_"a/b"-8 28.00n ± 1% 26.50n ± 1% -5.37% (p=0.000 n=10) Match/"[\\]a]"_"]"-8 40.72n ± 1% 39.55n ± 2% -2.86% (p=0.002 n=10) Match/"[\\-]"_"-"-8 30.52n ± 2% 30.15n ± 1% -1.18% (p=0.003 n=10) Match/"[x\\-]"_"x"-8 40.69n ± 1% 40.41n ± 2% ~ (p=0.105 n=10) Match/"[x\\-]"_"-"-8 40.50n ± 2% 40.69n ± 2% ~ (p=0.109 n=10) Match/"[x\\-]"_"z"-8 40.80n ± 3% 39.92n ± 2% -2.17% (p=0.002 n=10) Match/"[\\-x]"_"x"-8 40.73n ± 2% 43.78n ± 5% +7.49% (p=0.003 n=10) Match/"[\\-x]"_"-"-8 40.86n ± 2% 39.58n ± 9% ~ (p=0.105 n=10) Match/"[\\-x]"_"a"-8 40.67n ± 0% 40.85n ± 3% ~ (p=0.288 n=10) Match/"[]a]"_"]"-8 22.71n ± 1% 20.35n ± 2% -10.37% (p=0.000 n=10) Match/"[-]"_"-"-8 20.98n ± 2% 19.71n ± 2% -6.10% (p=0.000 n=10) Match/"[x-]"_"x"-8 30.78n ± 1% 26.29n ± 1% -14.56% (p=0.000 n=10) Match/"[x-]"_"-"-8 30.79n ± 2% 26.38n ± 3% -14.29% (p=0.000 n=10) Match/"[x-]"_"z"-8 30.77n ± 0% 26.47n ± 1% -13.97% (p=0.000 n=10) Match/"[-x]"_"x"-8 23.73n ± 1% 21.08n ± 1% -11.19% (p=0.000 n=10) Match/"[-x]"_"-"-8 23.68n ± 1% 21.11n ± 3% -10.81% (p=0.000 n=10) Match/"[-x]"_"a"-8 23.74n ± 3% 21.04n ± 0% -11.37% (p=0.000 n=10) Match/"\\"_"a"-8 11.57n ± 0% 10.35n ± 0% -10.63% (p=0.000 n=10) Match/"[a-b-c]"_"a"-8 42.46n ± 1% 38.97n ± 3% -8.23% (p=0.000 n=10) Match/"["_"a"-8 18.03n ± 7% 15.95n ± 1% -11.51% (p=0.000 n=10) Match/"[^"_"a"-8 19.20n ± 11% 17.96n ± 3% -6.41% (p=0.000 n=10) Match/"[^bc"_"a"-8 32.82n ± 3% 32.34n ± 0% -1.45% (p=0.000 n=10) Match/"a["_"a"-8 19.48n ± 2% 19.48n ± 2% ~ (p=0.670 n=10) Match/"a["_"ab"-8 22.19n ± 3% 22.09n ± 1% ~ (p=0.148 n=10) Match/"a["_"x"-8 20.32n ± 3% 19.66n ± 1% -3.27% (p=0.001 n=10) Match/"a/b["_"x"-8 28.68n ± 1% 28.52n ± 1% ~ (p=0.315 n=10) Match/"*x"_"xxx"-8 29.95n ± 3% 29.27n ± 3% -2.27% (p=0.006 n=10) geomean 36.76n 35.10n -4.51% Change-Id: I02d07b7a066e5789587035180fa15ae07a9579a6 GitHub-Last-Rev: 8b314b430920f9636088d0291adf8d518fc56b0a GitHub-Pull-Request: golang/go#75211 Reviewed-on: https://go-review.googlesource.com/c/go/+/700315 Reviewed-by: Emmanuel Odeke Reviewed-by: Keith Randall Reviewed-by: Kirill Kolyshkin Reviewed-by: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI --- src/cmd/compile/internal/test/inl_test.go | 6 ++++ src/path/filepath/match.go | 36 +++++++---------------- src/path/filepath/match_test.go | 17 +++++++++++ src/path/match.go | 28 +++++------------- src/path/match_test.go | 18 ++++++++++++ 5 files changed, 60 insertions(+), 45 deletions(-) diff --git a/src/cmd/compile/internal/test/inl_test.go b/src/cmd/compile/internal/test/inl_test.go index a49cd767db43d8..4e51d7d9d13543 100644 --- a/src/cmd/compile/internal/test/inl_test.go +++ b/src/cmd/compile/internal/test/inl_test.go @@ -233,6 +233,12 @@ func TestIntendedInlining(t *testing.T) { "testing": { "(*B).Loop", }, + "path": { + "scanChunk", + }, + "path/filepath": { + "scanChunk", + }, } if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" { diff --git a/src/path/filepath/match.go b/src/path/filepath/match.go index b93e89adb03a31..7ccf71ee0c3dd8 100644 --- a/src/path/filepath/match.go +++ b/src/path/filepath/match.go @@ -94,16 +94,12 @@ func scanChunk(pattern string) (star bool, chunk, rest string) { star = true } inrange := false - var i int -Scan: - for i = 0; i < len(pattern); i++ { + for i := 0; i < len(pattern); i++ { switch pattern[i] { case '\\': - if runtime.GOOS != "windows" { - // error check handled in matchChunk: bad pattern. - if i+1 < len(pattern) { - i++ - } + // error check handled in matchChunk: bad pattern. + if runtime.GOOS != "windows" && i+1 < len(pattern) { + i++ } case '[': inrange = true @@ -111,11 +107,11 @@ Scan: inrange = false case '*': if !inrange { - break Scan + return star, pattern[:i], pattern[i:] } } } - return star, pattern[0:i], pattern[i:] + return star, pattern, "" } // matchChunk checks whether chunk matches the beginning of s. @@ -127,9 +123,7 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) { // checking that the pattern is well-formed but no longer reading s. failed := false for len(chunk) > 0 { - if !failed && len(s) == 0 { - failed = true - } + failed = failed || len(s) == 0 switch chunk[0] { case '[': // character class @@ -164,20 +158,14 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) { return "", false, err } } - if lo <= r && r <= hi { - match = true - } + match = match || lo <= r && r <= hi nrange++ } - if match == negated { - failed = true - } + failed = failed || match == negated case '?': if !failed { - if s[0] == Separator { - failed = true - } + failed = s[0] == Separator _, n := utf8.DecodeRuneInString(s) s = s[n:] } @@ -194,9 +182,7 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) { default: if !failed { - if chunk[0] != s[0] { - failed = true - } + failed = chunk[0] != s[0] s = s[1:] } chunk = chunk[1:] diff --git a/src/path/filepath/match_test.go b/src/path/filepath/match_test.go index 2ae79980c753ee..51ccaecaa99fff 100644 --- a/src/path/filepath/match_test.go +++ b/src/path/filepath/match_test.go @@ -106,6 +106,23 @@ func TestMatch(t *testing.T) { } } +func BenchmarkMatch(b *testing.B) { + for _, tt := range matchTests { + name := fmt.Sprintf("%q %q", tt.pattern, tt.s) + b.Run(name, func(b *testing.B) { + b.ReportAllocs() + for range b.N { + bSink, errSink = Match(tt.pattern, tt.s) + } + }) + } +} + +var ( + bSink bool + errSink error +) + var globTests = []struct { pattern, result string }{ diff --git a/src/path/match.go b/src/path/match.go index d8b6809568fbdc..0678effd6e5fba 100644 --- a/src/path/match.go +++ b/src/path/match.go @@ -95,9 +95,7 @@ func scanChunk(pattern string) (star bool, chunk, rest string) { star = true } inrange := false - var i int -Scan: - for i = 0; i < len(pattern); i++ { + for i := 0; i < len(pattern); i++ { switch pattern[i] { case '\\': // error check handled in matchChunk: bad pattern. @@ -110,11 +108,11 @@ Scan: inrange = false case '*': if !inrange { - break Scan + return star, pattern[:i], pattern[i:] } } } - return star, pattern[0:i], pattern[i:] + return star, pattern, "" } // matchChunk checks whether chunk matches the beginning of s. @@ -126,9 +124,7 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) { // checking that the pattern is well-formed but no longer reading s. failed := false for len(chunk) > 0 { - if !failed && len(s) == 0 { - failed = true - } + failed = failed || len(s) == 0 switch chunk[0] { case '[': // character class @@ -163,20 +159,14 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) { return "", false, err } } - if lo <= r && r <= hi { - match = true - } + match = match || lo <= r && r <= hi nrange++ } - if match == negated { - failed = true - } + failed = failed || match == negated case '?': if !failed { - if s[0] == '/' { - failed = true - } + failed = s[0] == '/' _, n := utf8.DecodeRuneInString(s) s = s[n:] } @@ -191,9 +181,7 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) { default: if !failed { - if chunk[0] != s[0] { - failed = true - } + failed = chunk[0] != s[0] s = s[1:] } chunk = chunk[1:] diff --git a/src/path/match_test.go b/src/path/match_test.go index 996bd691eb82b6..90ca19a6f457a3 100644 --- a/src/path/match_test.go +++ b/src/path/match_test.go @@ -5,6 +5,7 @@ package path_test import ( + "fmt" . "path" "testing" ) @@ -82,3 +83,20 @@ func TestMatch(t *testing.T) { } } } + +func BenchmarkMatch(b *testing.B) { + for _, tt := range matchTests { + name := fmt.Sprintf("%q %q", tt.pattern, tt.s) + b.Run(name, func(b *testing.B) { + b.ReportAllocs() + for range b.N { + bSink, errSink = Match(tt.pattern, tt.s) + } + }) + } +} + +var ( + bSink bool + errSink error +) From b8cc907425c4b851d2b941cf689cf8177ea8a153 Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Fri, 29 Aug 2025 16:20:16 +0800 Subject: [PATCH 21/40] cmd/internal/obj/loong64: fix the usage of offset in the instructions [X]VLDREPL.{B/H/W/D} The previously defined usage of offset was ambiguous and not easy to understand. For example, to fetch 4 bytes of data from the address base+8 and broadcast it to each word element of vector register V5, the assembly implementation is as follows: previous: VMOVQ 2(base), V5.W4 current: VMOVQ 8(base), V5.W4 Change-Id: I8bc84e35033ab63bd10f4c61618789f94314f78c Reviewed-on: https://go-review.googlesource.com/c/go/+/699875 Reviewed-by: Cherry Mui Reviewed-by: Michael Pratt Reviewed-by: abner chenc Auto-Submit: Michael Pratt Reviewed-by: Meidan Li LUCI-TryBot-Result: Go LUCI --- .../asm/internal/asm/testdata/loong64enc1.s | 28 +++++++++++---- src/cmd/internal/obj/loong64/asm.go | 35 ++++++++++++++++++- src/cmd/internal/obj/loong64/doc.go | 9 +++++ src/internal/chacha8rand/chacha8_loong64.s | 20 +++++------ 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index 63676cc785967c..c5c6a4479a4060 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -538,13 +538,29 @@ lable2: // Load data from memory and broadcast to each element of a vector register: VMOVQ offset(Rj), . VMOVQ (R4), V0.B16 // 80008030 - VMOVQ 1(R4), V1.H8 // 81044030 - VMOVQ 2(R4), V2.W4 // 82082030 - VMOVQ 3(R4), V3.V2 // 830c1030 + VMOVQ 1(R4), V0.B16 // 80048030 + VMOVQ -3(R4), V0.B16 // 80f4bf30 + VMOVQ (R4), V1.H8 // 81004030 + VMOVQ 2(R4), V1.H8 // 81044030 + VMOVQ -6(R4), V1.H8 // 81f45f30 + VMOVQ (R4), V2.W4 // 82002030 + VMOVQ 8(R4), V2.W4 // 82082030 + VMOVQ -12(R4), V2.W4 // 82f42f30 + VMOVQ (R4), V3.V2 // 83001030 + VMOVQ 24(R4), V3.V2 // 830c1030 + VMOVQ -16(R4), V3.V2 // 83f81730 XVMOVQ (R4), X0.B32 // 80008032 - XVMOVQ 1(R4), X1.H16 // 81044032 - XVMOVQ 2(R4), X2.W8 // 82082032 - XVMOVQ 3(R4), X3.V4 // 830c1032 + XVMOVQ 1(R4), X0.B32 // 80048032 + XVMOVQ -5(R4), X0.B32 // 80ecbf32 + XVMOVQ (R4), X1.H16 // 81004032 + XVMOVQ 2(R4), X1.H16 // 81044032 + XVMOVQ -10(R4), X1.H16 // 81ec5f32 + XVMOVQ (R4), X2.W8 // 82002032 + XVMOVQ 8(R4), X2.W8 // 82082032 + XVMOVQ -20(R4), X2.W8 // 82ec2f32 + XVMOVQ (R4), X3.V4 // 83001032 + XVMOVQ 24(R4), X3.V4 // 830c1032 + XVMOVQ -24(R4), X3.V4 // 83f41732 // VSEQ{B,H,W,V}, XVSEQ{B,H,W,V} instruction VSEQB V1, V2, V3 // 43040070 diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 1b982f6c86fa53..35b33b93768428 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -1983,6 +1983,18 @@ func OP_12IRR(op uint32, i uint32, r2 uint32, r3 uint32) uint32 { return op | (i&0xFFF)<<10 | (r2&0x1F)<<5 | (r3&0x1F)<<0 } +func OP_11IRR(op uint32, i uint32, r2 uint32, r3 uint32) uint32 { + return op | (i&0x7FF)<<10 | (r2&0x1F)<<5 | (r3&0x1F)<<0 +} + +func OP_10IRR(op uint32, i uint32, r2 uint32, r3 uint32) uint32 { + return op | (i&0x3FF)<<10 | (r2&0x1F)<<5 | (r3&0x1F)<<0 +} + +func OP_9IRR(op uint32, i uint32, r2 uint32, r3 uint32) uint32 { + return op | (i&0x1FF)<<10 | (r2&0x1F)<<5 | (r3&0x1F)<<0 +} + func OP_8IRR(op uint32, i uint32, r2 uint32, r3 uint32) uint32 { return op | (i&0xFF)<<10 | (r2&0x1F)<<5 | (r3&0x1F)<<0 } @@ -2535,7 +2547,28 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { si := c.regoff(&p.From) Rj := uint32(p.From.Reg & EXT_REG_MASK) Vd := uint32(p.To.Reg & EXT_REG_MASK) - o1 = v | uint32(si<<10) | (Rj << 5) | Vd + switch v & 0xc00000 { + case 0x800000: // [x]vldrepl.b + o1 = OP_12IRR(v, uint32(si), Rj, Vd) + case 0x400000: // [x]vldrepl.h + if si&1 != 0 { + c.ctxt.Diag("%v: offset must be a multiple of 2.\n", p) + } + o1 = OP_11IRR(v, uint32(si>>1), Rj, Vd) + case 0x0: + switch v & 0x300000 { + case 0x200000: // [x]vldrepl.w + if si&3 != 0 { + c.ctxt.Diag("%v: offset must be a multiple of 4.\n", p) + } + o1 = OP_10IRR(v, uint32(si>>2), Rj, Vd) + case 0x100000: // [x]vldrepl.d + if si&7 != 0 { + c.ctxt.Diag("%v: offset must be a multiple of 8.\n", p) + } + o1 = OP_9IRR(v, uint32(si>>3), Rj, Vd) + } + } case 47: // preld offset(Rbase), $hint offs := c.regoff(&p.From) diff --git a/src/cmd/internal/obj/loong64/doc.go b/src/cmd/internal/obj/loong64/doc.go index 6c8f2618a2cb73..20c5a9e0a6faa8 100644 --- a/src/cmd/internal/obj/loong64/doc.go +++ b/src/cmd/internal/obj/loong64/doc.go @@ -220,6 +220,15 @@ Note: In the following sections 3.1 to 3.6, "ui4" (4-bit unsigned int immediate) XVMOVQ offset(Rj), Xd.W8 | xvldrepl.w Xd, Rj, si10 | for i in range(8) : XR[xd].w[i] = load 32 bit memory data from (GR[rj]+SignExtend(si10<<2)) XVMOVQ offset(Rj), Xd.V4 | xvldrepl.d Xd, Rj, si9 | for i in range(4) : XR[xd].d[i] = load 64 bit memory data from (GR[rj]+SignExtend(si9<<3)) + note: In Go assembly, for ease of understanding, offset representing the actual address offset. + However, during platform encoding, the offset is shifted to increase the encodable offset range, as follows: + + Go assembly | platform assembly + VMOVQ 1(R4), V5.B16 | vldrepl.b v5, r4, $1 + VMOVQ 2(R4), V5.H8 | vldrepl.h v5, r4, $1 + VMOVQ 8(R4), V5.W4 | vldrepl.w v5, r4, $2 + VMOVQ 8(R4), V5.V2 | vldrepl.d v5, r4, $1 + # Special instruction encoding definition and description on LoongArch 1. DBAR hint encoding for LA664(Loongson 3A6000) and later micro-architectures, paraphrased diff --git a/src/internal/chacha8rand/chacha8_loong64.s b/src/internal/chacha8rand/chacha8_loong64.s index 5e6857ed3a6598..73a1e5bf05f659 100644 --- a/src/internal/chacha8rand/chacha8_loong64.s +++ b/src/internal/chacha8rand/chacha8_loong64.s @@ -50,22 +50,22 @@ lsx_chacha8: // load contants VMOVQ (R10), V0.W4 - VMOVQ 1(R10), V1.W4 - VMOVQ 2(R10), V2.W4 - VMOVQ 3(R10), V3.W4 + VMOVQ 4(R10), V1.W4 + VMOVQ 8(R10), V2.W4 + VMOVQ 12(R10), V3.W4 // load 4-32bit data from incRotMatrix added to counter VMOVQ (R11), V30 // load seed VMOVQ (R4), V4.W4 - VMOVQ 1(R4), V5.W4 - VMOVQ 2(R4), V6.W4 - VMOVQ 3(R4), V7.W4 - VMOVQ 4(R4), V8.W4 - VMOVQ 5(R4), V9.W4 - VMOVQ 6(R4), V10.W4 - VMOVQ 7(R4), V11.W4 + VMOVQ 4(R4), V5.W4 + VMOVQ 8(R4), V6.W4 + VMOVQ 12(R4), V7.W4 + VMOVQ 16(R4), V8.W4 + VMOVQ 20(R4), V9.W4 + VMOVQ 24(R4), V10.W4 + VMOVQ 28(R4), V11.W4 // load counter and update counter VMOVQ R6, V12.W4 From 4f7bbc62c791d7e82218dbc097294649352de684 Mon Sep 17 00:00:00 2001 From: limeidan Date: Mon, 1 Sep 2025 09:21:13 +0800 Subject: [PATCH 22/40] runtime, cmd/compile, cmd/internal/obj: remove duff support for loong64 Change-Id: I44d6452933c8010f7dfbf821a32053f9d1cf151e Reviewed-on: https://go-review.googlesource.com/c/go/+/700096 LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: sophie zhao Reviewed-by: abner chenc Reviewed-by: Michael Pratt Reviewed-by: Keith Randall Auto-Submit: Michael Pratt --- src/cmd/compile/internal/loong64/ssa.go | 13 - .../compile/internal/ssa/_gen/LOONG64Ops.go | 37 - src/cmd/compile/internal/ssa/opGen.go | 28 - src/cmd/internal/obj/loong64/a.out.go | 2 - src/cmd/internal/obj/loong64/asm.go | 10 +- src/cmd/internal/obj/loong64/obj.go | 44 +- src/runtime/duff_loong64.s | 907 ------------------ src/runtime/mkduff.go | 25 - 8 files changed, 4 insertions(+), 1062 deletions(-) delete mode 100644 src/runtime/duff_loong64.s diff --git a/src/cmd/compile/internal/loong64/ssa.go b/src/cmd/compile/internal/loong64/ssa.go index 3959f8a7c11eb9..134575c85ca658 100644 --- a/src/cmd/compile/internal/loong64/ssa.go +++ b/src/cmd/compile/internal/loong64/ssa.go @@ -552,13 +552,6 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() - case ssa.OpLOONG64DUFFZERO: - // runtime.duffzero expects start address in R20 - p := s.Prog(obj.ADUFFZERO) - p.To.Type = obj.TYPE_MEM - p.To.Name = obj.NAME_EXTERN - p.To.Sym = ir.Syms.Duffzero - p.To.Offset = v.AuxInt case ssa.OpLOONG64LoweredZero: ptrReg := v.Args[0].Reg() n := v.AuxInt @@ -652,12 +645,6 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { zero8(s, ptrReg, off+n-8) } - case ssa.OpLOONG64DUFFCOPY: - p := s.Prog(obj.ADUFFCOPY) - p.To.Type = obj.TYPE_MEM - p.To.Name = obj.NAME_EXTERN - p.To.Sym = ir.Syms.Duffcopy - p.To.Offset = v.AuxInt case ssa.OpLOONG64LoweredMove: dstReg := v.Args[0].Reg() srcReg := v.Args[1].Reg() diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go index cc6ae8fb8e65de..bee619f6d93a78 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go @@ -360,24 +360,6 @@ func init() { {name: "CALLclosure", argLength: -1, reg: regInfo{inputs: []regMask{gpsp, buildReg("R29"), 0}, clobbers: callerSave}, aux: "CallOff", clobberFlags: true, call: true}, // call function via closure. arg0=codeptr, arg1=closure, last arg=mem, auxint=argsize, returns mem {name: "CALLinter", argLength: -1, reg: regInfo{inputs: []regMask{gp}, clobbers: callerSave}, aux: "CallOff", clobberFlags: true, call: true}, // call fn by pointer. arg0=codeptr, last arg=mem, auxint=argsize, returns mem - // duffzero - // arg0 = address of memory to zero - // arg1 = mem - // auxint = offset into duffzero code to start executing - // returns mem - // R20 aka loong64.REGRT1 changed as side effect - { - name: "DUFFZERO", - aux: "Int64", - argLength: 2, - reg: regInfo{ - inputs: []regMask{buildReg("R20")}, - clobbers: buildReg("R20 R1"), - }, - typ: "Mem", - faultOnNilArg0: true, - }, - // medium zeroing // arg0 = address of memory to zero // arg1 = mem @@ -393,25 +375,6 @@ func init() { faultOnNilArg0: true, }, - // duffcopy - // arg0 = address of dst memory (in R21, changed as side effect) - // arg1 = address of src memory (in R20, changed as side effect) - // arg2 = mem - // auxint = offset into duffcopy code to start executing - // returns mem - { - name: "DUFFCOPY", - aux: "Int64", - argLength: 3, - reg: regInfo{ - inputs: []regMask{buildReg("R21"), buildReg("R20")}, - clobbers: buildReg("R20 R21 R1"), - }, - typ: "Mem", - faultOnNilArg0: true, - faultOnNilArg1: true, - }, - // large zeroing // arg0 = address of memory to zero // arg1 = mem diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index f42d64228fae3a..4aef2d2aa1e9c5 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1924,9 +1924,7 @@ const ( OpLOONG64CALLtail OpLOONG64CALLclosure OpLOONG64CALLinter - OpLOONG64DUFFZERO OpLOONG64LoweredZero - OpLOONG64DUFFCOPY OpLOONG64LoweredZeroLoop OpLOONG64LoweredMove OpLOONG64LoweredMoveLoop @@ -25934,18 +25932,6 @@ var opcodeTable = [...]opInfo{ clobbers: 4611686018427387896, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 g R23 R24 R25 R26 R27 R28 R29 R31 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 }, }, - { - name: "DUFFZERO", - auxType: auxInt64, - argLen: 2, - faultOnNilArg0: true, - reg: regInfo{ - inputs: []inputInfo{ - {0, 524288}, // R20 - }, - clobbers: 524290, // R1 R20 - }, - }, { name: "LoweredZero", auxType: auxInt64, @@ -25957,20 +25943,6 @@ var opcodeTable = [...]opInfo{ }, }, }, - { - name: "DUFFCOPY", - auxType: auxInt64, - argLen: 3, - faultOnNilArg0: true, - faultOnNilArg1: true, - reg: regInfo{ - inputs: []inputInfo{ - {0, 1048576}, // R21 - {1, 524288}, // R20 - }, - clobbers: 1572866, // R1 R20 R21 - }, - }, { name: "LoweredZeroLoop", auxType: auxInt64, diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index 8e651cdfef0e21..7ab85a2f2378aa 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -225,8 +225,6 @@ const ( REGZERO = REG_R0 // set to zero REGLINK = REG_R1 REGSP = REG_R3 - REGRT1 = REG_R20 // reserved for runtime, duffzero and duffcopy - REGRT2 = REG_R21 // reserved for runtime, duffcopy REGCTXT = REG_R29 // context for closures REGG = REG_R22 // G in loong64 REGTMP = REG_R30 // used by the assembler diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 35b33b93768428..848d77fd0d92aa 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -440,8 +440,6 @@ var optab = []Optab{ {obj.ANOP, C_DCON, C_NONE, C_NONE, C_NONE, C_NONE, 0, 0, 0, 0}, // nop variants, see #40689 {obj.ANOP, C_REG, C_NONE, C_NONE, C_NONE, C_NONE, 0, 0, 0, 0}, {obj.ANOP, C_FREG, C_NONE, C_NONE, C_NONE, C_NONE, 0, 0, 0, 0}, - {obj.ADUFFZERO, C_NONE, C_NONE, C_NONE, C_BRAN, C_NONE, 11, 4, 0, 0}, // same as AJMP - {obj.ADUFFCOPY, C_NONE, C_NONE, C_NONE, C_BRAN, C_NONE, 11, 4, 0, 0}, // same as AJMP } var atomicInst = map[obj.As]uint32{ @@ -1526,9 +1524,7 @@ func buildop(ctxt *obj.Link) { obj.ATEXT, obj.AFUNCDATA, obj.APCALIGN, - obj.APCDATA, - obj.ADUFFZERO, - obj.ADUFFCOPY: + obj.APCDATA: break case ARDTIMELW: @@ -4040,9 +4036,7 @@ func (c *ctxt0) opirr(a obj.As) uint32 { case AJMP: return 0x14 << 26 - case AJAL, - obj.ADUFFZERO, - obj.ADUFFCOPY: + case AJAL: return 0x15 << 26 case AJIRL: diff --git a/src/cmd/internal/obj/loong64/obj.go b/src/cmd/internal/obj/loong64/obj.go index a1eb786da31067..a97217d31657cf 100644 --- a/src/cmd/internal/obj/loong64/obj.go +++ b/src/cmd/internal/obj/loong64/obj.go @@ -17,11 +17,7 @@ import ( func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { // Rewrite JMP/JAL to symbol as TYPE_BRANCH. switch p.As { - case AJMP, - AJAL, - ARET, - obj.ADUFFZERO, - obj.ADUFFCOPY: + case AJMP, AJAL, ARET: if p.To.Sym != nil { p.To.Type = obj.TYPE_BRANCH } @@ -93,40 +89,6 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { } func rewriteToUseGot(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { - // ADUFFxxx $offset - // becomes - // MOVV runtime.duffxxx@GOT, REGTMP - // ADD $offset, REGTMP - // JAL REGTMP - if p.As == obj.ADUFFCOPY || p.As == obj.ADUFFZERO { - var sym *obj.LSym - if p.As == obj.ADUFFZERO { - sym = ctxt.LookupABI("runtime.duffzero", obj.ABIInternal) - } else { - sym = ctxt.LookupABI("runtime.duffcopy", obj.ABIInternal) - } - offset := p.To.Offset - p.As = AMOVV - p.From.Type = obj.TYPE_MEM - p.From.Sym = sym - p.From.Name = obj.NAME_GOTREF - p.To.Type = obj.TYPE_REG - p.To.Reg = REGTMP - p.To.Name = obj.NAME_NONE - p.To.Offset = 0 - p.To.Sym = nil - p1 := obj.Appendp(p, newprog) - p1.As = AADDV - p1.From.Type = obj.TYPE_CONST - p1.From.Offset = offset - p1.To.Type = obj.TYPE_REG - p1.To.Reg = REGTMP - p2 := obj.Appendp(p1, newprog) - p2.As = AJAL - p2.To.Type = obj.TYPE_MEM - p2.To.Reg = REGTMP - } - // We only care about global data: NAME_EXTERN means a global // symbol in the Go sense, and p.Sym.Local is true for a few // internally defined symbols. @@ -256,9 +218,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } } - case AJAL, - obj.ADUFFZERO, - obj.ADUFFCOPY: + case AJAL: c.cursym.Func().Text.Mark &^= LEAF fallthrough diff --git a/src/runtime/duff_loong64.s b/src/runtime/duff_loong64.s deleted file mode 100644 index b05502d91db2ef..00000000000000 --- a/src/runtime/duff_loong64.s +++ /dev/null @@ -1,907 +0,0 @@ -// Code generated by mkduff.go; DO NOT EDIT. -// Run go generate from src/runtime to update. -// See mkduff.go for comments. - -#include "textflag.h" - -TEXT runtime·duffzero(SB), NOSPLIT|NOFRAME, $0-0 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - MOVV R0, (R20) - ADDV $8, R20 - RET - -TEXT runtime·duffcopy(SB), NOSPLIT|NOFRAME, $0-0 - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - MOVV (R20), R30 - ADDV $8, R20 - MOVV R30, (R21) - ADDV $8, R21 - - RET diff --git a/src/runtime/mkduff.go b/src/runtime/mkduff.go index dfff084e80f1a6..75ff437f262172 100644 --- a/src/runtime/mkduff.go +++ b/src/runtime/mkduff.go @@ -34,7 +34,6 @@ import ( func main() { gen("386", notags, zero386, copy386) gen("arm", notags, zeroARM, copyARM) - gen("loong64", notags, zeroLOONG64, copyLOONG64) gen("ppc64x", tagsPPC64x, zeroPPC64x, copyPPC64x) gen("mips64x", tagsMIPS64x, zeroMIPS64x, copyMIPS64x) gen("riscv64", notags, zeroRISCV64, copyRISCV64) @@ -175,30 +174,6 @@ func copyARM64(w io.Writer) { fmt.Fprintln(w, "\tRET") } -func zeroLOONG64(w io.Writer) { - // R0: always zero - // R20: ptr to memory to be zeroed - // On return, R20 points to the last zeroed dword. - fmt.Fprintln(w, "TEXT runtime·duffzero(SB), NOSPLIT|NOFRAME, $0-0") - for i := 0; i < 128; i++ { - fmt.Fprintln(w, "\tMOVV\tR0, (R20)") - fmt.Fprintln(w, "\tADDV\t$8, R20") - } - fmt.Fprintln(w, "\tRET") -} - -func copyLOONG64(w io.Writer) { - fmt.Fprintln(w, "TEXT runtime·duffcopy(SB), NOSPLIT|NOFRAME, $0-0") - for i := 0; i < 128; i++ { - fmt.Fprintln(w, "\tMOVV\t(R20), R30") - fmt.Fprintln(w, "\tADDV\t$8, R20") - fmt.Fprintln(w, "\tMOVV\tR30, (R21)") - fmt.Fprintln(w, "\tADDV\t$8, R21") - fmt.Fprintln(w) - } - fmt.Fprintln(w, "\tRET") -} - func tagsPPC64x(w io.Writer) { fmt.Fprintln(w) fmt.Fprintln(w, "//go:build ppc64 || ppc64le") From 150fae714eb2bcf0a5fb216ac0e5c7fd76f37e02 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 3 Sep 2025 21:01:51 +0200 Subject: [PATCH 23/40] crypto/x509: don't force system roots load in SetFallbackRoots This change removes the need from SetFallbackRoots to force loading of all system CAs, it postpones that to initSystemRoots. This change also introduces few tests for SetFallbackRoots (linux only), with the use of user and mount namespaces, such that we can control the system CAs in the test. Updates #73691 Change-Id: Ic37270f7825b96d5c3ed8358bbf1895a760a1312 Reviewed-on: https://go-review.googlesource.com/c/go/+/677496 Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker Reviewed-by: Emmanuel Odeke --- src/crypto/x509/root.go | 60 ++++-- src/crypto/x509/root_linux_test.go | 289 +++++++++++++++++++++++++++++ src/crypto/x509/root_test.go | 2 + 3 files changed, 337 insertions(+), 14 deletions(-) create mode 100644 src/crypto/x509/root_linux_test.go diff --git a/src/crypto/x509/root.go b/src/crypto/x509/root.go index fbd43430afa8f9..600f75979d6e9e 100644 --- a/src/crypto/x509/root.go +++ b/src/crypto/x509/root.go @@ -20,11 +20,12 @@ import ( // //go:linkname systemRoots var ( - once sync.Once - systemRootsMu sync.RWMutex - systemRoots *CertPool - systemRootsErr error - fallbacksSet bool + once sync.Once + systemRootsMu sync.RWMutex + systemRoots *CertPool + systemRootsErr error + fallbacksSet bool + useFallbackRoots bool ) func systemRootsPool() *CertPool { @@ -37,10 +38,28 @@ func systemRootsPool() *CertPool { func initSystemRoots() { systemRootsMu.Lock() defer systemRootsMu.Unlock() + + fallbackRoots := systemRoots systemRoots, systemRootsErr = loadSystemRoots() if systemRootsErr != nil { systemRoots = nil } + + if fallbackRoots == nil { + return // no fallbacks to try + } + + systemCertsAvail := systemRoots != nil && (systemRoots.len() > 0 || systemRoots.systemPool) + + if !useFallbackRoots && systemCertsAvail { + return + } + + if useFallbackRoots && systemCertsAvail { + x509usefallbackroots.IncNonDefault() // overriding system certs with fallback certs. + } + + systemRoots, systemRootsErr = fallbackRoots, nil } var x509usefallbackroots = godebug.New("x509usefallbackroots") @@ -63,10 +82,6 @@ func SetFallbackRoots(roots *CertPool) { panic("roots must be non-nil") } - // trigger initSystemRoots if it hasn't already been called before we - // take the lock - _ = systemRootsPool() - systemRootsMu.Lock() defer systemRootsMu.Unlock() @@ -75,11 +90,28 @@ func SetFallbackRoots(roots *CertPool) { } fallbacksSet = true - if systemRoots != nil && (systemRoots.len() > 0 || systemRoots.systemPool) { - if x509usefallbackroots.Value() != "1" { - return - } - x509usefallbackroots.IncNonDefault() + // Handle case when initSystemRoots was not yet executed. + // We handle that specially instead of calling loadSystemRoots, to avoid + // spending excessive amount of cpu here, since the SetFallbackRoots in most cases + // is going to be called at program startup. + if systemRoots == nil && systemRootsErr == nil { + systemRoots = roots + useFallbackRoots = x509usefallbackroots.Value() == "1" + return + } + + once.Do(func() { panic("unreachable") }) // asserts that system roots were indeed loaded before. + + forceFallbackRoots := x509usefallbackroots.Value() == "1" + systemCertsAvail := systemRoots != nil && (systemRoots.len() > 0 || systemRoots.systemPool) + + if !forceFallbackRoots && systemCertsAvail { + return + } + + if forceFallbackRoots && systemCertsAvail { + x509usefallbackroots.IncNonDefault() // overriding system certs with fallback certs. } + systemRoots, systemRootsErr = roots, nil } diff --git a/src/crypto/x509/root_linux_test.go b/src/crypto/x509/root_linux_test.go new file mode 100644 index 00000000000000..d4f92e0972b123 --- /dev/null +++ b/src/crypto/x509/root_linux_test.go @@ -0,0 +1,289 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux + +package x509 + +import ( + "encoding/pem" + "fmt" + "internal/testenv" + "os" + "os/exec" + "syscall" + "testing" +) + +func TestSetFallbackRoots(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + testenv.MustHaveExec(t) + + test := func(t *testing.T, name string, f func(t *testing.T)) { + t.Run(name, func(t *testing.T) { + if os.Getenv("CRYPTO_X509_SETFALLBACKROOTS_TEST") != "1" { + // Execute test in a separate process with CRYPTO_X509_SETFALBACKROOTS_TEST env. + cmd := exec.Command(os.Args[0], fmt.Sprintf("-test.run=^%v$", t.Name())) + cmd.Env = append(os.Environ(), "CRYPTO_X509_SETFALLBACKROOTS_TEST=1") + cmd.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER, + UidMappings: []syscall.SysProcIDMap{{ContainerID: 0, HostID: os.Getuid(), Size: 1}}, + GidMappings: []syscall.SysProcIDMap{{ContainerID: 0, HostID: os.Getgid(), Size: 1}}, + } + out, err := cmd.CombinedOutput() + if err != nil { + if testenv.SyscallIsNotSupported(err) { + t.Skipf("skipping: could not start process with CLONE_NEWNS and CLONE_NEWUSER: %v", err) + } + t.Errorf("%v\n%s", err, out) + } + return + } + + // This test is executed in a separate user and mount namespace, thus + // we can mount a separate "/etc" empty bind mount, without the need for root access. + // On linux all certs reside in /etc, so as we bind an empty dir, we + // get a full control over the system CAs, required for this test. + if err := syscall.Mount(t.TempDir(), "/etc", "", syscall.MS_BIND, ""); err != nil { + if testenv.SyscallIsNotSupported(err) { + t.Skipf("Failed to mount /etc: %v", err) + } + t.Fatalf("Failed to mount /etc: %v", err) + } + + t.Cleanup(func() { + if err := syscall.Unmount("/etc", 0); err != nil { + t.Errorf("failed to unmount /etc: %v", err) + } + }) + + f(t) + }) + } + + newFallbackCertPool := func(t *testing.T) *CertPool { + t.Helper() + + const fallbackCert = `-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- +` + b, _ := pem.Decode([]byte(fallbackCert)) + cert, err := ParseCertificate(b.Bytes) + if err != nil { + t.Fatal(err) + } + p := NewCertPool() + p.AddCert(cert) + return p + } + + installSystemRootCAs := func(t *testing.T) { + t.Helper() + + const systemCAs = `-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +` + if err := os.MkdirAll("/etc/ssl/certs", 06660); err != nil { + t.Fatal(err) + } + + if err := os.WriteFile("/etc/ssl/certs/ca-certificates.crt", []byte(systemCAs), 0666); err != nil { + t.Fatal(err) + } + } + + test(t, "after_first_load_no_system_CAs", func(t *testing.T) { + SystemCertPool() // load system certs, before setting fallbacks + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if !got.Equal(fallback) { + t.Fatal("SystemCertPool returned a non-fallback CertPool") + } + }) + + test(t, "after_first_load_system_CA_read_error", func(t *testing.T) { + // This will fail to load in SystemCertPool since this is a directory, + // rather than a file with certificates. + if err := os.MkdirAll("/etc/ssl/certs/ca-certificates.crt", 0666); err != nil { + t.Fatal(err) + } + + _, err := SystemCertPool() // load system certs, before setting fallbacks + if err == nil { + t.Fatal("unexpected success") + } + + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if !got.Equal(fallback) { + t.Fatal("SystemCertPool returned a non-fallback CertPool") + } + }) + + test(t, "after_first_load_with_system_CAs", func(t *testing.T) { + installSystemRootCAs(t) + + SystemCertPool() // load system certs, before setting fallbacks + + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if got.Equal(fallback) { + t.Fatal("SystemCertPool returned the fallback CertPool") + } + }) + + test(t, "before_first_load_no_system_CAs", func(t *testing.T) { + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if !got.Equal(fallback) { + t.Fatal("SystemCertPool returned a non-fallback CertPool") + } + }) + + test(t, "before_first_load_system_CA_read_error", func(t *testing.T) { + // This will fail to load in SystemCertPool since this is a directory, + // rather than a file with certificates. + if err := os.MkdirAll("/etc/ssl/certs/ca-certificates.crt", 0666); err != nil { + t.Fatal(err) + } + + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if !got.Equal(fallback) { + t.Fatal("SystemCertPool returned a non-fallback CertPool") + } + }) + + test(t, "before_first_load_with_system_CAs", func(t *testing.T) { + installSystemRootCAs(t) + + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if got.Equal(fallback) { + t.Fatal("SystemCertPool returned the fallback CertPool") + } + }) + + test(t, "before_first_load_force_godebug", func(t *testing.T) { + if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil { + t.Fatal(err) + } + + installSystemRootCAs(t) + + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if !got.Equal(fallback) { + t.Fatal("SystemCertPool returned a non-fallback CertPool") + } + }) + + test(t, "after_first_load_force_godebug", func(t *testing.T) { + if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil { + t.Fatal(err) + } + + installSystemRootCAs(t) + SystemCertPool() // load system certs, before setting fallbacks + + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if !got.Equal(fallback) { + t.Fatal("SystemCertPool returned a non-fallback CertPool") + } + }) + + test(t, "after_first_load_force_godebug_no_system_certs", func(t *testing.T) { + if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil { + t.Fatal(err) + } + + SystemCertPool() // load system certs, before setting fallbacks + + fallback := newFallbackCertPool(t) + SetFallbackRoots(fallback) + got, err := SystemCertPool() + if err != nil { + t.Fatal(err) + } + if !got.Equal(fallback) { + t.Fatal("SystemCertPool returned a non-fallback CertPool") + } + }) +} diff --git a/src/crypto/x509/root_test.go b/src/crypto/x509/root_test.go index 94ee6a632d9f02..218d2b6f98c7a6 100644 --- a/src/crypto/x509/root_test.go +++ b/src/crypto/x509/root_test.go @@ -79,8 +79,10 @@ func TestFallback(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + useFallbackRoots = false fallbacksSet = false systemRoots = tc.systemRoots + if systemRoots != nil { systemRoots.systemPool = tc.systemPool } From e36c5aead681d8264f1fac725f2a15c1ca2b895a Mon Sep 17 00:00:00 2001 From: Jes Cok Date: Wed, 27 Aug 2025 14:27:31 +0000 Subject: [PATCH 24/40] log/slog: add multiple handlers support for logger Fixes #65954 Change-Id: Ib01c6f47126ce290108b20c07479c82ef17c427c GitHub-Last-Rev: 34a36ea4bf099b2ad30f35e639155853ff73ef46 GitHub-Pull-Request: golang/go#74840 Reviewed-on: https://go-review.googlesource.com/c/go/+/692237 LUCI-TryBot-Result: Go LUCI Reviewed-by: Jonathan Amsterdam Reviewed-by: Michael Pratt Auto-Submit: Michael Pratt --- api/next/65954.txt | 6 + doc/next/6-stdlib/99-minor/log/slog/65954.md | 6 + src/log/slog/example_multi_handler_test.go | 39 ++++++ src/log/slog/multi_handler.go | 61 ++++++++ src/log/slog/multi_handler_test.go | 139 +++++++++++++++++++ 5 files changed, 251 insertions(+) create mode 100644 api/next/65954.txt create mode 100644 doc/next/6-stdlib/99-minor/log/slog/65954.md create mode 100644 src/log/slog/example_multi_handler_test.go create mode 100644 src/log/slog/multi_handler.go create mode 100644 src/log/slog/multi_handler_test.go diff --git a/api/next/65954.txt b/api/next/65954.txt new file mode 100644 index 00000000000000..88d7c2668fe02d --- /dev/null +++ b/api/next/65954.txt @@ -0,0 +1,6 @@ +pkg log/slog, func NewMultiHandler(...Handler) *MultiHandler #65954 +pkg log/slog, method (*MultiHandler) Enabled(context.Context, Level) bool #65954 +pkg log/slog, method (*MultiHandler) Handle(context.Context, Record) error #65954 +pkg log/slog, method (*MultiHandler) WithAttrs([]Attr) Handler #65954 +pkg log/slog, method (*MultiHandler) WithGroup(string) Handler #65954 +pkg log/slog, type MultiHandler struct #65954 diff --git a/doc/next/6-stdlib/99-minor/log/slog/65954.md b/doc/next/6-stdlib/99-minor/log/slog/65954.md new file mode 100644 index 00000000000000..631ed665df24a7 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/log/slog/65954.md @@ -0,0 +1,6 @@ +The [`NewMultiHandler`](/pkg/log/slog#NewMultiHandler) function creates a +[`MultiHandler`](/pkg/log/slog#MultiHandler) that invokes all the given Handlers. +Its `Enable` method reports whether any of the handlers' `Enabled` methods +return true. +Its `Handle`, `WithAttr` and `WithGroup` methods call the corresponding method +on each of the enabled handlers. diff --git a/src/log/slog/example_multi_handler_test.go b/src/log/slog/example_multi_handler_test.go new file mode 100644 index 00000000000000..daba82c47d72df --- /dev/null +++ b/src/log/slog/example_multi_handler_test.go @@ -0,0 +1,39 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slog_test + +import ( + "bytes" + "log/slog" + "os" +) + +func ExampleMultiHandler() { + removeTime := func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey && len(groups) == 0 { + return slog.Attr{} + } + return a + } + + var textBuf, jsonBuf bytes.Buffer + textHandler := slog.NewTextHandler(&textBuf, &slog.HandlerOptions{ReplaceAttr: removeTime}) + jsonHandler := slog.NewJSONHandler(&jsonBuf, &slog.HandlerOptions{ReplaceAttr: removeTime}) + + multiHandler := slog.NewMultiHandler(textHandler, jsonHandler) + logger := slog.New(multiHandler) + + logger.Info("login", + slog.String("name", "whoami"), + slog.Int("id", 42), + ) + + os.Stdout.WriteString(textBuf.String()) + os.Stdout.WriteString(jsonBuf.String()) + + // Output: + // level=INFO msg=login name=whoami id=42 + // {"level":"INFO","msg":"login","name":"whoami","id":42} +} diff --git a/src/log/slog/multi_handler.go b/src/log/slog/multi_handler.go new file mode 100644 index 00000000000000..4cc802b29ba653 --- /dev/null +++ b/src/log/slog/multi_handler.go @@ -0,0 +1,61 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slog + +import ( + "context" + "errors" +) + +// NewMultiHandler creates a [MultiHandler] with the given Handlers. +func NewMultiHandler(handlers ...Handler) *MultiHandler { + h := make([]Handler, len(handlers)) + copy(h, handlers) + return &MultiHandler{multi: h} +} + +// MultiHandler is a [Handler] that invokes all the given Handlers. +// Its Enable method reports whether any of the handlers' Enabled methods return true. +// Its Handle, WithAttr and WithGroup methods call the corresponding method on each of the enabled handlers. +type MultiHandler struct { + multi []Handler +} + +func (h *MultiHandler) Enabled(ctx context.Context, l Level) bool { + for i := range h.multi { + if h.multi[i].Enabled(ctx, l) { + return true + } + } + return false +} + +func (h *MultiHandler) Handle(ctx context.Context, r Record) error { + var errs []error + for i := range h.multi { + if h.multi[i].Enabled(ctx, r.Level) { + if err := h.multi[i].Handle(ctx, r.Clone()); err != nil { + errs = append(errs, err) + } + } + } + return errors.Join(errs...) +} + +func (h *MultiHandler) WithAttrs(attrs []Attr) Handler { + handlers := make([]Handler, 0, len(h.multi)) + for i := range h.multi { + handlers = append(handlers, h.multi[i].WithAttrs(attrs)) + } + return &MultiHandler{multi: handlers} +} + +func (h *MultiHandler) WithGroup(name string) Handler { + handlers := make([]Handler, 0, len(h.multi)) + for i := range h.multi { + handlers = append(handlers, h.multi[i].WithGroup(name)) + } + return &MultiHandler{multi: handlers} +} diff --git a/src/log/slog/multi_handler_test.go b/src/log/slog/multi_handler_test.go new file mode 100644 index 00000000000000..86844a661b8bfd --- /dev/null +++ b/src/log/slog/multi_handler_test.go @@ -0,0 +1,139 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slog + +import ( + "bytes" + "context" + "errors" + "testing" + "time" +) + +// mockFailingHandler is a handler that always returns an error +// from its Handle method. +type mockFailingHandler struct { + Handler + err error +} + +func (h *mockFailingHandler) Handle(ctx context.Context, r Record) error { + _ = h.Handler.Handle(ctx, r) + return h.err +} + +func TestMultiHandler(t *testing.T) { + t.Run("Handle sends log to all handlers", func(t *testing.T) { + var buf1, buf2 bytes.Buffer + h1 := NewTextHandler(&buf1, nil) + h2 := NewJSONHandler(&buf2, nil) + + multi := NewMultiHandler(h1, h2) + logger := New(multi) + + logger.Info("hello world", "user", "test") + + checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="hello world" user=test`) + checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"hello world","user":"test"}`) + }) + + t.Run("Enabled returns true if any handler is enabled", func(t *testing.T) { + h1 := NewTextHandler(&bytes.Buffer{}, &HandlerOptions{Level: LevelError}) + h2 := NewTextHandler(&bytes.Buffer{}, &HandlerOptions{Level: LevelInfo}) + + multi := NewMultiHandler(h1, h2) + + if !multi.Enabled(context.Background(), LevelInfo) { + t.Error("Enabled should be true for INFO level, but got false") + } + if !multi.Enabled(context.Background(), LevelError) { + t.Error("Enabled should be true for ERROR level, but got false") + } + }) + + t.Run("Enabled returns false if no handlers are enabled", func(t *testing.T) { + h1 := NewTextHandler(&bytes.Buffer{}, &HandlerOptions{Level: LevelError}) + h2 := NewTextHandler(&bytes.Buffer{}, &HandlerOptions{Level: LevelInfo}) + + multi := NewMultiHandler(h1, h2) + + if multi.Enabled(context.Background(), LevelDebug) { + t.Error("Enabled should be false for DEBUG level, but got true") + } + }) + + t.Run("WithAttrs propagates attributes to all handlers", func(t *testing.T) { + var buf1, buf2 bytes.Buffer + h1 := NewTextHandler(&buf1, nil) + h2 := NewJSONHandler(&buf2, nil) + + multi := NewMultiHandler(h1, h2).WithAttrs([]Attr{String("request_id", "123")}) + logger := New(multi) + + logger.Info("request processed") + + checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="request processed" request_id=123`) + checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"request processed","request_id":"123"}`) + }) + + t.Run("WithGroup propagates group to all handlers", func(t *testing.T) { + var buf1, buf2 bytes.Buffer + h1 := NewTextHandler(&buf1, &HandlerOptions{AddSource: false}) + h2 := NewJSONHandler(&buf2, &HandlerOptions{AddSource: false}) + + multi := NewMultiHandler(h1, h2).WithGroup("req") + logger := New(multi) + + logger.Info("user login", "user_id", 42) + + checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="user login" req.user_id=42`) + checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"user login","req":{"user_id":42}}`) + }) + + t.Run("Handle propagates errors from handlers", func(t *testing.T) { + errFail := errors.New("mock failing") + + var buf1, buf2 bytes.Buffer + h1 := NewTextHandler(&buf1, nil) + h2 := &mockFailingHandler{Handler: NewJSONHandler(&buf2, nil), err: errFail} + + multi := NewMultiHandler(h2, h1) + + err := multi.Handle(context.Background(), NewRecord(time.Now(), LevelInfo, "test message", 0)) + if !errors.Is(err, errFail) { + t.Errorf("Expected error: %v, but got: %v", errFail, err) + } + + checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="test message"`) + checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"test message"}`) + }) + + t.Run("Handle with no handlers", func(t *testing.T) { + multi := NewMultiHandler() + logger := New(multi) + + logger.Info("nothing") + + err := multi.Handle(context.Background(), NewRecord(time.Now(), LevelInfo, "test", 0)) + if err != nil { + t.Errorf("Handle with no sub-handlers should return nil, but got: %v", err) + } + }) +} + +// Test that NewMultiHandler copies the input slice and is insulated from future modification. +func TestNewMultiHandlerCopy(t *testing.T) { + var buf1 bytes.Buffer + h1 := NewTextHandler(&buf1, nil) + slice := []Handler{h1} + multi := NewMultiHandler(slice...) + slice[0] = nil + + err := multi.Handle(context.Background(), NewRecord(time.Now(), LevelInfo, "test message", 0)) + if err != nil { + t.Errorf("Expected nil error, but got: %v", err) + } + checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="test message"`) +} From 00b8474e47a1f0381170734604a7ce8123d7146d Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Thu, 7 Aug 2025 18:53:00 +0000 Subject: [PATCH 25/40] cmd/trace: don't filter events for profile by whether they have stack Right now the profile-from-trace code blindly discards events that don't have a stack, but this means it can discard 'end' events for goroutine time ranges that don't have stacks, like when a goroutine exits a syscall. This means we drop stack samples we *do* have, because we correctly already only use the stack trace of the corresponding 'start' event for a time-range-of-interest anyway. This change means that some events will be tracked that have no stack in their start event, but that's fine. It won't end up in the profile anyway because the stack is empty! And the rest of the code appears to be robust to an empty stack already. Thank you to Rhys Hiltner for reporting this issue and for the reproducer, which I have worked into a test for this change. Fixes #74850. Change-Id: I943b97ecf6b82803e4a778a0f83a14473d32254e Reviewed-on: https://go-review.googlesource.com/c/go/+/694156 Reviewed-by: Rhys Hiltner Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Carlos Amedee --- src/cmd/trace/pprof.go | 4 - src/cmd/trace/pprof_test.go | 103 ++++++++++++++++++++++ src/go/build/deps_test.go | 34 ++++--- src/internal/trace/testtrace/platforms.go | 32 +++++++ 4 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 src/cmd/trace/pprof_test.go create mode 100644 src/internal/trace/testtrace/platforms.go diff --git a/src/cmd/trace/pprof.go b/src/cmd/trace/pprof.go index a66419aedf764e..b472ffa759114b 100644 --- a/src/cmd/trace/pprof.go +++ b/src/cmd/trace/pprof.go @@ -153,10 +153,6 @@ func makeComputePprofFunc(state trace.GoState, trackReason func(string) bool) co if ev.Kind() != trace.EventStateTransition { continue } - stack := ev.Stack() - if stack == trace.NoStack { - continue - } // The state transition has to apply to a goroutine. st := ev.StateTransition() diff --git a/src/cmd/trace/pprof_test.go b/src/cmd/trace/pprof_test.go new file mode 100644 index 00000000000000..6d18e7d5d16a0f --- /dev/null +++ b/src/cmd/trace/pprof_test.go @@ -0,0 +1,103 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "net/http" + "os" + "runtime/trace" + "strings" + "testing" + "testing/synctest" + "time" + + "internal/trace/testtrace" +) + +// Regression test for go.dev/issue/74850. +func TestSyscallProfile74850(t *testing.T) { + testtrace.MustHaveSyscallEvents(t) + + var buf bytes.Buffer + err := trace.Start(&buf) + if err != nil { + t.Fatalf("start tracing: %v", err) + } + + synctest.Test(t, func(t *testing.T) { + go hidden1(t) + go hidden2(t) + go visible(t) + synctest.Wait() + time.Sleep(1 * time.Millisecond) + synctest.Wait() + }) + trace.Stop() + + if t.Failed() { + return + } + + parsed, err := parseTrace(&buf, int64(buf.Len())) + if err != nil { + t.Fatalf("parsing trace: %v", err) + } + + records, err := pprofByGoroutine(computePprofSyscall(), parsed)(&http.Request{}) + if err != nil { + t.Fatalf("failed to generate pprof: %v\n", err) + } + + for _, r := range records { + t.Logf("Record: n=%d, total=%v", r.Count, r.Time) + for _, f := range r.Stack { + t.Logf("\t%s", f.Func) + t.Logf("\t\t%s:%d @ 0x%x", f.File, f.Line, f.PC) + } + } + if len(records) == 0 { + t.Error("empty profile") + } + + // Make sure we see the right frames. + wantSymbols := []string{"cmd/trace.visible", "cmd/trace.hidden1", "cmd/trace.hidden2"} + haveSymbols := make([]bool, len(wantSymbols)) + for _, r := range records { + for _, f := range r.Stack { + for i, s := range wantSymbols { + if strings.Contains(f.Func, s) { + haveSymbols[i] = true + } + } + } + } + for i, have := range haveSymbols { + if !have { + t.Errorf("expected %s in syscall profile", wantSymbols[i]) + } + } +} + +func stat(t *testing.T) { + _, err := os.Stat(".") + if err != nil { + t.Errorf("os.Stat: %v", err) + } +} + +func hidden1(t *testing.T) { + stat(t) +} + +func hidden2(t *testing.T) { + stat(t) + stat(t) +} + +func visible(t *testing.T) { + stat(t) + time.Sleep(1 * time.Millisecond) +} diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 41dde20bf9cc9b..d50a98d34c9092 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -742,12 +742,6 @@ var depsRules = ` FMT, encoding/binary, internal/trace/version, internal/trace/internal/tracev1, container/heap, math/rand < internal/trace; - regexp, internal/trace, internal/trace/raw, internal/txtar - < internal/trace/testtrace; - - regexp, internal/txtar, internal/trace, internal/trace/raw - < internal/trace/internal/testgen; - # cmd/trace dependencies. FMT, embed, @@ -792,14 +786,24 @@ var depsRules = ` < testing/internal/testdeps; # Test-only packages can have anything they want - FMT, compress/gzip, embed, encoding/binary < encoding/json/internal/jsontest; - CGO, internal/syscall/unix < net/internal/cgotest; - FMT < math/big/internal/asmgen; - FMT, testing < internal/cgrouptest; - C, CGO < internal/runtime/cgobench; + FMT, compress/gzip, embed, encoding/binary + < encoding/json/internal/jsontest; + + CGO, internal/syscall/unix + < net/internal/cgotest; + + FMT, testing + < internal/cgrouptest; + + regexp, internal/trace, internal/trace/raw, internal/txtar, testing + < internal/trace/testtrace; + + C, CGO + < internal/runtime/cgobench; + + # Generate-only packages can have anything they want. - # Generate-only packages can have anything they want container/heap, encoding/binary, fmt, @@ -812,6 +816,12 @@ var depsRules = ` strings, sync < internal/runtime/gc/internal/gen; + + regexp, internal/txtar, internal/trace, internal/trace/raw + < internal/trace/internal/testgen; + + FMT + < math/big/internal/asmgen; ` // listStdPkgs returns the same list of packages as "go list std". diff --git a/src/internal/trace/testtrace/platforms.go b/src/internal/trace/testtrace/platforms.go new file mode 100644 index 00000000000000..937a2dcc841903 --- /dev/null +++ b/src/internal/trace/testtrace/platforms.go @@ -0,0 +1,32 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testtrace + +import ( + "runtime" + "testing" +) + +// MustHaveSyscallEvents skips the current test if the current +// platform does not support true system call events. +func MustHaveSyscallEvents(t *testing.T) { + if HasSyscallEvents() { + return + } + t.Skip("current platform has no true syscall events") +} + +// HasSyscallEvents returns true if the current platform +// has real syscall events available. +func HasSyscallEvents() bool { + switch runtime.GOOS { + case "js", "wasip1": + // js and wasip1 emulate system calls by blocking on channels + // while yielding back to the environment. They never actually + // call entersyscall/exitsyscall. + return false + } + return true +} From ddce0522bee36764c3b9529b8584c3d5b53c5dac Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Tue, 26 Aug 2025 15:40:57 +0800 Subject: [PATCH 26/40] cmd/internal/obj/loong64: add ADDU16I.D instruction support Go asm syntax: ADDV16 $(1<<16), R4, R5 Equivalent platform assembler syntax: addu16i.d r5, r4, $1 Change-Id: Ica4a4e779d0a107cda3eade86027abd6458779a4 Reviewed-on: https://go-review.googlesource.com/c/go/+/699056 Reviewed-by: abner chenc Reviewed-by: Michael Pratt Auto-Submit: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/cmd/asm/internal/asm/testdata/loong64enc1.s | 7 +++++++ src/cmd/asm/internal/asm/testdata/loong64error.s | 2 ++ src/cmd/internal/obj/loong64/a.out.go | 3 +++ src/cmd/internal/obj/loong64/anames.go | 1 + src/cmd/internal/obj/loong64/asm.go | 15 ++++++++++++++- src/cmd/internal/obj/loong64/doc.go | 12 ++++++++++++ 6 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index c5c6a4479a4060..fd86db7a4fc184 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -282,6 +282,13 @@ lable2: MOVVP 4(R5), R4 // a4040026 MOVVP (R5), R4 // a4000026 + // ADDU16I.D instruction + ADDV16 $(-32768<<16), R4, R5 // ADDV16 $-2147483648, R4, R5 // 85000012 + ADDV16 $(0<<16), R4, R5 // ADDV16 $0, R4, R5 // 85000010 + ADDV16 $(8<<16), R4, R5 // ADDV16 $524288, R4, R5 // 85200010 + ADDV16 $(32767<<16), R4, R5 // ADDV16 $2147418112, R4, R5 // 85fcff11 + ADDV16 $(16<<16), R4 // ADDV16 $1048576, R4 // 84400010 + // Loong64 atomic memory access instructions AMSWAPB R14, (R13), R12 // ac395c38 AMSWAPH R14, (R13), R12 // acb95c38 diff --git a/src/cmd/asm/internal/asm/testdata/loong64error.s b/src/cmd/asm/internal/asm/testdata/loong64error.s index 9272ce51c36e62..2dcd34bf61c676 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64error.s +++ b/src/cmd/asm/internal/asm/testdata/loong64error.s @@ -5,3 +5,5 @@ TEXT errors(SB),$0 VSHUF4IV $16, V1, V2 // ERROR "operand out of range 0 to 15" XVSHUF4IV $16, X1, X2 // ERROR "operand out of range 0 to 15" + ADDV16 $1, R4, R5 // ERROR "the constant must be a multiple of 65536." + ADDV16 $65535, R4, R5 // ERROR "the constant must be a multiple of 65536." diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index 7ab85a2f2378aa..100e99b1c4faa4 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -565,6 +565,9 @@ const ( AMOVVF AMOVVD + // 2.2.1.2 + AADDV16 + // 2.2.1.3 AALSLW AALSLWU diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go index c629553d5598af..422ccbd9b0bc0a 100644 --- a/src/cmd/internal/obj/loong64/anames.go +++ b/src/cmd/internal/obj/loong64/anames.go @@ -125,6 +125,7 @@ var Anames = []string{ "MOVDV", "MOVVF", "MOVVD", + "ADDV16", "ALSLW", "ALSLWU", "ALSLV", diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 848d77fd0d92aa..e20ceaae959206 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -267,6 +267,9 @@ var optab = []Optab{ {AADDV, C_U12CON, C_REG, C_NONE, C_REG, C_NONE, 10, 8, 0, 0}, {AADDV, C_U12CON, C_NONE, C_NONE, C_REG, C_NONE, 10, 8, 0, 0}, + {AADDV16, C_32CON, C_REG, C_NONE, C_REG, C_NONE, 4, 4, 0, 0}, + {AADDV16, C_32CON, C_NONE, C_NONE, C_REG, C_NONE, 4, 4, 0, 0}, + {AAND, C_UU12CON, C_REG, C_NONE, C_REG, C_NONE, 4, 4, 0, 0}, {AAND, C_UU12CON, C_NONE, C_NONE, C_REG, C_NONE, 4, 4, 0, 0}, {AAND, C_S12CON, C_REG, C_NONE, C_REG, C_NONE, 10, 8, 0, 0}, @@ -1520,6 +1523,7 @@ func buildop(ctxt *obj.Link) { APRELD, APRELDX, AFSEL, + AADDV16, obj.ANOP, obj.ATEXT, obj.AFUNCDATA, @@ -2087,7 +2091,14 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { if r == 0 { r = int(p.To.Reg) } - o1 = OP_12IRR(c.opirr(p.As), uint32(v), uint32(r), uint32(p.To.Reg)) + if p.As == AADDV16 { + if v&65535 != 0 { + c.ctxt.Diag("%v: the constant must be a multiple of 65536.\n", p) + } + o1 = OP_16IRR(c.opirr(p.As), uint32(v>>16), uint32(r), uint32(p.To.Reg)) + } else { + o1 = OP_12IRR(c.opirr(p.As), uint32(v), uint32(r), uint32(p.To.Reg)) + } case 5: // syscall v := c.regoff(&p.From) @@ -4033,6 +4044,8 @@ func (c *ctxt0) opirr(a obj.As) uint32 { return 0x00b << 22 case AADDVU: return 0x00b << 22 + case AADDV16: + return 0x4 << 26 case AJMP: return 0x14 << 26 diff --git a/src/cmd/internal/obj/loong64/doc.go b/src/cmd/internal/obj/loong64/doc.go index 20c5a9e0a6faa8..f7e5a4fb4279ea 100644 --- a/src/cmd/internal/obj/loong64/doc.go +++ b/src/cmd/internal/obj/loong64/doc.go @@ -326,6 +326,18 @@ Note: In the following sections 3.1 to 3.6, "ui4" (4-bit unsigned int immediate) Go assembly | platform assembly MOVWP 8(R4), R5 | ldptr.w r5, r4, $2 +6. Note of special add instrction + Mapping between Go and platform assembly: + Go assembly | platform assembly + ADDV16 si16<<16, Rj, Rd | addu16i.d rd, rj, si16 + + note: si16 is a 16-bit immediate number, and si16<<16 is the actual operand. + + The addu16i.d instruction logically left-shifts the 16-bit immediate number si16 by 16 bits, then + sign-extends it. The resulting data is added to the [63:0] bits of data in the general-purpose register + rj, and the sum is written into the general-purpose register rd. + The addu16i.d instruction is used in conjunction with the ldptr.w/d and stptr.w/d instructions to + accelerate access based on the GOT table in position-independent code. */ package loong64 From 9d0829963ccab19093c37f21cfc35d019addc78a Mon Sep 17 00:00:00 2001 From: "Nicholas S. Husin" Date: Wed, 3 Sep 2025 14:25:59 -0400 Subject: [PATCH 27/40] net/http: fix cookie value of "" being interpreted as empty string. In issue #46443, we have established that double-quotes in cookie values should be kept as part of the value, rather than being discarded. However, we have missed the edge case of "" until now. This CL fixes said edge case. Fixes #75244 Change-Id: I627ad2376931514aa5dcc8961ad804e42b7d9434 Reviewed-on: https://go-review.googlesource.com/c/go/+/700755 Reviewed-by: Nicholas Husin LUCI-TryBot-Result: Go LUCI Auto-Submit: Nicholas Husin Reviewed-by: Damien Neil --- src/net/http/cookie.go | 3 --- src/net/http/cookie_test.go | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/net/http/cookie.go b/src/net/http/cookie.go index 408fe88452b37a..efe6cc3e77e5b5 100644 --- a/src/net/http/cookie.go +++ b/src/net/http/cookie.go @@ -459,9 +459,6 @@ func sanitizeCookieName(n string) string { // See https://golang.org/issue/7243 for the discussion. func sanitizeCookieValue(v string, quoted bool) string { v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) - if len(v) == 0 { - return v - } if strings.ContainsAny(v, " ,") || quoted { return `"` + v + `"` } diff --git a/src/net/http/cookie_test.go b/src/net/http/cookie_test.go index aac69563624fcd..8db4957b2cc37d 100644 --- a/src/net/http/cookie_test.go +++ b/src/net/http/cookie_test.go @@ -530,6 +530,7 @@ func TestCookieSanitizeValue(t *testing.T) { {"a,z", false, `"a,z"`}, {",z", false, `",z"`}, {"a,", false, `"a,"`}, + {"", true, `""`}, } for _, tt := range tests { if got := sanitizeCookieValue(tt.in, tt.quoted); got != tt.want { From d52a56cce1cc78eeff753d952d7abfc109f78ead Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 28 Aug 2025 14:37:04 +0200 Subject: [PATCH 28/40] cmd/link/internal/ld: unconditionally use posix_fallocate on FreeBSD Now that Go 1.24 is the minimum bootstrap toolchain we can drop the version dependent use of posix_fallocate on FreeBSD. For #69315 Change-Id: Ie0c7ca67e3c21138d690e1e11a12172d52619493 Reviewed-on: https://go-review.googlesource.com/c/go/+/699735 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Auto-Submit: Tobias Klauser Reviewed-by: Dmitri Shuralyov --- src/cmd/link/internal/ld/fallocate_test.go | 2 +- src/cmd/link/internal/ld/outbuf_bsd.go | 2 +- src/cmd/link/internal/ld/outbuf_nofallocate.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmd/link/internal/ld/fallocate_test.go b/src/cmd/link/internal/ld/fallocate_test.go index 163ffc26e8406a..3c6b7ef752edd3 100644 --- a/src/cmd/link/internal/ld/fallocate_test.go +++ b/src/cmd/link/internal/ld/fallocate_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || (freebsd && go1.21) || linux || (netbsd && go1.25) +//go:build darwin || freebsd || linux || (netbsd && go1.25) package ld diff --git a/src/cmd/link/internal/ld/outbuf_bsd.go b/src/cmd/link/internal/ld/outbuf_bsd.go index 5dce83fefd2d7c..a1d61aa045a8cb 100644 --- a/src/cmd/link/internal/ld/outbuf_bsd.go +++ b/src/cmd/link/internal/ld/outbuf_bsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (freebsd && go1.21) || (netbsd && go1.25) +//go:build freebsd || (netbsd && go1.25) package ld diff --git a/src/cmd/link/internal/ld/outbuf_nofallocate.go b/src/cmd/link/internal/ld/outbuf_nofallocate.go index 9169379e23897b..0207b3988a4f5b 100644 --- a/src/cmd/link/internal/ld/outbuf_nofallocate.go +++ b/src/cmd/link/internal/ld/outbuf_nofallocate.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !darwin && !(freebsd && go1.21) && !linux && !(netbsd && go1.25) +//go:build !darwin && !freebsd && !linux && !(netbsd && go1.25) package ld From bb48272e24ca38676442afc20afb6e0b9a91048b Mon Sep 17 00:00:00 2001 From: Julian Zhu Date: Thu, 4 Sep 2025 21:38:47 +0800 Subject: [PATCH 29/40] cmd/compile: simplify zerorange on mips64 Change-Id: I488b55a21eaaf74373c2789a34bf9b3945ced072 Reviewed-on: https://go-review.googlesource.com/c/go/+/700936 Reviewed-by: Michael Pratt Reviewed-by: Keith Randall Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI Auto-Submit: Keith Randall --- src/cmd/compile/internal/mips64/ggen.go | 38 +++++-------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/src/cmd/compile/internal/mips64/ggen.go b/src/cmd/compile/internal/mips64/ggen.go index 5f3f3e64d9cc1a..0740d9abe88f3e 100644 --- a/src/cmd/compile/internal/mips64/ggen.go +++ b/src/cmd/compile/internal/mips64/ggen.go @@ -5,7 +5,6 @@ package mips64 import ( - "cmd/compile/internal/ir" "cmd/compile/internal/objw" "cmd/compile/internal/types" "cmd/internal/obj" @@ -13,37 +12,14 @@ import ( ) func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog { - if cnt == 0 { - return p + if cnt%int64(types.PtrSize) != 0 { + panic("zeroed region not aligned") } - if cnt < int64(4*types.PtrSize) { - for i := int64(0); i < cnt; i += int64(types.PtrSize) { - p = pp.Append(p, mips.AMOVV, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, 8+off+i) - } - } else if cnt <= int64(128*types.PtrSize) { - p = pp.Append(p, mips.AADDV, obj.TYPE_CONST, 0, 8+off-8, obj.TYPE_REG, mips.REGRT1, 0) - p.Reg = mips.REGSP - p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) - p.To.Name = obj.NAME_EXTERN - p.To.Sym = ir.Syms.Duffzero - p.To.Offset = 8 * (128 - cnt/int64(types.PtrSize)) - } else { - // ADDV $(8+frame+lo-8), SP, r1 - // ADDV $cnt, r1, r2 - // loop: - // MOVV R0, (Widthptr)r1 - // ADDV $Widthptr, r1 - // BNE r1, r2, loop - p = pp.Append(p, mips.AADDV, obj.TYPE_CONST, 0, 8+off-8, obj.TYPE_REG, mips.REGRT1, 0) - p.Reg = mips.REGSP - p = pp.Append(p, mips.AADDV, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, mips.REGRT2, 0) - p.Reg = mips.REGRT1 - p = pp.Append(p, mips.AMOVV, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGRT1, int64(types.PtrSize)) - p1 := p - p = pp.Append(p, mips.AADDV, obj.TYPE_CONST, 0, int64(types.PtrSize), obj.TYPE_REG, mips.REGRT1, 0) - p = pp.Append(p, mips.ABNE, obj.TYPE_REG, mips.REGRT1, 0, obj.TYPE_BRANCH, 0, 0) - p.Reg = mips.REGRT2 - p.To.SetTarget(p1) + + for cnt != 0 { + p = pp.Append(p, mips.AMOVV, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, off) + cnt -= int64(types.PtrSize) + off += int64(types.PtrSize) } return p From 87e72769fa74a2ef0bd81a3fd4febe75b8fc6731 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 4 Sep 2025 17:13:37 +0200 Subject: [PATCH 30/40] runtime: simplify openbsd check in usesLibcall and mStackIsSystemAllocated The openbsd/mips64 runtime code was removed in CL 649659. For #61546 Change-Id: I03f16c3396baddb0ee9aa751dd6f699a835e7586 Reviewed-on: https://go-review.googlesource.com/c/go/+/700976 Auto-Submit: Tobias Klauser Reviewed-by: Michael Pratt Reviewed-by: Joel Sing Reviewed-by: Keith Randall Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI --- src/runtime/proc.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 2584eb4cac6a33..51e2c42605acbb 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -1816,10 +1816,8 @@ func startTheWorldWithSema(now int64, w worldStop) int64 { // via libcall. func usesLibcall() bool { switch GOOS { - case "aix", "darwin", "illumos", "ios", "solaris", "windows": + case "aix", "darwin", "illumos", "ios", "openbsd", "solaris", "windows": return true - case "openbsd": - return GOARCH != "mips64" } return false } @@ -1828,10 +1826,8 @@ func usesLibcall() bool { // system-allocated stack. func mStackIsSystemAllocated() bool { switch GOOS { - case "aix", "darwin", "plan9", "illumos", "ios", "solaris", "windows": + case "aix", "darwin", "plan9", "illumos", "ios", "openbsd", "solaris", "windows": return true - case "openbsd": - return GOARCH != "mips64" } return false } From 459b85ccaa30bbce4c22b2779672dfe402a2b2da Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Wed, 13 Aug 2025 22:33:14 +0800 Subject: [PATCH 31/40] cmd/fix: remove all functionality except for buildtag For #73605 Change-Id: I4b46b5eb72471c215f2cc208c1b0cdd1fbdbf81a Reviewed-on: https://go-review.googlesource.com/c/go/+/695855 Reviewed-by: Michael Matloob Reviewed-by: Michael Pratt Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI Auto-Submit: Alan Donovan --- src/cmd/fix/cftype.go | 130 +--------------- src/cmd/fix/cftype_test.go | 241 ------------------------------ src/cmd/fix/context.go | 12 +- src/cmd/fix/context_test.go | 42 ------ src/cmd/fix/egltype.go | 42 +----- src/cmd/fix/egltype_test.go | 214 -------------------------- src/cmd/fix/gotypes.go | 63 +------- src/cmd/fix/gotypes_test.go | 89 ----------- src/cmd/fix/jnitype.go | 56 +------ src/cmd/fix/jnitype_test.go | 203 ------------------------- src/cmd/fix/netipv6zone.go | 56 +------ src/cmd/fix/netipv6zone_test.go | 43 ------ src/cmd/fix/printerconfig.go | 49 +----- src/cmd/fix/printerconfig_test.go | 37 ----- 14 files changed, 18 insertions(+), 1259 deletions(-) delete mode 100644 src/cmd/fix/cftype_test.go delete mode 100644 src/cmd/fix/context_test.go delete mode 100644 src/cmd/fix/egltype_test.go delete mode 100644 src/cmd/fix/gotypes_test.go delete mode 100644 src/cmd/fix/jnitype_test.go delete mode 100644 src/cmd/fix/netipv6zone_test.go delete mode 100644 src/cmd/fix/printerconfig_test.go diff --git a/src/cmd/fix/cftype.go b/src/cmd/fix/cftype.go index 04ece9fe5b4079..3e9f4c5a35c128 100644 --- a/src/cmd/fix/cftype.go +++ b/src/cmd/fix/cftype.go @@ -6,9 +6,6 @@ package main import ( "go/ast" - "go/token" - "reflect" - "strings" ) func init() { @@ -18,130 +15,11 @@ func init() { var cftypeFix = fix{ name: "cftype", date: "2017-09-27", - f: cftypefix, - desc: `Fixes initializers and casts of C.*Ref and JNI types`, + f: noop, + desc: `Fixes initializers and casts of C.*Ref and JNI types (removed)`, disabled: false, } -// Old state: -// -// type CFTypeRef unsafe.Pointer -// -// New state: -// -// type CFTypeRef uintptr -// -// and similar for other *Ref types. -// This fix finds nils initializing these types and replaces the nils with 0s. -func cftypefix(f *ast.File) bool { - return typefix(f, func(s string) bool { - return strings.HasPrefix(s, "C.") && strings.HasSuffix(s, "Ref") && s != "C.CFAllocatorRef" - }) -} - -// typefix replaces nil with 0 for all nils whose type, when passed to badType, returns true. -func typefix(f *ast.File, badType func(string) bool) bool { - if !imports(f, "C") { - return false - } - typeof, _ := typecheck(&TypeConfig{}, f) - changed := false - - // step 1: Find all the nils with the offending types. - // Compute their replacement. - badNils := map[any]ast.Expr{} - walk(f, func(n any) { - if i, ok := n.(*ast.Ident); ok && i.Name == "nil" && badType(typeof[n]) { - badNils[n] = &ast.BasicLit{ValuePos: i.NamePos, Kind: token.INT, Value: "0"} - } - }) - - // step 2: find all uses of the bad nils, replace them with 0. - // There's no easy way to map from an ast.Expr to all the places that use them, so - // we use reflect to find all such references. - if len(badNils) > 0 { - exprType := reflect.TypeFor[ast.Expr]() - exprSliceType := reflect.TypeFor[[]ast.Expr]() - walk(f, func(n any) { - if n == nil { - return - } - v := reflect.ValueOf(n) - if v.Kind() != reflect.Pointer { - return - } - if v.IsNil() { - return - } - v = v.Elem() - if v.Kind() != reflect.Struct { - return - } - for i := 0; i < v.NumField(); i++ { - f := v.Field(i) - if f.Type() == exprType { - if r := badNils[f.Interface()]; r != nil { - f.Set(reflect.ValueOf(r)) - changed = true - } - } - if f.Type() == exprSliceType { - for j := 0; j < f.Len(); j++ { - e := f.Index(j) - if r := badNils[e.Interface()]; r != nil { - e.Set(reflect.ValueOf(r)) - changed = true - } - } - } - } - }) - } - - // step 3: fix up invalid casts. - // It used to be ok to cast between *unsafe.Pointer and *C.CFTypeRef in a single step. - // Now we need unsafe.Pointer as an intermediate cast. - // (*unsafe.Pointer)(x) where x is type *bad -> (*unsafe.Pointer)(unsafe.Pointer(x)) - // (*bad.type)(x) where x is type *unsafe.Pointer -> (*bad.type)(unsafe.Pointer(x)) - walk(f, func(n any) { - if n == nil { - return - } - // Find pattern like (*a.b)(x) - c, ok := n.(*ast.CallExpr) - if !ok { - return - } - if len(c.Args) != 1 { - return - } - p, ok := c.Fun.(*ast.ParenExpr) - if !ok { - return - } - s, ok := p.X.(*ast.StarExpr) - if !ok { - return - } - t, ok := s.X.(*ast.SelectorExpr) - if !ok { - return - } - pkg, ok := t.X.(*ast.Ident) - if !ok { - return - } - dst := pkg.Name + "." + t.Sel.Name - src := typeof[c.Args[0]] - if badType(dst) && src == "*unsafe.Pointer" || - dst == "unsafe.Pointer" && strings.HasPrefix(src, "*") && badType(src[1:]) { - c.Args[0] = &ast.CallExpr{ - Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "unsafe"}, Sel: &ast.Ident{Name: "Pointer"}}, - Args: []ast.Expr{c.Args[0]}, - } - changed = true - } - }) - - return changed +func noop(f *ast.File) bool { + return false } diff --git a/src/cmd/fix/cftype_test.go b/src/cmd/fix/cftype_test.go deleted file mode 100644 index cde47f28a3bbf1..00000000000000 --- a/src/cmd/fix/cftype_test.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2017 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 main - -func init() { - addTestCases(cftypeTests, cftypefix) -} - -var cftypeTests = []testCase{ - { - Name: "cftype.localVariable", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -func f() { - var x C.CFTypeRef = nil - x = nil - x, x = nil, nil -} -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -func f() { - var x C.CFTypeRef = 0 - x = 0 - x, x = 0, 0 -} -`, - }, - { - Name: "cftype.globalVariable", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x C.CFTypeRef = nil - -func f() { - x = nil -} -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x C.CFTypeRef = 0 - -func f() { - x = 0 -} -`, - }, - { - Name: "cftype.EqualArgument", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x C.CFTypeRef -var y = x == nil -var z = x != nil -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x C.CFTypeRef -var y = x == 0 -var z = x != 0 -`, - }, - { - Name: "cftype.StructField", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -type T struct { - x C.CFTypeRef -} - -var t = T{x: nil} -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -type T struct { - x C.CFTypeRef -} - -var t = T{x: 0} -`, - }, - { - Name: "cftype.FunctionArgument", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -func f(x C.CFTypeRef) { -} - -func g() { - f(nil) -} -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -func f(x C.CFTypeRef) { -} - -func g() { - f(0) -} -`, - }, - { - Name: "cftype.ArrayElement", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x = [3]C.CFTypeRef{nil, nil, nil} -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x = [3]C.CFTypeRef{0, 0, 0} -`, - }, - { - Name: "cftype.SliceElement", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x = []C.CFTypeRef{nil, nil, nil} -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x = []C.CFTypeRef{0, 0, 0} -`, - }, - { - Name: "cftype.MapKey", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x = map[C.CFTypeRef]int{nil: 0} -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x = map[C.CFTypeRef]int{0: 0} -`, - }, - { - Name: "cftype.MapValue", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x = map[int]C.CFTypeRef{0: nil} -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x = map[int]C.CFTypeRef{0: 0} -`, - }, - { - Name: "cftype.Conversion1", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x C.CFTypeRef -var y = (*unsafe.Pointer)(&x) -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x C.CFTypeRef -var y = (*unsafe.Pointer)(unsafe.Pointer(&x)) -`, - }, - { - Name: "cftype.Conversion2", - In: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x unsafe.Pointer -var y = (*C.CFTypeRef)(&x) -`, - Out: `package main - -// typedef const void *CFTypeRef; -import "C" - -var x unsafe.Pointer -var y = (*C.CFTypeRef)(unsafe.Pointer(&x)) -`, - }, -} diff --git a/src/cmd/fix/context.go b/src/cmd/fix/context.go index 1107f4d66c0ceb..fe2e0950520bd2 100644 --- a/src/cmd/fix/context.go +++ b/src/cmd/fix/context.go @@ -4,10 +4,6 @@ package main -import ( - "go/ast" -) - func init() { register(contextFix) } @@ -15,11 +11,7 @@ func init() { var contextFix = fix{ name: "context", date: "2016-09-09", - f: ctxfix, - desc: `Change imports of golang.org/x/net/context to context`, + f: noop, + desc: `Change imports of golang.org/x/net/context to context (removed)`, disabled: false, } - -func ctxfix(f *ast.File) bool { - return rewriteImport(f, "golang.org/x/net/context", "context") -} diff --git a/src/cmd/fix/context_test.go b/src/cmd/fix/context_test.go deleted file mode 100644 index 935d0d72358916..00000000000000 --- a/src/cmd/fix/context_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2016 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 main - -func init() { - addTestCases(contextTests, ctxfix) -} - -var contextTests = []testCase{ - { - Name: "context.0", - In: `package main - -import "golang.org/x/net/context" - -var _ = "golang.org/x/net/context" -`, - Out: `package main - -import "context" - -var _ = "golang.org/x/net/context" -`, - }, - { - Name: "context.1", - In: `package main - -import ctx "golang.org/x/net/context" - -var _ = ctx.Background() -`, - Out: `package main - -import ctx "context" - -var _ = ctx.Background() -`, - }, -} diff --git a/src/cmd/fix/egltype.go b/src/cmd/fix/egltype.go index a096db6665a5fb..8ba66efb062d61 100644 --- a/src/cmd/fix/egltype.go +++ b/src/cmd/fix/egltype.go @@ -4,10 +4,6 @@ package main -import ( - "go/ast" -) - func init() { register(eglFixDisplay) register(eglFixConfig) @@ -16,45 +12,15 @@ func init() { var eglFixDisplay = fix{ name: "egl", date: "2018-12-15", - f: eglfixDisp, - desc: `Fixes initializers of EGLDisplay`, + f: noop, + desc: `Fixes initializers of EGLDisplay (removed)`, disabled: false, } -// Old state: -// -// type EGLDisplay unsafe.Pointer -// -// New state: -// -// type EGLDisplay uintptr -// -// This fix finds nils initializing these types and replaces the nils with 0s. -func eglfixDisp(f *ast.File) bool { - return typefix(f, func(s string) bool { - return s == "C.EGLDisplay" - }) -} - var eglFixConfig = fix{ name: "eglconf", date: "2020-05-30", - f: eglfixConfig, - desc: `Fixes initializers of EGLConfig`, + f: noop, + desc: `Fixes initializers of EGLConfig (removed)`, disabled: false, } - -// Old state: -// -// type EGLConfig unsafe.Pointer -// -// New state: -// -// type EGLConfig uintptr -// -// This fix finds nils initializing these types and replaces the nils with 0s. -func eglfixConfig(f *ast.File) bool { - return typefix(f, func(s string) bool { - return s == "C.EGLConfig" - }) -} diff --git a/src/cmd/fix/egltype_test.go b/src/cmd/fix/egltype_test.go deleted file mode 100644 index c44525c0539e8b..00000000000000 --- a/src/cmd/fix/egltype_test.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2017 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 main - -import "strings" - -func init() { - addTestCases(eglTestsFor("EGLDisplay"), eglfixDisp) - addTestCases(eglTestsFor("EGLConfig"), eglfixConfig) -} - -func eglTestsFor(tname string) []testCase { - var eglTests = []testCase{ - { - Name: "egl.localVariable", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -func f() { - var x C.$EGLTYPE = nil - x = nil - x, x = nil, nil -} -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -func f() { - var x C.$EGLTYPE = 0 - x = 0 - x, x = 0, 0 -} -`, - }, - { - Name: "egl.globalVariable", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x C.$EGLTYPE = nil - -func f() { - x = nil -} -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x C.$EGLTYPE = 0 - -func f() { - x = 0 -} -`, - }, - { - Name: "egl.EqualArgument", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x C.$EGLTYPE -var y = x == nil -var z = x != nil -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x C.$EGLTYPE -var y = x == 0 -var z = x != 0 -`, - }, - { - Name: "egl.StructField", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -type T struct { - x C.$EGLTYPE -} - -var t = T{x: nil} -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -type T struct { - x C.$EGLTYPE -} - -var t = T{x: 0} -`, - }, - { - Name: "egl.FunctionArgument", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -func f(x C.$EGLTYPE) { -} - -func g() { - f(nil) -} -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -func f(x C.$EGLTYPE) { -} - -func g() { - f(0) -} -`, - }, - { - Name: "egl.ArrayElement", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x = [3]C.$EGLTYPE{nil, nil, nil} -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x = [3]C.$EGLTYPE{0, 0, 0} -`, - }, - { - Name: "egl.SliceElement", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x = []C.$EGLTYPE{nil, nil, nil} -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x = []C.$EGLTYPE{0, 0, 0} -`, - }, - { - Name: "egl.MapKey", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x = map[C.$EGLTYPE]int{nil: 0} -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x = map[C.$EGLTYPE]int{0: 0} -`, - }, - { - Name: "egl.MapValue", - In: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x = map[int]C.$EGLTYPE{0: nil} -`, - Out: `package main - -// typedef void *$EGLTYPE; -import "C" - -var x = map[int]C.$EGLTYPE{0: 0} -`, - }, - } - for i := range eglTests { - t := &eglTests[i] - t.In = strings.ReplaceAll(t.In, "$EGLTYPE", tname) - t.Out = strings.ReplaceAll(t.Out, "$EGLTYPE", tname) - } - return eglTests -} diff --git a/src/cmd/fix/gotypes.go b/src/cmd/fix/gotypes.go index 6085816ada45c0..987dab5d028c7b 100644 --- a/src/cmd/fix/gotypes.go +++ b/src/cmd/fix/gotypes.go @@ -4,11 +4,6 @@ package main -import ( - "go/ast" - "strconv" -) - func init() { register(gotypesFix) } @@ -16,60 +11,6 @@ func init() { var gotypesFix = fix{ name: "gotypes", date: "2015-07-16", - f: gotypes, - desc: `Change imports of golang.org/x/tools/go/{exact,types} to go/{constant,types}`, -} - -func gotypes(f *ast.File) bool { - fixed := fixGoTypes(f) - if fixGoExact(f) { - fixed = true - } - return fixed -} - -func fixGoTypes(f *ast.File) bool { - return rewriteImport(f, "golang.org/x/tools/go/types", "go/types") -} - -func fixGoExact(f *ast.File) bool { - // This one is harder because the import name changes. - // First find the import spec. - var importSpec *ast.ImportSpec - walk(f, func(n any) { - if importSpec != nil { - return - } - spec, ok := n.(*ast.ImportSpec) - if !ok { - return - } - path, err := strconv.Unquote(spec.Path.Value) - if err != nil { - return - } - if path == "golang.org/x/tools/go/exact" { - importSpec = spec - } - - }) - if importSpec == nil { - return false - } - - // We are about to rename exact.* to constant.*, but constant is a common - // name. See if it will conflict. This is a hack but it is effective. - exists := renameTop(f, "constant", "constant") - suffix := "" - if exists { - suffix = "_" - } - // Now we need to rename all the uses of the import. RewriteImport - // affects renameTop, but not vice versa, so do them in this order. - renameTop(f, "exact", "constant"+suffix) - rewriteImport(f, "golang.org/x/tools/go/exact", "go/constant") - // renameTop will also rewrite the imported package name. Fix that; - // we know it should be missing. - importSpec.Name = nil - return true + f: noop, + desc: `Change imports of golang.org/x/tools/go/{exact,types} to go/{constant,types} (removed)`, } diff --git a/src/cmd/fix/gotypes_test.go b/src/cmd/fix/gotypes_test.go deleted file mode 100644 index 9248fffd246bb0..00000000000000 --- a/src/cmd/fix/gotypes_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2012 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 main - -func init() { - addTestCases(gotypesTests, gotypes) -} - -var gotypesTests = []testCase{ - { - Name: "gotypes.0", - In: `package main - -import "golang.org/x/tools/go/types" -import "golang.org/x/tools/go/exact" - -var _ = exact.Kind - -func f() { - _ = exact.MakeBool(true) -} -`, - Out: `package main - -import "go/types" -import "go/constant" - -var _ = constant.Kind - -func f() { - _ = constant.MakeBool(true) -} -`, - }, - { - Name: "gotypes.1", - In: `package main - -import "golang.org/x/tools/go/types" -import foo "golang.org/x/tools/go/exact" - -var _ = foo.Kind - -func f() { - _ = foo.MakeBool(true) -} -`, - Out: `package main - -import "go/types" -import "go/constant" - -var _ = foo.Kind - -func f() { - _ = foo.MakeBool(true) -} -`, - }, - { - Name: "gotypes.0", - In: `package main - -import "golang.org/x/tools/go/types" -import "golang.org/x/tools/go/exact" - -var _ = exact.Kind -var constant = 23 // Use of new package name. - -func f() { - _ = exact.MakeBool(true) -} -`, - Out: `package main - -import "go/types" -import "go/constant" - -var _ = constant_.Kind -var constant = 23 // Use of new package name. - -func f() { - _ = constant_.MakeBool(true) -} -`, - }, -} diff --git a/src/cmd/fix/jnitype.go b/src/cmd/fix/jnitype.go index 111be8e70c6be3..bee38e672009cc 100644 --- a/src/cmd/fix/jnitype.go +++ b/src/cmd/fix/jnitype.go @@ -4,10 +4,6 @@ package main -import ( - "go/ast" -) - func init() { register(jniFix) } @@ -15,55 +11,7 @@ func init() { var jniFix = fix{ name: "jni", date: "2017-12-04", - f: jnifix, - desc: `Fixes initializers of JNI's jobject and subtypes`, + f: noop, + desc: `Fixes initializers of JNI's jobject and subtypes (removed)`, disabled: false, } - -// Old state: -// -// type jobject *_jobject -// -// New state: -// -// type jobject uintptr -// -// and similar for subtypes of jobject. -// This fix finds nils initializing these types and replaces the nils with 0s. -func jnifix(f *ast.File) bool { - return typefix(f, func(s string) bool { - switch s { - case "C.jobject": - return true - case "C.jclass": - return true - case "C.jthrowable": - return true - case "C.jstring": - return true - case "C.jarray": - return true - case "C.jbooleanArray": - return true - case "C.jbyteArray": - return true - case "C.jcharArray": - return true - case "C.jshortArray": - return true - case "C.jintArray": - return true - case "C.jlongArray": - return true - case "C.jfloatArray": - return true - case "C.jdoubleArray": - return true - case "C.jobjectArray": - return true - case "C.jweak": - return true - } - return false - }) -} diff --git a/src/cmd/fix/jnitype_test.go b/src/cmd/fix/jnitype_test.go deleted file mode 100644 index ecf01408c7c840..00000000000000 --- a/src/cmd/fix/jnitype_test.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2017 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 main - -func init() { - addTestCases(jniTests, jnifix) -} - -var jniTests = []testCase{ - { - Name: "jni.localVariable", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -func f() { - var x C.jobject = nil - x = nil - x, x = nil, nil -} -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -func f() { - var x C.jobject = 0 - x = 0 - x, x = 0, 0 -} -`, - }, - { - Name: "jni.globalVariable", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x C.jobject = nil - -func f() { - x = nil -} -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x C.jobject = 0 - -func f() { - x = 0 -} -`, - }, - { - Name: "jni.EqualArgument", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x C.jobject -var y = x == nil -var z = x != nil -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x C.jobject -var y = x == 0 -var z = x != 0 -`, - }, - { - Name: "jni.StructField", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -type T struct { - x C.jobject -} - -var t = T{x: nil} -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -type T struct { - x C.jobject -} - -var t = T{x: 0} -`, - }, - { - Name: "jni.FunctionArgument", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -func f(x C.jobject) { -} - -func g() { - f(nil) -} -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -func f(x C.jobject) { -} - -func g() { - f(0) -} -`, - }, - { - Name: "jni.ArrayElement", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x = [3]C.jobject{nil, nil, nil} -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x = [3]C.jobject{0, 0, 0} -`, - }, - { - Name: "jni.SliceElement", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x = []C.jobject{nil, nil, nil} -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x = []C.jobject{0, 0, 0} -`, - }, - { - Name: "jni.MapKey", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x = map[C.jobject]int{nil: 0} -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x = map[C.jobject]int{0: 0} -`, - }, - { - Name: "jni.MapValue", - In: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x = map[int]C.jobject{0: nil} -`, - Out: `package main - -// typedef struct _jobject* jobject; -import "C" - -var x = map[int]C.jobject{0: 0} -`, - }, -} diff --git a/src/cmd/fix/netipv6zone.go b/src/cmd/fix/netipv6zone.go index c27b4b4529abfb..75d2150e43c7a0 100644 --- a/src/cmd/fix/netipv6zone.go +++ b/src/cmd/fix/netipv6zone.go @@ -4,11 +4,6 @@ package main -import ( - "go/ast" - "slices" -) - func init() { register(netipv6zoneFix) } @@ -16,56 +11,9 @@ func init() { var netipv6zoneFix = fix{ name: "netipv6zone", date: "2012-11-26", - f: netipv6zone, - desc: `Adapt element key to IPAddr, UDPAddr or TCPAddr composite literals. + f: noop, + desc: `Adapt element key to IPAddr, UDPAddr or TCPAddr composite literals (removed). https://codereview.appspot.com/6849045/ `, } - -func netipv6zone(f *ast.File) bool { - if !imports(f, "net") { - return false - } - - fixed := false - walk(f, func(n any) { - cl, ok := n.(*ast.CompositeLit) - if !ok { - return - } - se, ok := cl.Type.(*ast.SelectorExpr) - if !ok { - return - } - if !isTopName(se.X, "net") || se.Sel == nil { - return - } - switch ss := se.Sel.String(); ss { - case "IPAddr", "UDPAddr", "TCPAddr": - for i, e := range cl.Elts { - if _, ok := e.(*ast.KeyValueExpr); ok { - break - } - switch i { - case 0: - cl.Elts[i] = &ast.KeyValueExpr{ - Key: ast.NewIdent("IP"), - Value: e, - } - case 1: - if elit, ok := e.(*ast.BasicLit); ok && elit.Value == "0" { - cl.Elts = slices.Delete(cl.Elts, i, i+1) - } else { - cl.Elts[i] = &ast.KeyValueExpr{ - Key: ast.NewIdent("Port"), - Value: e, - } - } - } - fixed = true - } - } - }) - return fixed -} diff --git a/src/cmd/fix/netipv6zone_test.go b/src/cmd/fix/netipv6zone_test.go deleted file mode 100644 index 5b8d964d4134b1..00000000000000 --- a/src/cmd/fix/netipv6zone_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2012 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 main - -func init() { - addTestCases(netipv6zoneTests, netipv6zone) -} - -var netipv6zoneTests = []testCase{ - { - Name: "netipv6zone.0", - In: `package main - -import "net" - -func f() net.Addr { - a := &net.IPAddr{ip1} - sub(&net.UDPAddr{ip2, 12345}) - c := &net.TCPAddr{IP: ip3, Port: 54321} - d := &net.TCPAddr{ip4, 0} - p := 1234 - e := &net.TCPAddr{ip4, p} - return &net.TCPAddr{ip5}, nil -} -`, - Out: `package main - -import "net" - -func f() net.Addr { - a := &net.IPAddr{IP: ip1} - sub(&net.UDPAddr{IP: ip2, Port: 12345}) - c := &net.TCPAddr{IP: ip3, Port: 54321} - d := &net.TCPAddr{IP: ip4} - p := 1234 - e := &net.TCPAddr{IP: ip4, Port: p} - return &net.TCPAddr{IP: ip5}, nil -} -`, - }, -} diff --git a/src/cmd/fix/printerconfig.go b/src/cmd/fix/printerconfig.go index bad69531964214..f9e49d7c0bd05d 100644 --- a/src/cmd/fix/printerconfig.go +++ b/src/cmd/fix/printerconfig.go @@ -4,8 +4,6 @@ package main -import "go/ast" - func init() { register(printerconfigFix) } @@ -13,49 +11,6 @@ func init() { var printerconfigFix = fix{ name: "printerconfig", date: "2012-12-11", - f: printerconfig, - desc: `Add element keys to Config composite literals.`, -} - -func printerconfig(f *ast.File) bool { - if !imports(f, "go/printer") { - return false - } - - fixed := false - walk(f, func(n any) { - cl, ok := n.(*ast.CompositeLit) - if !ok { - return - } - se, ok := cl.Type.(*ast.SelectorExpr) - if !ok { - return - } - if !isTopName(se.X, "printer") || se.Sel == nil { - return - } - - if ss := se.Sel.String(); ss == "Config" { - for i, e := range cl.Elts { - if _, ok := e.(*ast.KeyValueExpr); ok { - break - } - switch i { - case 0: - cl.Elts[i] = &ast.KeyValueExpr{ - Key: ast.NewIdent("Mode"), - Value: e, - } - case 1: - cl.Elts[i] = &ast.KeyValueExpr{ - Key: ast.NewIdent("Tabwidth"), - Value: e, - } - } - fixed = true - } - } - }) - return fixed + f: noop, + desc: `Add element keys to Config composite literals (removed).`, } diff --git a/src/cmd/fix/printerconfig_test.go b/src/cmd/fix/printerconfig_test.go deleted file mode 100644 index e485c137b7bbeb..00000000000000 --- a/src/cmd/fix/printerconfig_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2012 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 main - -func init() { - addTestCases(printerconfigTests, printerconfig) -} - -var printerconfigTests = []testCase{ - { - Name: "printerconfig.0", - In: `package main - -import "go/printer" - -func f() printer.Config { - b := printer.Config{0, 8} - c := &printer.Config{0} - d := &printer.Config{Tabwidth: 8, Mode: 0} - return printer.Config{0, 8} -} -`, - Out: `package main - -import "go/printer" - -func f() printer.Config { - b := printer.Config{Mode: 0, Tabwidth: 8} - c := &printer.Config{Mode: 0} - d := &printer.Config{Tabwidth: 8, Mode: 0} - return printer.Config{Mode: 0, Tabwidth: 8} -} -`, - }, -} From 3492e4262b751c5a117a3c77ba5e243b16918e61 Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Wed, 3 Sep 2025 15:43:21 +0800 Subject: [PATCH 32/40] cmd/compile: simplify specific addition operations using the ADDV16 instruction On loong64, the addi.d instruction can only directly handle 12-bit immediate numbers. If a larger immediate number needs to be processed, it must first be placed in a register, and then the add.d instruction is used to complete the processing of the larger immediate number. If a larger immediate number c satisfies is32Bit(c) && c&0xffff == 0, then the ADDV16 instruction can be used to complete the addition operation. Removes 164 instructions from the go binary on loong64. Change-Id: I404de93cc4eaaa12fe424f5a0d61b03231215d1a Reviewed-on: https://go-review.googlesource.com/c/go/+/700536 Reviewed-by: Meidan Li Reviewed-by: Keith Randall Reviewed-by: Keith Randall Auto-Submit: Michael Pratt Reviewed-by: abner chenc LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt --- src/cmd/compile/internal/loong64/ssa.go | 1 + src/cmd/compile/internal/ssa/_gen/LOONG64.rules | 1 + src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go | 9 +++++---- src/cmd/compile/internal/ssa/opGen.go | 15 +++++++++++++++ src/cmd/compile/internal/ssa/rewriteLOONG64.go | 14 ++++++++++++++ test/codegen/arithmetic.go | 5 +++++ 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/cmd/compile/internal/loong64/ssa.go b/src/cmd/compile/internal/loong64/ssa.go index 134575c85ca658..40fa10c6de7d3d 100644 --- a/src/cmd/compile/internal/loong64/ssa.go +++ b/src/cmd/compile/internal/loong64/ssa.go @@ -276,6 +276,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpLOONG64ADDVconst, + ssa.OpLOONG64ADDV16const, ssa.OpLOONG64SUBVconst, ssa.OpLOONG64ANDconst, ssa.OpLOONG64ORconst, diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules index 3fa4f363f65515..501d3745292c05 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules @@ -794,6 +794,7 @@ (SUBVconst [c] (SUBVconst [d] x)) && is32Bit(-c-d) => (ADDVconst [-c-d] x) (SUBVconst [c] (ADDVconst [d] x)) && is32Bit(-c+d) => (ADDVconst [-c+d] x) (SUBV (MOVVconst [c]) (NEGV (SUBVconst [d] x))) => (ADDVconst [c-d] x) +(ADDVconst [c] x) && is32Bit(c) && c&0xffff == 0 && c != 0 => (ADDV16const [c] x) (SLLVconst [c] (MOVVconst [d])) => (MOVVconst [d< (MOVVconst [int64(uint64(d)>>uint64(c))]) (SRAVconst [c] (MOVVconst [d])) => (MOVVconst [d>>uint64(c)]) diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go index bee619f6d93a78..0d5e0eb76f8832 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64Ops.go @@ -189,10 +189,11 @@ func init() { {name: "VPCNT16", argLength: 1, reg: fp11, asm: "VPCNTH"}, // count set bits for each 16-bit unit and store the result in each 16-bit unit // binary ops - {name: "ADDV", argLength: 2, reg: gp21, asm: "ADDVU", commutative: true}, // arg0 + arg1 - {name: "ADDVconst", argLength: 1, reg: gp11sp, asm: "ADDVU", aux: "Int64"}, // arg0 + auxInt. auxInt is 32-bit, also in other *const ops. - {name: "SUBV", argLength: 2, reg: gp21, asm: "SUBVU"}, // arg0 - arg1 - {name: "SUBVconst", argLength: 1, reg: gp11, asm: "SUBVU", aux: "Int64"}, // arg0 - auxInt + {name: "ADDV", argLength: 2, reg: gp21, asm: "ADDVU", commutative: true}, // arg0 + arg1 + {name: "ADDVconst", argLength: 1, reg: gp11sp, asm: "ADDVU", aux: "Int64"}, // arg0 + auxInt. auxInt is 32-bit, also in other *const ops. + {name: "ADDV16const", argLength: 1, reg: gp11sp, asm: "ADDV16", aux: "Int64"}, // arg0 + auxInt. auxInt is signed 32-bit and is a multiple of 65536, also in other *const ops. + {name: "SUBV", argLength: 2, reg: gp21, asm: "SUBVU"}, // arg0 - arg1 + {name: "SUBVconst", argLength: 1, reg: gp11, asm: "SUBVU", aux: "Int64"}, // arg0 - auxInt {name: "MULV", argLength: 2, reg: gp21, asm: "MULV", commutative: true, typ: "Int64"}, // arg0 * arg1 {name: "MULHV", argLength: 2, reg: gp21, asm: "MULHV", commutative: true, typ: "Int64"}, // (arg0 * arg1) >> 64, signed diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 4aef2d2aa1e9c5..d9cccb27ba9000 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1790,6 +1790,7 @@ const ( OpLOONG64VPCNT16 OpLOONG64ADDV OpLOONG64ADDVconst + OpLOONG64ADDV16const OpLOONG64SUBV OpLOONG64SUBVconst OpLOONG64MULV @@ -24067,6 +24068,20 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "ADDV16const", + auxType: auxInt64, + argLen: 1, + asm: loong64.AADDV16, + reg: regInfo{ + inputs: []inputInfo{ + {0, 1073741820}, // SP R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 g R23 R24 R25 R26 R27 R28 R29 R31 + }, + outputs: []outputInfo{ + {0, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31 + }, + }, + }, { name: "SUBV", argLen: 2, diff --git a/src/cmd/compile/internal/ssa/rewriteLOONG64.go b/src/cmd/compile/internal/ssa/rewriteLOONG64.go index 5890fe050a222b..c49ce31ae43e30 100644 --- a/src/cmd/compile/internal/ssa/rewriteLOONG64.go +++ b/src/cmd/compile/internal/ssa/rewriteLOONG64.go @@ -2008,6 +2008,20 @@ func rewriteValueLOONG64_OpLOONG64ADDVconst(v *Value) bool { v.AddArg(x) return true } + // match: (ADDVconst [c] x) + // cond: is32Bit(c) && c&0xffff == 0 && c != 0 + // result: (ADDV16const [c] x) + for { + c := auxIntToInt64(v.AuxInt) + x := v_0 + if !(is32Bit(c) && c&0xffff == 0 && c != 0) { + break + } + v.reset(OpLOONG64ADDV16const) + v.AuxInt = int64ToAuxInt(c) + v.AddArg(x) + return true + } return false } func rewriteValueLOONG64_OpLOONG64AND(v *Value) bool { diff --git a/test/codegen/arithmetic.go b/test/codegen/arithmetic.go index beabfe24eb95c7..7055db3dc9f72f 100644 --- a/test/codegen/arithmetic.go +++ b/test/codegen/arithmetic.go @@ -51,6 +51,11 @@ func AddLargeConst(a uint64, out []uint64) { out[9] = a - 32769 } +func AddLargeConst2(a int, out []int) { + // loong64: -"ADDVU","ADDV16" + out[0] = a + 0x10000 +} + // ----------------- // // Subtraction // // ----------------- // From f5b20689e9a055223a6dbb6b63f53648ed02cd74 Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Thu, 4 Sep 2025 19:13:23 +0800 Subject: [PATCH 33/40] cmd/compile: optimize loads from readonly globals into constants on loong64 Ref: CL 141118 Update #26498 Change-Id: I9c4ad2bedc4d50bd273bbe9119a898d4fca95e45 Reviewed-on: https://go-review.googlesource.com/c/go/+/700875 Reviewed-by: abner chenc Reviewed-by: Michael Pratt Reviewed-by: Meidan Li Auto-Submit: Michael Pratt Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- .../compile/internal/ssa/_gen/LOONG64.rules | 9 ++ .../compile/internal/ssa/rewriteLOONG64.go | 91 +++++++++++++++++++ test/codegen/strings.go | 6 ++ 3 files changed, 106 insertions(+) diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules index 501d3745292c05..a3dd050d14b843 100644 --- a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules +++ b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules @@ -949,3 +949,12 @@ && isInlinableMemmove(dst, src, sz, config) && clobber(call) => (Move [sz] dst src mem) + +// fold readonly sym load +(MOVBUload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(read8(sym, int64(off)))]) +(MOVHUload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))]) +(MOVWUload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(read32(sym, int64(off), config.ctxt.Arch.ByteOrder))]) +(MOVVload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(read64(sym, int64(off), config.ctxt.Arch.ByteOrder))]) +(MOVBload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(int8(read8(sym, int64(off))))]) +(MOVHload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(int16(read16(sym, int64(off), config.ctxt.Arch.ByteOrder)))]) +(MOVWload [off] {sym} (SB) _) && symIsRO(sym) => (MOVVconst [int64(int32(read32(sym, int64(off), config.ctxt.Arch.ByteOrder)))]) diff --git a/src/cmd/compile/internal/ssa/rewriteLOONG64.go b/src/cmd/compile/internal/ssa/rewriteLOONG64.go index c49ce31ae43e30..036d70a15fbc53 100644 --- a/src/cmd/compile/internal/ssa/rewriteLOONG64.go +++ b/src/cmd/compile/internal/ssa/rewriteLOONG64.go @@ -2422,6 +2422,19 @@ func rewriteValueLOONG64_OpLOONG64MOVBUload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (MOVBUload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(read8(sym, int64(off)))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpLOONG64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(read8(sym, int64(off)))) + return true + } return false } func rewriteValueLOONG64_OpLOONG64MOVBUloadidx(v *Value) bool { @@ -2716,6 +2729,19 @@ func rewriteValueLOONG64_OpLOONG64MOVBload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (MOVBload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(int8(read8(sym, int64(off))))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpLOONG64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(int8(read8(sym, int64(off))))) + return true + } return false } func rewriteValueLOONG64_OpLOONG64MOVBloadidx(v *Value) bool { @@ -3653,6 +3679,19 @@ func rewriteValueLOONG64_OpLOONG64MOVHUload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (MOVHUload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpLOONG64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(read16(sym, int64(off), config.ctxt.Arch.ByteOrder))) + return true + } return false } func rewriteValueLOONG64_OpLOONG64MOVHUloadidx(v *Value) bool { @@ -3909,6 +3948,19 @@ func rewriteValueLOONG64_OpLOONG64MOVHload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (MOVHload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(int16(read16(sym, int64(off), config.ctxt.Arch.ByteOrder)))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpLOONG64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(int16(read16(sym, int64(off), config.ctxt.Arch.ByteOrder)))) + return true + } return false } func rewriteValueLOONG64_OpLOONG64MOVHloadidx(v *Value) bool { @@ -4369,6 +4421,19 @@ func rewriteValueLOONG64_OpLOONG64MOVVload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (MOVVload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(read64(sym, int64(off), config.ctxt.Arch.ByteOrder))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpLOONG64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(read64(sym, int64(off), config.ctxt.Arch.ByteOrder))) + return true + } return false } func rewriteValueLOONG64_OpLOONG64MOVVloadidx(v *Value) bool { @@ -4694,6 +4759,19 @@ func rewriteValueLOONG64_OpLOONG64MOVWUload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (MOVWUload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(read32(sym, int64(off), config.ctxt.Arch.ByteOrder))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpLOONG64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(read32(sym, int64(off), config.ctxt.Arch.ByteOrder))) + return true + } return false } func rewriteValueLOONG64_OpLOONG64MOVWUloadidx(v *Value) bool { @@ -4983,6 +5061,19 @@ func rewriteValueLOONG64_OpLOONG64MOVWload(v *Value) bool { v.AddArg3(ptr, idx, mem) return true } + // match: (MOVWload [off] {sym} (SB) _) + // cond: symIsRO(sym) + // result: (MOVVconst [int64(int32(read32(sym, int64(off), config.ctxt.Arch.ByteOrder)))]) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + if v_0.Op != OpSB || !(symIsRO(sym)) { + break + } + v.reset(OpLOONG64MOVVconst) + v.AuxInt = int64ToAuxInt(int64(int32(read32(sym, int64(off), config.ctxt.Arch.ByteOrder)))) + return true + } return false } func rewriteValueLOONG64_OpLOONG64MOVWloadidx(v *Value) bool { diff --git a/test/codegen/strings.go b/test/codegen/strings.go index 64dcf4c8428d4c..fbcef57cd1a33e 100644 --- a/test/codegen/strings.go +++ b/test/codegen/strings.go @@ -39,6 +39,7 @@ func ConstantLoad() { // 386:`MOVW\t\$12592, \(`,`MOVB\t\$50, 2\(` // arm:`MOVW\t\$48`,`MOVW\t\$49`,`MOVW\t\$50` // arm64:`MOVD\t\$12592`,`MOVD\t\$50` + // loong64:`MOVV\t\$12592`,`MOVV\t\$50` // wasm:`I64Const\t\$12592`,`I64Store16\t\$0`,`I64Const\t\$50`,`I64Store8\t\$2` // mips64:`MOVV\t\$48`,`MOVV\t\$49`,`MOVV\t\$50` bsink = []byte("012") @@ -48,6 +49,7 @@ func ConstantLoad() { // amd64:`MOVL\t\$858927408`,`MOVW\t\$13620, 4\(` // 386:`MOVL\t\$858927408`,`MOVW\t\$13620, 4\(` // arm64:`MOVD\t\$858927408`,`MOVD\t\$13620` + // loong64:`MOVV\t\$858927408`,`MOVV\t\$13620` // wasm:`I64Const\t\$858927408`,`I64Store32\t\$0`,`I64Const\t\$13620`,`I64Store16\t\$4` bsink = []byte("012345") @@ -56,19 +58,23 @@ func ConstantLoad() { // amd64:`MOVQ\t\$3978425819141910832`,`MOVQ\t\$7306073769690871863` // 386:`MOVL\t\$858927408, \(`,`DUFFCOPY` // arm64:`MOVD\t\$3978425819141910832`,`MOVD\t\$7306073769690871863`,`MOVD\t\$15` + // loong64:`MOVV\t\$3978425819141910832`,`MOVV\t\$7306073769690871863`,`MOVV\t\$15` // wasm:`I64Const\t\$3978425819141910832`,`I64Store\t\$0`,`I64Const\t\$7306073769690871863`,`I64Store\t\$7` bsink = []byte("0123456789abcde") // 56 = 0x38 // amd64:`MOVQ\t\$3978425819141910832`,`MOVB\t\$56` + // loong64:`MOVV\t\$3978425819141910832`,`MOVV\t\$56` bsink = []byte("012345678") // 14648 = 0x3938 // amd64:`MOVQ\t\$3978425819141910832`,`MOVW\t\$14648` + // loong64:`MOVV\t\$3978425819141910832`,`MOVV\t\$14648` bsink = []byte("0123456789") // 1650538808 = 0x62613938 // amd64:`MOVQ\t\$3978425819141910832`,`MOVL\t\$1650538808` + // loong64:`MOVV\t\$3978425819141910832`,`MOVV\t\$1650538808` bsink = []byte("0123456789ab") } From 0b1eed09a34f5a1e0a5c237bc9771eb036b35319 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 4 Sep 2025 15:29:52 -0400 Subject: [PATCH 34/40] vendor/golang.org/x/tools: update to a09a2fb Notably, this includes unitchecker's -fix flag. Also, fix one vet test that failed due to new diagnostic wording. Change-Id: I87751083dcd9cc4b1d8dce7d54bb796c745436d0 Reviewed-on: https://go-review.googlesource.com/c/go/+/701195 Reviewed-by: Dmitri Shuralyov Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI --- src/cmd/go.mod | 2 +- src/cmd/go.sum | 4 +- .../go/analysis/internal/analysisflags/fix.go | 284 +++++++++ .../analysis/internal/analysisflags/flags.go | 13 +- .../go/analysis/unitchecker/unitchecker.go | 98 ++- .../golang.org/x/tools/internal/diff/diff.go | 177 ++++++ .../x/tools/internal/diff/lcs/common.go | 179 ++++++ .../x/tools/internal/diff/lcs/doc.go | 156 +++++ .../x/tools/internal/diff/lcs/git.sh | 33 + .../x/tools/internal/diff/lcs/labels.go | 55 ++ .../x/tools/internal/diff/lcs/old.go | 478 ++++++++++++++ .../x/tools/internal/diff/lcs/sequence.go | 113 ++++ .../golang.org/x/tools/internal/diff/merge.go | 81 +++ .../golang.org/x/tools/internal/diff/ndiff.go | 99 +++ .../x/tools/internal/diff/unified.go | 251 ++++++++ .../x/tools/internal/stdlib/deps.go | 596 +++++++++--------- .../x/tools/internal/stdlib/manifest.go | 58 +- src/cmd/vendor/modules.txt | 6 +- 18 files changed, 2350 insertions(+), 333 deletions(-) create mode 100644 src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/fix.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/diff.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/common.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/doc.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/git.sh create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/labels.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/sequence.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/merge.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/ndiff.go create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/diff/unified.go diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 46630f4ed83f05..98244769b40b18 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -11,7 +11,7 @@ require ( golang.org/x/sys v0.35.0 golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488 golang.org/x/term v0.34.0 - golang.org/x/tools v0.36.1-0.20250808220315-8866876b956f + golang.org/x/tools v0.36.1-0.20250904192731-a09a2fba1c08 ) require ( diff --git a/src/cmd/go.sum b/src/cmd/go.sum index 57c506936c4475..606a2d02313dc5 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -22,7 +22,7 @@ golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/tools v0.36.1-0.20250808220315-8866876b956f h1:9m2Iptt9ZZU5llKDJy1XUl5d13PN1ZYV16KwOvE6jOw= -golang.org/x/tools v0.36.1-0.20250808220315-8866876b956f/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.36.1-0.20250904192731-a09a2fba1c08 h1:KS/PXsrK6W9NdlNu8iuCiNb7KM8UFwsh8g1BUjJ9rww= +golang.org/x/tools v0.36.1-0.20250904192731-a09a2fba1c08/go.mod h1:n+8pplxVZfXnmHBxWsfPnQRJ5vWroQDk+U2MFpjwtFY= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ= diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/fix.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/fix.go new file mode 100644 index 00000000000000..43a456a4576f18 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/fix.go @@ -0,0 +1,284 @@ +// Copyright 2018 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 analysisflags + +// This file defines the -fix logic common to unitchecker and +// {single,multi}checker. + +import ( + "fmt" + "go/format" + "go/token" + "log" + "maps" + "os" + "sort" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/diff" +) + +// FixAction abstracts a checker action (running one analyzer on one +// package) for the purposes of applying its diagnostics' fixes. +type FixAction struct { + Name string // e.g. "analyzer@package" + FileSet *token.FileSet + ReadFileFunc analysisinternal.ReadFileFunc + Diagnostics []analysis.Diagnostic +} + +// ApplyFixes attempts to apply the first suggested fix associated +// with each diagnostic reported by the specified actions. +// All fixes must have been validated by [analysisinternal.ValidateFixes]. +// +// Each fix is treated as an independent change; fixes are merged in +// an arbitrary deterministic order as if by a three-way diff tool +// such as the UNIX diff3 command or 'git merge'. Any fix that cannot be +// cleanly merged is discarded, in which case the final summary tells +// the user to re-run the tool. +// TODO(adonovan): make the checker tool re-run the analysis itself. +// +// When the same file is analyzed as a member of both a primary +// package "p" and a test-augmented package "p [p.test]", there may be +// duplicate diagnostics and fixes. One set of fixes will be applied +// and the other will be discarded; but re-running the tool may then +// show zero fixes, which may cause the confused user to wonder what +// happened to the other ones. +// TODO(adonovan): consider pre-filtering completely identical fixes. +// +// A common reason for overlapping fixes is duplicate additions of the +// same import. The merge algorithm may often cleanly resolve such +// fixes, coalescing identical edits, but the merge may sometimes be +// confused by nearby changes. +// +// Even when merging succeeds, there is no guarantee that the +// composition of the two fixes is semantically correct. Coalescing +// identical edits is appropriate for imports, but not for, say, +// increments to a counter variable; the correct resolution in that +// case might be to increment it twice. Or consider two fixes that +// each delete the penultimate reference to an import or local +// variable: each fix is sound individually, and they may be textually +// distant from each other, but when both are applied, the program is +// no longer valid because it has an unreferenced import or local +// variable. +// TODO(adonovan): investigate replacing the final "gofmt" step with a +// formatter that applies the unused-import deletion logic of +// "goimports". +// +// Merging depends on both the order of fixes and they order of edits +// within them. For example, if three fixes add import "a" twice and +// import "b" once, the two imports of "a" may be combined if they +// appear in order [a, a, b], or not if they appear as [a, b, a]. +// TODO(adonovan): investigate an algebraic approach to imports; +// that is, for fixes to Go source files, convert changes within the +// import(...) portion of the file into semantic edits, compose those +// edits algebraically, then convert the result back to edits. +// +// applyFixes returns success if all fixes are valid, could be cleanly +// merged, and the corresponding files were successfully updated. +// +// If the -diff flag was set, instead of updating the files it display the final +// patch composed of all the cleanly merged fixes. +// +// TODO(adonovan): handle file-system level aliases such as symbolic +// links using robustio.FileID. +func ApplyFixes(actions []FixAction, verbose bool) error { + // Select fixes to apply. + // + // If there are several for a given Diagnostic, choose the first. + // Preserve the order of iteration, for determinism. + type fixact struct { + fix *analysis.SuggestedFix + act FixAction + } + var fixes []*fixact + for _, act := range actions { + for _, diag := range act.Diagnostics { + for i := range diag.SuggestedFixes { + fix := &diag.SuggestedFixes[i] + if i == 0 { + fixes = append(fixes, &fixact{fix, act}) + } else { + // TODO(adonovan): abstract the logger. + log.Printf("%s: ignoring alternative fix %q", act.Name, fix.Message) + } + } + } + } + + // Read file content on demand, from the virtual + // file system that fed the analyzer (see #62292). + // + // This cache assumes that all successful reads for the same + // file name return the same content. + // (It is tempting to group fixes by package and do the + // merge/apply/format steps one package at a time, but + // packages are not disjoint, due to test variants, so this + // would not really address the issue.) + baselineContent := make(map[string][]byte) + getBaseline := func(readFile analysisinternal.ReadFileFunc, filename string) ([]byte, error) { + content, ok := baselineContent[filename] + if !ok { + var err error + content, err = readFile(filename) + if err != nil { + return nil, err + } + baselineContent[filename] = content + } + return content, nil + } + + // Apply each fix, updating the current state + // only if the entire fix can be cleanly merged. + accumulatedEdits := make(map[string][]diff.Edit) + goodFixes := 0 +fixloop: + for _, fixact := range fixes { + // Convert analysis.TextEdits to diff.Edits, grouped by file. + // Precondition: a prior call to validateFix succeeded. + fileEdits := make(map[string][]diff.Edit) + for _, edit := range fixact.fix.TextEdits { + file := fixact.act.FileSet.File(edit.Pos) + + baseline, err := getBaseline(fixact.act.ReadFileFunc, file.Name()) + if err != nil { + log.Printf("skipping fix to file %s: %v", file.Name(), err) + continue fixloop + } + + // We choose to treat size mismatch as a serious error, + // as it indicates a concurrent write to at least one file, + // and possibly others (consider a git checkout, for example). + if file.Size() != len(baseline) { + return fmt.Errorf("concurrent file modification detected in file %s (size changed from %d -> %d bytes); aborting fix", + file.Name(), file.Size(), len(baseline)) + } + + fileEdits[file.Name()] = append(fileEdits[file.Name()], diff.Edit{ + Start: file.Offset(edit.Pos), + End: file.Offset(edit.End), + New: string(edit.NewText), + }) + } + + // Apply each set of edits by merging atop + // the previous accumulated state. + after := make(map[string][]diff.Edit) + for file, edits := range fileEdits { + if prev := accumulatedEdits[file]; len(prev) > 0 { + merged, ok := diff.Merge(prev, edits) + if !ok { + // debugging + if false { + log.Printf("%s: fix %s conflicts", fixact.act.Name, fixact.fix.Message) + } + continue fixloop // conflict + } + edits = merged + } + after[file] = edits + } + + // The entire fix applied cleanly; commit it. + goodFixes++ + maps.Copy(accumulatedEdits, after) + // debugging + if false { + log.Printf("%s: fix %s applied", fixact.act.Name, fixact.fix.Message) + } + } + badFixes := len(fixes) - goodFixes + + // Show diff or update files to final state. + var files []string + for file := range accumulatedEdits { + files = append(files, file) + } + sort.Strings(files) // for deterministic -diff + var filesUpdated, totalFiles int + for _, file := range files { + edits := accumulatedEdits[file] + if len(edits) == 0 { + continue // the diffs annihilated (a miracle?) + } + + // Apply accumulated fixes. + baseline := baselineContent[file] // (cache hit) + final, err := diff.ApplyBytes(baseline, edits) + if err != nil { + log.Fatalf("internal error in diff.ApplyBytes: %v", err) + } + + // Attempt to format each file. + if formatted, err := format.Source(final); err == nil { + final = formatted + } + + if diffFlag { + // Since we formatted the file, we need to recompute the diff. + unified := diff.Unified(file+" (old)", file+" (new)", string(baseline), string(final)) + // TODO(adonovan): abstract the I/O. + os.Stdout.WriteString(unified) + + } else { + // write + totalFiles++ + // TODO(adonovan): abstract the I/O. + if err := os.WriteFile(file, final, 0644); err != nil { + log.Println(err) + continue + } + filesUpdated++ + } + } + + // TODO(adonovan): consider returning a structured result that + // maps each SuggestedFix to its status: + // - invalid + // - secondary, not selected + // - applied + // - had conflicts. + // and a mapping from each affected file to: + // - its final/original content pair, and + // - whether formatting was successful. + // Then file writes and the UI can be applied by the caller + // in whatever form they like. + + // If victory was incomplete, report an error that indicates partial progress. + // + // badFixes > 0 indicates that we decided not to attempt some + // fixes due to conflicts or failure to read the source; still + // it's a relatively benign situation since the user can + // re-run the tool, and we may still make progress. + // + // filesUpdated < totalFiles indicates that some file updates + // failed. This should be rare, but is a serious error as it + // may apply half a fix, or leave the files in a bad state. + // + // These numbers are potentially misleading: + // The denominator includes duplicate conflicting fixes due to + // common files in packages "p" and "p [p.test]", which may + // have been fixed fixed and won't appear in the re-run. + // TODO(adonovan): eliminate identical fixes as an initial + // filtering step. + // + // TODO(adonovan): should we log that n files were updated in case of total victory? + if badFixes > 0 || filesUpdated < totalFiles { + if diffFlag { + return fmt.Errorf("%d of %d fixes skipped (e.g. due to conflicts)", badFixes, len(fixes)) + } else { + return fmt.Errorf("applied %d of %d fixes; %d files updated. (Re-run the command to apply more.)", + goodFixes, len(fixes), filesUpdated) + } + } + + if verbose { + log.Printf("applied %d fixes, updated %d files", len(fixes), filesUpdated) + } + + return nil +} diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go index 18e01c40def3aa..ffc4169083269a 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package analysisflags defines helpers for processing flags of -// analysis driver tools. +// Package analysisflags defines helpers for processing flags (-help, +// -json, -fix, -diff, etc) common to unitchecker and +// {single,multi}checker. It is not intended for broader use. package analysisflags import ( @@ -24,8 +25,10 @@ import ( // flags common to all {single,multi,unit}checkers. var ( - JSON = false // -json - Context = -1 // -c=N: if N>0, display offending line plus N lines of context + JSON = false // -json + Context = -1 // -c=N: if N>0, display offending line plus N lines of context + Fix bool // -fix + diffFlag bool // -diff (changes [ApplyFixes] behavior) ) // Parse creates a flag for each of the analyzer's flags, @@ -74,6 +77,8 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer { // flags common to all checkers flag.BoolVar(&JSON, "json", JSON, "emit JSON output") flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`) + flag.BoolVar(&Fix, "fix", false, "apply all suggested fixes") + flag.BoolVar(&diffFlag, "diff", false, "with -fix, don't update the files, but print a unified diff") // Add shims for legacy vet flags to enable existing // scripts that run vet to continue to work. diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go index a1ee80388b6ed2..5503916243b600 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go @@ -85,6 +85,18 @@ type Config struct { // -V=full describe executable for build caching // foo.cfg perform separate modular analyze on the single // unit described by a JSON config file foo.cfg. +// +// Also, subject to approval of proposal #71859: +// +// -fix don't print each diagnostic, apply its first fix +// -diff don't apply a fix, print the diff (requires -fix) +// +// Additionally, the environment variable GOVET has the value "vet" or +// "fix" depending on whether the command is being invoked by "go vet", +// to report diagnostics, or "go fix", to apply fixes. This is +// necessary so that callers of Main can select their analyzer suite +// before flag parsing. (Vet analyzers must report real code problems, +// whereas Fix analyzers may fix non-problems such as style issues.) func Main(analyzers ...*analysis.Analyzer) { progname := filepath.Base(os.Args[0]) log.SetFlags(0) @@ -136,35 +148,14 @@ func Run(configFile string, analyzers []*analysis.Analyzer) { log.Fatal(err) } + code := 0 + // In VetxOnly mode, the analysis is run only for facts. if !cfg.VetxOnly { - if analysisflags.JSON { - // JSON output - tree := make(analysisflags.JSONTree) - for _, res := range results { - tree.Add(fset, cfg.ID, res.a.Name, res.diagnostics, res.err) - } - tree.Print(os.Stdout) - } else { - // plain text - exit := 0 - for _, res := range results { - if res.err != nil { - log.Println(res.err) - exit = 1 - } - } - for _, res := range results { - for _, diag := range res.diagnostics { - analysisflags.PrintPlain(os.Stderr, fset, analysisflags.Context, diag) - exit = 1 - } - } - os.Exit(exit) - } + code = processResults(fset, cfg.ID, results) } - os.Exit(0) + os.Exit(code) } func readConfig(filename string) (*Config, error) { @@ -185,6 +176,63 @@ func readConfig(filename string) (*Config, error) { return cfg, nil } +func processResults(fset *token.FileSet, id string, results []result) (exit int) { + if analysisflags.Fix { + // Don't print the diagnostics, + // but apply all fixes from the root actions. + + // Convert results to form needed by ApplyFixes. + fixActions := make([]analysisflags.FixAction, len(results)) + for i, res := range results { + fixActions[i] = analysisflags.FixAction{ + Name: res.a.Name, + FileSet: fset, + ReadFileFunc: os.ReadFile, + Diagnostics: res.diagnostics, + } + } + if err := analysisflags.ApplyFixes(fixActions, false); err != nil { + // Fail when applying fixes failed. + log.Print(err) + exit = 1 + } + + // Don't proceed to print text/JSON, + // and don't report an error + // just because there were diagnostics. + return + } + + // Keep consistent with analogous logic in + // printDiagnostics in ../internal/checker/checker.go. + + if analysisflags.JSON { + // JSON output + tree := make(analysisflags.JSONTree) + for _, res := range results { + tree.Add(fset, id, res.a.Name, res.diagnostics, res.err) + } + tree.Print(os.Stdout) // ignore error + + } else { + // plain text + for _, res := range results { + if res.err != nil { + log.Println(res.err) + exit = 1 + } + } + for _, res := range results { + for _, diag := range res.diagnostics { + analysisflags.PrintPlain(os.Stderr, fset, analysisflags.Context, diag) + exit = 1 + } + } + } + + return +} + type factImporter = func(pkgPath string) ([]byte, error) // These four hook variables are a proof of concept of a future diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/diff.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/diff.go new file mode 100644 index 00000000000000..c12bdfd2acd652 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/diff.go @@ -0,0 +1,177 @@ +// Copyright 2019 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 diff computes differences between text files or strings. +package diff + +import ( + "fmt" + "slices" + "sort" + "strings" +) + +// An Edit describes the replacement of a portion of a text file. +type Edit struct { + Start, End int // byte offsets of the region to replace + New string // the replacement +} + +func (e Edit) String() string { + return fmt.Sprintf("{Start:%d,End:%d,New:%q}", e.Start, e.End, e.New) +} + +// Apply applies a sequence of edits to the src buffer and returns the +// result. Edits are applied in order of start offset; edits with the +// same start offset are applied in they order they were provided. +// +// Apply returns an error if any edit is out of bounds, +// or if any pair of edits is overlapping. +func Apply(src string, edits []Edit) (string, error) { + edits, size, err := validate(src, edits) + if err != nil { + return "", err + } + + // Apply edits. + out := make([]byte, 0, size) + lastEnd := 0 + for _, edit := range edits { + if lastEnd < edit.Start { + out = append(out, src[lastEnd:edit.Start]...) + } + out = append(out, edit.New...) + lastEnd = edit.End + } + out = append(out, src[lastEnd:]...) + + if len(out) != size { + panic("wrong size") + } + + return string(out), nil +} + +// ApplyBytes is like Apply, but it accepts a byte slice. +// The result is always a new array. +func ApplyBytes(src []byte, edits []Edit) ([]byte, error) { + res, err := Apply(string(src), edits) + return []byte(res), err +} + +// validate checks that edits are consistent with src, +// and returns the size of the patched output. +// It may return a different slice. +func validate(src string, edits []Edit) ([]Edit, int, error) { + if !sort.IsSorted(editsSort(edits)) { + edits = slices.Clone(edits) + SortEdits(edits) + } + + // Check validity of edits and compute final size. + size := len(src) + lastEnd := 0 + for _, edit := range edits { + if !(0 <= edit.Start && edit.Start <= edit.End && edit.End <= len(src)) { + return nil, 0, fmt.Errorf("diff has out-of-bounds edits") + } + if edit.Start < lastEnd { + return nil, 0, fmt.Errorf("diff has overlapping edits") + } + size += len(edit.New) + edit.Start - edit.End + lastEnd = edit.End + } + + return edits, size, nil +} + +// SortEdits orders a slice of Edits by (start, end) offset. +// This ordering puts insertions (end = start) before deletions +// (end > start) at the same point, but uses a stable sort to preserve +// the order of multiple insertions at the same point. +// (Apply detects multiple deletions at the same point as an error.) +func SortEdits(edits []Edit) { + sort.Stable(editsSort(edits)) +} + +type editsSort []Edit + +func (a editsSort) Len() int { return len(a) } +func (a editsSort) Less(i, j int) bool { + if cmp := a[i].Start - a[j].Start; cmp != 0 { + return cmp < 0 + } + return a[i].End < a[j].End +} +func (a editsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// lineEdits expands and merges a sequence of edits so that each +// resulting edit replaces one or more complete lines. +// See ApplyEdits for preconditions. +func lineEdits(src string, edits []Edit) ([]Edit, error) { + edits, _, err := validate(src, edits) + if err != nil { + return nil, err + } + + // Do all deletions begin and end at the start of a line, + // and all insertions end with a newline? + // (This is merely a fast path.) + for _, edit := range edits { + if edit.Start >= len(src) || // insertion at EOF + edit.Start > 0 && src[edit.Start-1] != '\n' || // not at line start + edit.End > 0 && src[edit.End-1] != '\n' || // not at line start + edit.New != "" && edit.New[len(edit.New)-1] != '\n' { // partial insert + goto expand // slow path + } + } + return edits, nil // aligned + +expand: + if len(edits) == 0 { + return edits, nil // no edits (unreachable due to fast path) + } + expanded := make([]Edit, 0, len(edits)) // a guess + prev := edits[0] + // TODO(adonovan): opt: start from the first misaligned edit. + // TODO(adonovan): opt: avoid quadratic cost of string += string. + for _, edit := range edits[1:] { + between := src[prev.End:edit.Start] + if !strings.Contains(between, "\n") { + // overlapping lines: combine with previous edit. + prev.New += between + edit.New + prev.End = edit.End + } else { + // non-overlapping lines: flush previous edit. + expanded = append(expanded, expandEdit(prev, src)) + prev = edit + } + } + return append(expanded, expandEdit(prev, src)), nil // flush final edit +} + +// expandEdit returns edit expanded to complete whole lines. +func expandEdit(edit Edit, src string) Edit { + // Expand start left to start of line. + // (delta is the zero-based column number of start.) + start := edit.Start + if delta := start - 1 - strings.LastIndex(src[:start], "\n"); delta > 0 { + edit.Start -= delta + edit.New = src[start-delta:start] + edit.New + } + + // Expand end right to end of line. + end := edit.End + if end > 0 && src[end-1] != '\n' || + edit.New != "" && edit.New[len(edit.New)-1] != '\n' { + if nl := strings.IndexByte(src[end:], '\n'); nl < 0 { + edit.End = len(src) // extend to EOF + } else { + edit.End = end + nl + 1 // extend beyond \n + } + } + edit.New += src[end:edit.End] + + return edit +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/common.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/common.go new file mode 100644 index 00000000000000..27fa9ecbd5c565 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/common.go @@ -0,0 +1,179 @@ +// Copyright 2022 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 lcs + +import ( + "log" + "sort" +) + +// lcs is a longest common sequence +type lcs []diag + +// A diag is a piece of the edit graph where A[X+i] == B[Y+i], for 0<=i l[j].Len + }) + return l +} + +// validate that the elements of the lcs do not overlap +// (can only happen when the two-sided algorithm ends early) +// expects the lcs to be sorted +func (l lcs) valid() bool { + for i := 1; i < len(l); i++ { + if l[i-1].X+l[i-1].Len > l[i].X { + return false + } + if l[i-1].Y+l[i-1].Len > l[i].Y { + return false + } + } + return true +} + +// repair overlapping lcs +// only called if two-sided stops early +func (l lcs) fix() lcs { + // from the set of diagonals in l, find a maximal non-conflicting set + // this problem may be NP-complete, but we use a greedy heuristic, + // which is quadratic, but with a better data structure, could be D log D. + // independent is not enough: {0,3,1} and {3,0,2} can't both occur in an lcs + // which has to have monotone x and y + if len(l) == 0 { + return nil + } + sort.Slice(l, func(i, j int) bool { return l[i].Len > l[j].Len }) + tmp := make(lcs, 0, len(l)) + tmp = append(tmp, l[0]) + for i := 1; i < len(l); i++ { + var dir direction + nxt := l[i] + for _, in := range tmp { + if dir, nxt = overlap(in, nxt); dir == empty || dir == bad { + break + } + } + if nxt.Len > 0 && dir != bad { + tmp = append(tmp, nxt) + } + } + tmp.sort() + if false && !tmp.valid() { // debug checking + log.Fatalf("here %d", len(tmp)) + } + return tmp +} + +type direction int + +const ( + empty direction = iota // diag is empty (so not in lcs) + leftdown // proposed acceptably to the left and below + rightup // proposed diag is acceptably to the right and above + bad // proposed diag is inconsistent with the lcs so far +) + +// overlap trims the proposed diag prop so it doesn't overlap with +// the existing diag that has already been added to the lcs. +func overlap(exist, prop diag) (direction, diag) { + if prop.X <= exist.X && exist.X < prop.X+prop.Len { + // remove the end of prop where it overlaps with the X end of exist + delta := prop.X + prop.Len - exist.X + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + } + if exist.X <= prop.X && prop.X < exist.X+exist.Len { + // remove the beginning of prop where overlaps with exist + delta := exist.X + exist.Len - prop.X + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + prop.X += delta + prop.Y += delta + } + if prop.Y <= exist.Y && exist.Y < prop.Y+prop.Len { + // remove the end of prop that overlaps (in Y) with exist + delta := prop.Y + prop.Len - exist.Y + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + } + if exist.Y <= prop.Y && prop.Y < exist.Y+exist.Len { + // remove the beginning of peop that overlaps with exist + delta := exist.Y + exist.Len - prop.Y + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + prop.X += delta // no test reaches this code + prop.Y += delta + } + if prop.X+prop.Len <= exist.X && prop.Y+prop.Len <= exist.Y { + return leftdown, prop + } + if exist.X+exist.Len <= prop.X && exist.Y+exist.Len <= prop.Y { + return rightup, prop + } + // prop can't be in an lcs that contains exist + return bad, prop +} + +// manipulating Diag and lcs + +// prepend a diagonal (x,y)-(x+1,y+1) segment either to an empty lcs +// or to its first Diag. prepend is only called to extend diagonals +// the backward direction. +func (lcs lcs) prepend(x, y int) lcs { + if len(lcs) > 0 { + d := &lcs[0] + if int(d.X) == x+1 && int(d.Y) == y+1 { + // extend the diagonal down and to the left + d.X, d.Y = int(x), int(y) + d.Len++ + return lcs + } + } + + r := diag{X: int(x), Y: int(y), Len: 1} + lcs = append([]diag{r}, lcs...) + return lcs +} + +// append appends a diagonal, or extends the existing one. +// by adding the edge (x,y)-(x+1.y+1). append is only called +// to extend diagonals in the forward direction. +func (lcs lcs) append(x, y int) lcs { + if len(lcs) > 0 { + last := &lcs[len(lcs)-1] + // Expand last element if adjoining. + if last.X+last.Len == x && last.Y+last.Len == y { + last.Len++ + return lcs + } + } + + return append(lcs, diag{X: x, Y: y, Len: 1}) +} + +// enforce constraint on d, k +func ok(d, k int) bool { + return d >= 0 && -d <= k && k <= d +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/doc.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/doc.go new file mode 100644 index 00000000000000..aa4b0fb5910e84 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/doc.go @@ -0,0 +1,156 @@ +// Copyright 2022 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 lcs contains code to find longest-common-subsequences +// (and diffs) +package lcs + +/* +Compute longest-common-subsequences of two slices A, B using +algorithms from Myers' paper. A longest-common-subsequence +(LCS from now on) of A and B is a maximal set of lexically increasing +pairs of subscripts (x,y) with A[x]==B[y]. There may be many LCS, but +they all have the same length. An LCS determines a sequence of edits +that changes A into B. + +The key concept is the edit graph of A and B. +If A has length N and B has length M, then the edit graph has +vertices v[i][j] for 0 <= i <= N, 0 <= j <= M. There is a +horizontal edge from v[i][j] to v[i+1][j] whenever both are in +the graph, and a vertical edge from v[i][j] to f[i][j+1] similarly. +When A[i] == B[j] there is a diagonal edge from v[i][j] to v[i+1][j+1]. + +A path between in the graph between (0,0) and (N,M) determines a sequence +of edits converting A into B: each horizontal edge corresponds to removing +an element of A, and each vertical edge corresponds to inserting an +element of B. + +A vertex (x,y) is on (forward) diagonal k if x-y=k. A path in the graph +is of length D if it has D non-diagonal edges. The algorithms generate +forward paths (in which at least one of x,y increases at each edge), +or backward paths (in which at least one of x,y decreases at each edge), +or a combination. (Note that the orientation is the traditional mathematical one, +with the origin in the lower-left corner.) + +Here is the edit graph for A:"aabbaa", B:"aacaba". (I know the diagonals look weird.) + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + b | | | ___/‾‾‾ | ___/‾‾‾ | | | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + c | | | | | | | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a a b b a a + + +The algorithm labels a vertex (x,y) with D,k if it is on diagonal k and at +the end of a maximal path of length D. (Because x-y=k it suffices to remember +only the x coordinate of the vertex.) + +The forward algorithm: Find the longest diagonal starting at (0,0) and +label its end with D=0,k=0. From that vertex take a vertical step and +then follow the longest diagonal (up and to the right), and label that vertex +with D=1,k=-1. From the D=0,k=0 point take a horizontal step and the follow +the longest diagonal (up and to the right) and label that vertex +D=1,k=1. In the same way, having labelled all the D vertices, +from a vertex labelled D,k find two vertices +tentatively labelled D+1,k-1 and D+1,k+1. There may be two on the same +diagonal, in which case take the one with the larger x. + +Eventually the path gets to (N,M), and the diagonals on it are the LCS. + +Here is the edit graph with the ends of D-paths labelled. (So, for instance, +0/2,2 indicates that x=2,y=2 is labelled with 0, as it should be, since the first +step is to go up the longest diagonal from (0,0).) +A:"aabbaa", B:"aacaba" + ⊙ ------- ⊙ ------- ⊙ -------(3/3,6)------- ⊙ -------(3/5,6)-------(4/6,6) + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ -------(2/3,5)------- ⊙ ------- ⊙ ------- ⊙ + b | | | ___/‾‾‾ | ___/‾‾‾ | | | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ -------(3/5,4)------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ -------(1/2,3)-------(2/3,3)------- ⊙ ------- ⊙ ------- ⊙ + c | | | | | | | + ⊙ ------- ⊙ -------(0/2,2)-------(1/3,2)-------(2/4,2)-------(3/5,2)-------(4/6,2) + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a a b b a a + +The 4-path is reconstructed starting at (4/6,6), horizontal to (3/5,6), diagonal to (3,4), vertical +to (2/3,3), horizontal to (1/2,3), vertical to (0/2,2), and diagonal to (0,0). As expected, +there are 4 non-diagonal steps, and the diagonals form an LCS. + +There is a symmetric backward algorithm, which gives (backwards labels are prefixed with a colon): +A:"aabbaa", B:"aacaba" + ⊙ -------- ⊙ -------- ⊙ -------- ⊙ -------- ⊙ -------- ⊙ -------- ⊙ + a | ____/‾‾‾ | ____/‾‾‾ | | | ____/‾‾‾ | ____/‾‾‾ | + ⊙ -------- ⊙ -------- ⊙ -------- ⊙ -------- ⊙ --------(:0/5,5)-------- ⊙ + b | | | ____/‾‾‾ | ____/‾‾‾ | | | + ⊙ -------- ⊙ -------- ⊙ --------(:1/3,4)-------- ⊙ -------- ⊙ -------- ⊙ + a | ____/‾‾‾ | ____/‾‾‾ | | | ____/‾‾‾ | ____/‾‾‾ | + (:3/0,3)--------(:2/1,3)-------- ⊙ --------(:2/3,3)--------(:1/4,3)-------- ⊙ -------- ⊙ + c | | | | | | | + ⊙ -------- ⊙ -------- ⊙ --------(:3/3,2)--------(:2/4,2)-------- ⊙ -------- ⊙ + a | ____/‾‾‾ | ____/‾‾‾ | | | ____/‾‾‾ | ____/‾‾‾ | + (:3/0,1)-------- ⊙ -------- ⊙ -------- ⊙ --------(:3/4,1)-------- ⊙ -------- ⊙ + a | ____/‾‾‾ | ____/‾‾‾ | | | ____/‾‾‾ | ____/‾‾‾ | + (:4/0,0)-------- ⊙ -------- ⊙ -------- ⊙ --------(:4/4,0)-------- ⊙ -------- ⊙ + a a b b a a + +Neither of these is ideal for use in an editor, where it is undesirable to send very long diffs to the +front end. It's tricky to decide exactly what 'very long diffs' means, as "replace A by B" is very short. +We want to control how big D can be, by stopping when it gets too large. The forward algorithm then +privileges common prefixes, and the backward algorithm privileges common suffixes. Either is an undesirable +asymmetry. + +Fortunately there is a two-sided algorithm, implied by results in Myers' paper. Here's what the labels in +the edit graph look like. +A:"aabbaa", B:"aacaba" + ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ + a | ____/‾‾‾‾ | ____/‾‾‾‾ | | | ____/‾‾‾‾ | ____/‾‾‾‾ | + ⊙ --------- ⊙ --------- ⊙ --------- (2/3,5) --------- ⊙ --------- (:0/5,5)--------- ⊙ + b | | | ____/‾‾‾‾ | ____/‾‾‾‾ | | | + ⊙ --------- ⊙ --------- ⊙ --------- (:1/3,4)--------- ⊙ --------- ⊙ --------- ⊙ + a | ____/‾‾‾‾ | ____/‾‾‾‾ | | | ____/‾‾‾‾ | ____/‾‾‾‾ | + ⊙ --------- (:2/1,3)--------- (1/2,3) ---------(2:2/3,3)--------- (:1/4,3)--------- ⊙ --------- ⊙ + c | | | | | | | + ⊙ --------- ⊙ --------- (0/2,2) --------- (1/3,2) ---------(2:2/4,2)--------- ⊙ --------- ⊙ + a | ____/‾‾‾‾ | ____/‾‾‾‾ | | | ____/‾‾‾‾ | ____/‾‾‾‾ | + ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ + a | ____/‾‾‾‾ | ____/‾‾‾‾ | | | ____/‾‾‾‾ | ____/‾‾‾‾ | + ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ + a a b b a a + +The algorithm stopped when it saw the backwards 2-path ending at (1,3) and the forwards 2-path ending at (3,5). The criterion +is a backwards path ending at (u,v) and a forward path ending at (x,y), where u <= x and the two points are on the same +diagonal. (Here the edgegraph has a diagonal, but the criterion is x-y=u-v.) Myers proves there is a forward +2-path from (0,0) to (1,3), and that together with the backwards 2-path ending at (1,3) gives the expected 4-path. +Unfortunately the forward path has to be constructed by another run of the forward algorithm; it can't be found from the +computed labels. That is the worst case. Had the code noticed (x,y)=(u,v)=(3,3) the whole path could be reconstructed +from the edgegraph. The implementation looks for a number of special cases to try to avoid computing an extra forward path. + +If the two-sided algorithm has stop early (because D has become too large) it will have found a forward LCS and a +backwards LCS. Ideally these go with disjoint prefixes and suffixes of A and B, but disjointedness may fail and the two +computed LCS may conflict. (An easy example is where A is a suffix of B, and shares a short prefix. The backwards LCS +is all of A, and the forward LCS is a prefix of A.) The algorithm combines the two +to form a best-effort LCS. In the worst case the forward partial LCS may have to +be recomputed. +*/ + +/* Eugene Myers paper is titled +"An O(ND) Difference Algorithm and Its Variations" +and can be found at +http://www.xmailserver.org/diff2.pdf + +(There is a generic implementation of the algorithm the repository with git hash +b9ad7e4ade3a686d608e44475390ad428e60e7fc) +*/ diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/git.sh b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/git.sh new file mode 100644 index 00000000000000..b25ba4aac74b81 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/git.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright 2022 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. +# +# Creates a zip file containing all numbered versions +# of the commit history of a large source file, for use +# as input data for the tests of the diff algorithm. +# +# Run script from root of the x/tools repo. + +set -eu + +# WARNING: This script will install the latest version of $file +# The largest real source file in the x/tools repo. +# file=internal/golang/completion/completion.go +# file=internal/golang/diagnostics.go +file=internal/protocol/tsprotocol.go + +tmp=$(mktemp -d) +git log $file | + awk '/^commit / {print $2}' | + nl -ba -nrz | + while read n hash; do + git checkout --quiet $hash $file + cp -f $file $tmp/$n + done +(cd $tmp && zip -q - *) > testdata.zip +rm -fr $tmp +git restore --staged $file +git restore $file +echo "Created testdata.zip" diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/labels.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/labels.go new file mode 100644 index 00000000000000..504913d1da3c0a --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/labels.go @@ -0,0 +1,55 @@ +// Copyright 2022 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 lcs + +import ( + "fmt" +) + +// For each D, vec[D] has length D+1, +// and the label for (D, k) is stored in vec[D][(D+k)/2]. +type label struct { + vec [][]int +} + +// Temporary checking DO NOT COMMIT true TO PRODUCTION CODE +const debug = false + +// debugging. check that the (d,k) pair is valid +// (that is, -d<=k<=d and d+k even) +func checkDK(D, k int) { + if k >= -D && k <= D && (D+k)%2 == 0 { + return + } + panic(fmt.Sprintf("out of range, d=%d,k=%d", D, k)) +} + +func (t *label) set(D, k, x int) { + if debug { + checkDK(D, k) + } + for len(t.vec) <= D { + t.vec = append(t.vec, nil) + } + if t.vec[D] == nil { + t.vec[D] = make([]int, D+1) + } + t.vec[D][(D+k)/2] = x // known that D+k is even +} + +func (t *label) get(d, k int) int { + if debug { + checkDK(d, k) + } + return int(t.vec[d][(d+k)/2]) +} + +func newtriang(limit int) label { + if limit < 100 { + // Preallocate if limit is not large. + return label{vec: make([][]int, limit)} + } + return label{} +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go new file mode 100644 index 00000000000000..4c346706a7566c --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go @@ -0,0 +1,478 @@ +// Copyright 2022 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 lcs + +// TODO(adonovan): remove unclear references to "old" in this package. + +import ( + "fmt" +) + +// A Diff is a replacement of a portion of A by a portion of B. +type Diff struct { + Start, End int // offsets of portion to delete in A + ReplStart, ReplEnd int // offset of replacement text in B +} + +// DiffStrings returns the differences between two strings. +// It does not respect rune boundaries. +func DiffStrings(a, b string) []Diff { return diff(stringSeqs{a, b}) } + +// DiffBytes returns the differences between two byte sequences. +// It does not respect rune boundaries. +func DiffBytes(a, b []byte) []Diff { return diff(bytesSeqs{a, b}) } + +// DiffRunes returns the differences between two rune sequences. +func DiffRunes(a, b []rune) []Diff { return diff(runesSeqs{a, b}) } + +func diff(seqs sequences) []Diff { + // A limit on how deeply the LCS algorithm should search. The value is just a guess. + const maxDiffs = 100 + diff, _ := compute(seqs, twosided, maxDiffs/2) + return diff +} + +// compute computes the list of differences between two sequences, +// along with the LCS. It is exercised directly by tests. +// The algorithm is one of {forward, backward, twosided}. +func compute(seqs sequences, algo func(*editGraph) lcs, limit int) ([]Diff, lcs) { + if limit <= 0 { + limit = 1 << 25 // effectively infinity + } + alen, blen := seqs.lengths() + g := &editGraph{ + seqs: seqs, + vf: newtriang(limit), + vb: newtriang(limit), + limit: limit, + ux: alen, + uy: blen, + delta: alen - blen, + } + lcs := algo(g) + diffs := lcs.toDiffs(alen, blen) + return diffs, lcs +} + +// editGraph carries the information for computing the lcs of two sequences. +type editGraph struct { + seqs sequences + vf, vb label // forward and backward labels + + limit int // maximal value of D + // the bounding rectangle of the current edit graph + lx, ly, ux, uy int + delta int // common subexpression: (ux-lx)-(uy-ly) +} + +// toDiffs converts an LCS to a list of edits. +func (lcs lcs) toDiffs(alen, blen int) []Diff { + var diffs []Diff + var pa, pb int // offsets in a, b + for _, l := range lcs { + if pa < l.X || pb < l.Y { + diffs = append(diffs, Diff{pa, l.X, pb, l.Y}) + } + pa = l.X + l.Len + pb = l.Y + l.Len + } + if pa < alen || pb < blen { + diffs = append(diffs, Diff{pa, alen, pb, blen}) + } + return diffs +} + +// --- FORWARD --- + +// fdone decides if the forward path has reached the upper right +// corner of the rectangle. If so, it also returns the computed lcs. +func (e *editGraph) fdone(D, k int) (bool, lcs) { + // x, y, k are relative to the rectangle + x := e.vf.get(D, k) + y := x - k + if x == e.ux && y == e.uy { + return true, e.forwardlcs(D, k) + } + return false, nil +} + +// run the forward algorithm, until success or up to the limit on D. +func forward(e *editGraph) lcs { + e.setForward(0, 0, e.lx) + if ok, ans := e.fdone(0, 0); ok { + return ans + } + // from D to D+1 + for D := range e.limit { + e.setForward(D+1, -(D + 1), e.getForward(D, -D)) + if ok, ans := e.fdone(D+1, -(D + 1)); ok { + return ans + } + e.setForward(D+1, D+1, e.getForward(D, D)+1) + if ok, ans := e.fdone(D+1, D+1); ok { + return ans + } + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get backwards + lookv := e.lookForward(k, e.getForward(D, k-1)+1) + lookh := e.lookForward(k, e.getForward(D, k+1)) + if lookv > lookh { + e.setForward(D+1, k, lookv) + } else { + e.setForward(D+1, k, lookh) + } + if ok, ans := e.fdone(D+1, k); ok { + return ans + } + } + } + // D is too large + // find the D path with maximal x+y inside the rectangle and + // use that to compute the found part of the lcs + kmax := -e.limit - 1 + diagmax := -1 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getForward(e.limit, k) + y := x - k + if x+y > diagmax && x <= e.ux && y <= e.uy { + diagmax, kmax = x+y, k + } + } + return e.forwardlcs(e.limit, kmax) +} + +// recover the lcs by backtracking from the farthest point reached +func (e *editGraph) forwardlcs(D, k int) lcs { + var ans lcs + for x := e.getForward(D, k); x != 0 || x-k != 0; { + if ok(D-1, k-1) && x-1 == e.getForward(D-1, k-1) { + // if (x-1,y) is labelled D-1, x--,D--,k--,continue + D, k, x = D-1, k-1, x-1 + continue + } else if ok(D-1, k+1) && x == e.getForward(D-1, k+1) { + // if (x,y-1) is labelled D-1, x, D--,k++, continue + D, k = D-1, k+1 + continue + } + // if (x-1,y-1)--(x,y) is a diagonal, prepend,x--,y--, continue + y := x - k + ans = ans.prepend(x+e.lx-1, y+e.ly-1) + x-- + } + return ans +} + +// start at (x,y), go up the diagonal as far as possible, +// and label the result with d +func (e *editGraph) lookForward(k, relx int) int { + rely := relx - k + x, y := relx+e.lx, rely+e.ly + if x < e.ux && y < e.uy { + x += e.seqs.commonPrefixLen(x, e.ux, y, e.uy) + } + return x +} + +func (e *editGraph) setForward(d, k, relx int) { + x := e.lookForward(k, relx) + e.vf.set(d, k, x-e.lx) +} + +func (e *editGraph) getForward(d, k int) int { + x := e.vf.get(d, k) + return x +} + +// --- BACKWARD --- + +// bdone decides if the backward path has reached the lower left corner +func (e *editGraph) bdone(D, k int) (bool, lcs) { + // x, y, k are relative to the rectangle + x := e.vb.get(D, k) + y := x - (k + e.delta) + if x == 0 && y == 0 { + return true, e.backwardlcs(D, k) + } + return false, nil +} + +// run the backward algorithm, until success or up to the limit on D. +// (used only by tests) +func backward(e *editGraph) lcs { + e.setBackward(0, 0, e.ux) + if ok, ans := e.bdone(0, 0); ok { + return ans + } + // from D to D+1 + for D := range e.limit { + e.setBackward(D+1, -(D + 1), e.getBackward(D, -D)-1) + if ok, ans := e.bdone(D+1, -(D + 1)); ok { + return ans + } + e.setBackward(D+1, D+1, e.getBackward(D, D)) + if ok, ans := e.bdone(D+1, D+1); ok { + return ans + } + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get wrong + lookv := e.lookBackward(k, e.getBackward(D, k-1)) + lookh := e.lookBackward(k, e.getBackward(D, k+1)-1) + if lookv < lookh { + e.setBackward(D+1, k, lookv) + } else { + e.setBackward(D+1, k, lookh) + } + if ok, ans := e.bdone(D+1, k); ok { + return ans + } + } + } + + // D is too large + // find the D path with minimal x+y inside the rectangle and + // use that to compute the part of the lcs found + kmax := -e.limit - 1 + diagmin := 1 << 25 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getBackward(e.limit, k) + y := x - (k + e.delta) + if x+y < diagmin && x >= 0 && y >= 0 { + diagmin, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no paths when limit=%d?", e.limit)) + } + return e.backwardlcs(e.limit, kmax) +} + +// recover the lcs by backtracking +func (e *editGraph) backwardlcs(D, k int) lcs { + var ans lcs + for x := e.getBackward(D, k); x != e.ux || x-(k+e.delta) != e.uy; { + if ok(D-1, k-1) && x == e.getBackward(D-1, k-1) { + // D--, k--, x unchanged + D, k = D-1, k-1 + continue + } else if ok(D-1, k+1) && x+1 == e.getBackward(D-1, k+1) { + // D--, k++, x++ + D, k, x = D-1, k+1, x+1 + continue + } + y := x - (k + e.delta) + ans = ans.append(x+e.lx, y+e.ly) + x++ + } + return ans +} + +// start at (x,y), go down the diagonal as far as possible, +func (e *editGraph) lookBackward(k, relx int) int { + rely := relx - (k + e.delta) // forward k = k + e.delta + x, y := relx+e.lx, rely+e.ly + if x > 0 && y > 0 { + x -= e.seqs.commonSuffixLen(0, x, 0, y) + } + return x +} + +// convert to rectangle, and label the result with d +func (e *editGraph) setBackward(d, k, relx int) { + x := e.lookBackward(k, relx) + e.vb.set(d, k, x-e.lx) +} + +func (e *editGraph) getBackward(d, k int) int { + x := e.vb.get(d, k) + return x +} + +// -- TWOSIDED --- + +func twosided(e *editGraph) lcs { + // The termination condition could be improved, as either the forward + // or backward pass could succeed before Myers' Lemma applies. + // Aside from questions of efficiency (is the extra testing cost-effective) + // this is more likely to matter when e.limit is reached. + e.setForward(0, 0, e.lx) + e.setBackward(0, 0, e.ux) + + // from D to D+1 + for D := range e.limit { + // just finished a backwards pass, so check + if got, ok := e.twoDone(D, D); ok { + return e.twolcs(D, D, got) + } + // do a forwards pass (D to D+1) + e.setForward(D+1, -(D + 1), e.getForward(D, -D)) + e.setForward(D+1, D+1, e.getForward(D, D)+1) + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get backwards + lookv := e.lookForward(k, e.getForward(D, k-1)+1) + lookh := e.lookForward(k, e.getForward(D, k+1)) + if lookv > lookh { + e.setForward(D+1, k, lookv) + } else { + e.setForward(D+1, k, lookh) + } + } + // just did a forward pass, so check + if got, ok := e.twoDone(D+1, D); ok { + return e.twolcs(D+1, D, got) + } + // do a backward pass, D to D+1 + e.setBackward(D+1, -(D + 1), e.getBackward(D, -D)-1) + e.setBackward(D+1, D+1, e.getBackward(D, D)) + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get wrong + lookv := e.lookBackward(k, e.getBackward(D, k-1)) + lookh := e.lookBackward(k, e.getBackward(D, k+1)-1) + if lookv < lookh { + e.setBackward(D+1, k, lookv) + } else { + e.setBackward(D+1, k, lookh) + } + } + } + + // D too large. combine a forward and backward partial lcs + // first, a forward one + kmax := -e.limit - 1 + diagmax := -1 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getForward(e.limit, k) + y := x - k + if x+y > diagmax && x <= e.ux && y <= e.uy { + diagmax, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no forward paths when limit=%d?", e.limit)) + } + lcs := e.forwardlcs(e.limit, kmax) + // now a backward one + // find the D path with minimal x+y inside the rectangle and + // use that to compute the lcs + diagmin := 1 << 25 // infinity + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getBackward(e.limit, k) + y := x - (k + e.delta) + if x+y < diagmin && x >= 0 && y >= 0 { + diagmin, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no backward paths when limit=%d?", e.limit)) + } + lcs = append(lcs, e.backwardlcs(e.limit, kmax)...) + // These may overlap (e.forwardlcs and e.backwardlcs return sorted lcs) + ans := lcs.fix() + return ans +} + +// Does Myers' Lemma apply? +func (e *editGraph) twoDone(df, db int) (int, bool) { + if (df+db+e.delta)%2 != 0 { + return 0, false // diagonals cannot overlap + } + kmin := max(-df, -db+e.delta) + kmax := db + e.delta + if df < kmax { + kmax = df + } + for k := kmin; k <= kmax; k += 2 { + x := e.vf.get(df, k) + u := e.vb.get(db, k-e.delta) + if u <= x { + // is it worth looking at all the other k? + for l := k; l <= kmax; l += 2 { + x := e.vf.get(df, l) + y := x - l + u := e.vb.get(db, l-e.delta) + v := u - l + if x == u || u == 0 || v == 0 || y == e.uy || x == e.ux { + return l, true + } + } + return k, true + } + } + return 0, false +} + +func (e *editGraph) twolcs(df, db, kf int) lcs { + // db==df || db+1==df + x := e.vf.get(df, kf) + y := x - kf + kb := kf - e.delta + u := e.vb.get(db, kb) + v := u - kf + + // Myers proved there is a df-path from (0,0) to (u,v) + // and a db-path from (x,y) to (N,M). + // In the first case the overall path is the forward path + // to (u,v) followed by the backward path to (N,M). + // In the second case the path is the backward path to (x,y) + // followed by the forward path to (x,y) from (0,0). + + // Look for some special cases to avoid computing either of these paths. + if x == u { + // "babaab" "cccaba" + // already patched together + lcs := e.forwardlcs(df, kf) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + + // is (u-1,v) or (u,v-1) labelled df-1? + // if so, that forward df-1-path plus a horizontal or vertical edge + // is the df-path to (u,v), then plus the db-path to (N,M) + if u > 0 && ok(df-1, u-1-v) && e.vf.get(df-1, u-1-v) == u-1 { + // "aabbab" "cbcabc" + lcs := e.forwardlcs(df-1, u-1-v) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + if v > 0 && ok(df-1, (u-(v-1))) && e.vf.get(df-1, u-(v-1)) == u { + // "abaabb" "bcacab" + lcs := e.forwardlcs(df-1, u-(v-1)) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + + // The path can't possibly contribute to the lcs because it + // is all horizontal or vertical edges + if u == 0 || v == 0 || x == e.ux || y == e.uy { + // "abaabb" "abaaaa" + if u == 0 || v == 0 { + return e.backwardlcs(db, kb) + } + return e.forwardlcs(df, kf) + } + + // is (x+1,y) or (x,y+1) labelled db-1? + if x+1 <= e.ux && ok(db-1, x+1-y-e.delta) && e.vb.get(db-1, x+1-y-e.delta) == x+1 { + // "bababb" "baaabb" + lcs := e.backwardlcs(db-1, kb+1) + lcs = append(lcs, e.forwardlcs(df, kf)...) + return lcs.sort() + } + if y+1 <= e.uy && ok(db-1, x-(y+1)-e.delta) && e.vb.get(db-1, x-(y+1)-e.delta) == x { + // "abbbaa" "cabacc" + lcs := e.backwardlcs(db-1, kb-1) + lcs = append(lcs, e.forwardlcs(df, kf)...) + return lcs.sort() + } + + // need to compute another path + // "aabbaa" "aacaba" + lcs := e.backwardlcs(db, kb) + oldx, oldy := e.ux, e.uy + e.ux = u + e.uy = v + lcs = append(lcs, forward(e)...) + e.ux, e.uy = oldx, oldy + return lcs.sort() +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/sequence.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/sequence.go new file mode 100644 index 00000000000000..2d72d2630435b8 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/sequence.go @@ -0,0 +1,113 @@ +// Copyright 2022 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 lcs + +// This file defines the abstract sequence over which the LCS algorithm operates. + +// sequences abstracts a pair of sequences, A and B. +type sequences interface { + lengths() (int, int) // len(A), len(B) + commonPrefixLen(ai, aj, bi, bj int) int // len(commonPrefix(A[ai:aj], B[bi:bj])) + commonSuffixLen(ai, aj, bi, bj int) int // len(commonSuffix(A[ai:aj], B[bi:bj])) +} + +type stringSeqs struct{ a, b string } + +func (s stringSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s stringSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenString(s.a[ai:aj], s.b[bi:bj]) +} +func (s stringSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenString(s.a[ai:aj], s.b[bi:bj]) +} + +// The explicit capacity in s[i:j:j] leads to more efficient code. + +type bytesSeqs struct{ a, b []byte } + +func (s bytesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s bytesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenBytes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} +func (s bytesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenBytes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} + +type runesSeqs struct{ a, b []rune } + +func (s runesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s runesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenRunes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} +func (s runesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenRunes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} + +// TODO(adonovan): optimize these functions using ideas from: +// - https://go.dev/cl/408116 common.go +// - https://go.dev/cl/421435 xor_generic.go + +// TODO(adonovan): factor using generics when available, +// but measure performance impact. + +// commonPrefixLen* returns the length of the common prefix of a[ai:aj] and b[bi:bj]. +func commonPrefixLenBytes(a, b []byte) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} +func commonPrefixLenRunes(a, b []rune) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} +func commonPrefixLenString(a, b string) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} + +// commonSuffixLen* returns the length of the common suffix of a[ai:aj] and b[bi:bj]. +func commonSuffixLenBytes(a, b []byte) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} +func commonSuffixLenRunes(a, b []rune) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} +func commonSuffixLenString(a, b string) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} + +func min(x, y int) int { + if x < y { + return x + } else { + return y + } +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/merge.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/merge.go new file mode 100644 index 00000000000000..eeae98adf76358 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/merge.go @@ -0,0 +1,81 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package diff + +import ( + "slices" +) + +// Merge merges two valid, ordered lists of edits. +// It returns zero if there was a conflict. +// +// If corresponding edits in x and y are identical, +// they are coalesced in the result. +// +// If x and y both provide different insertions at the same point, +// the insertions from x will be first in the result. +// +// TODO(adonovan): this algorithm could be improved, for example by +// working harder to coalesce non-identical edits that share a common +// deletion or common prefix of insertion (see the tests). +// Survey the academic literature for insights. +func Merge(x, y []Edit) ([]Edit, bool) { + // Make a defensive (premature) copy of the arrays. + x = slices.Clone(x) + y = slices.Clone(y) + + var merged []Edit + add := func(edit Edit) { + merged = append(merged, edit) + } + var xi, yi int + for xi < len(x) && yi < len(y) { + px := &x[xi] + py := &y[yi] + + if *px == *py { + // x and y are identical: coalesce. + add(*px) + xi++ + yi++ + + } else if px.End <= py.Start { + // x is entirely before y, + // or an insertion at start of y. + add(*px) + xi++ + + } else if py.End <= px.Start { + // y is entirely before x, + // or an insertion at start of x. + add(*py) + yi++ + + } else if px.Start < py.Start { + // x is partly before y: + // split it into a deletion and an edit. + add(Edit{px.Start, py.Start, ""}) + px.Start = py.Start + + } else if py.Start < px.Start { + // y is partly before x: + // split it into a deletion and an edit. + add(Edit{py.Start, px.Start, ""}) + py.Start = px.Start + + } else { + // x and y are unequal non-insertions + // at the same point: conflict. + return nil, false + } + } + for ; xi < len(x); xi++ { + add(x[xi]) + } + for ; yi < len(y); yi++ { + add(y[yi]) + } + return merged, true +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/ndiff.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/ndiff.go new file mode 100644 index 00000000000000..a2eef26ac77dc0 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/ndiff.go @@ -0,0 +1,99 @@ +// Copyright 2022 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 diff + +import ( + "bytes" + "unicode/utf8" + + "golang.org/x/tools/internal/diff/lcs" +) + +// Strings computes the differences between two strings. +// The resulting edits respect rune boundaries. +func Strings(before, after string) []Edit { + if before == after { + return nil // common case + } + + if isASCII(before) && isASCII(after) { + // TODO(adonovan): opt: specialize diffASCII for strings. + return diffASCII([]byte(before), []byte(after)) + } + return diffRunes([]rune(before), []rune(after)) +} + +// Bytes computes the differences between two byte slices. +// The resulting edits respect rune boundaries. +func Bytes(before, after []byte) []Edit { + if bytes.Equal(before, after) { + return nil // common case + } + + if isASCII(before) && isASCII(after) { + return diffASCII(before, after) + } + return diffRunes(runes(before), runes(after)) +} + +func diffASCII(before, after []byte) []Edit { + diffs := lcs.DiffBytes(before, after) + + // Convert from LCS diffs. + res := make([]Edit, len(diffs)) + for i, d := range diffs { + res[i] = Edit{d.Start, d.End, string(after[d.ReplStart:d.ReplEnd])} + } + return res +} + +func diffRunes(before, after []rune) []Edit { + diffs := lcs.DiffRunes(before, after) + + // The diffs returned by the lcs package use indexes + // into whatever slice was passed in. + // Convert rune offsets to byte offsets. + res := make([]Edit, len(diffs)) + lastEnd := 0 + utf8Len := 0 + for i, d := range diffs { + utf8Len += runesLen(before[lastEnd:d.Start]) // text between edits + start := utf8Len + utf8Len += runesLen(before[d.Start:d.End]) // text deleted by this edit + res[i] = Edit{start, utf8Len, string(after[d.ReplStart:d.ReplEnd])} + lastEnd = d.End + } + return res +} + +// runes is like []rune(string(bytes)) without the duplicate allocation. +func runes(bytes []byte) []rune { + n := utf8.RuneCount(bytes) + runes := make([]rune, n) + for i := range n { + r, sz := utf8.DecodeRune(bytes) + bytes = bytes[sz:] + runes[i] = r + } + return runes +} + +// runesLen returns the length in bytes of the UTF-8 encoding of runes. +func runesLen(runes []rune) (len int) { + for _, r := range runes { + len += utf8.RuneLen(r) + } + return len +} + +// isASCII reports whether s contains only ASCII. +func isASCII[S string | []byte](s S) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/unified.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/unified.go new file mode 100644 index 00000000000000..cfbda61020a0ea --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/unified.go @@ -0,0 +1,251 @@ +// Copyright 2019 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 diff + +import ( + "fmt" + "log" + "strings" +) + +// DefaultContextLines is the number of unchanged lines of surrounding +// context displayed by Unified. Use ToUnified to specify a different value. +const DefaultContextLines = 3 + +// Unified returns a unified diff of the old and new strings. +// The old and new labels are the names of the old and new files. +// If the strings are equal, it returns the empty string. +func Unified(oldLabel, newLabel, old, new string) string { + edits := Strings(old, new) + unified, err := ToUnified(oldLabel, newLabel, old, edits, DefaultContextLines) + if err != nil { + // Can't happen: edits are consistent. + log.Fatalf("internal error in diff.Unified: %v", err) + } + return unified +} + +// ToUnified applies the edits to content and returns a unified diff, +// with contextLines lines of (unchanged) context around each diff hunk. +// The old and new labels are the names of the content and result files. +// It returns an error if the edits are inconsistent; see ApplyEdits. +func ToUnified(oldLabel, newLabel, content string, edits []Edit, contextLines int) (string, error) { + u, err := toUnified(oldLabel, newLabel, content, edits, contextLines) + if err != nil { + return "", err + } + return u.String(), nil +} + +// unified represents a set of edits as a unified diff. +type unified struct { + // from is the name of the original file. + from string + // to is the name of the modified file. + to string + // hunks is the set of edit hunks needed to transform the file content. + hunks []*hunk +} + +// Hunk represents a contiguous set of line edits to apply. +type hunk struct { + // The line in the original source where the hunk starts. + fromLine int + // The line in the original source where the hunk finishes. + toLine int + // The set of line based edits to apply. + lines []line +} + +// Line represents a single line operation to apply as part of a Hunk. +type line struct { + // kind is the type of line this represents, deletion, insertion or copy. + kind opKind + // content is the content of this line. + // For deletion it is the line being removed, for all others it is the line + // to put in the output. + content string +} + +// opKind is used to denote the type of operation a line represents. +type opKind int + +const ( + // opDelete is the operation kind for a line that is present in the input + // but not in the output. + opDelete opKind = iota + // opInsert is the operation kind for a line that is new in the output. + opInsert + // opEqual is the operation kind for a line that is the same in the input and + // output, often used to provide context around edited lines. + opEqual +) + +// String returns a human readable representation of an OpKind. It is not +// intended for machine processing. +func (k opKind) String() string { + switch k { + case opDelete: + return "delete" + case opInsert: + return "insert" + case opEqual: + return "equal" + default: + panic("unknown operation kind") + } +} + +// toUnified takes a file contents and a sequence of edits, and calculates +// a unified diff that represents those edits. +func toUnified(fromName, toName string, content string, edits []Edit, contextLines int) (unified, error) { + gap := contextLines * 2 + u := unified{ + from: fromName, + to: toName, + } + if len(edits) == 0 { + return u, nil + } + var err error + edits, err = lineEdits(content, edits) // expand to whole lines + if err != nil { + return u, err + } + lines := splitLines(content) + var h *hunk + last := 0 + toLine := 0 + for _, edit := range edits { + // Compute the zero-based line numbers of the edit start and end. + // TODO(adonovan): opt: compute incrementally, avoid O(n^2). + start := strings.Count(content[:edit.Start], "\n") + end := strings.Count(content[:edit.End], "\n") + if edit.End == len(content) && len(content) > 0 && content[len(content)-1] != '\n' { + end++ // EOF counts as an implicit newline + } + + switch { + case h != nil && start == last: + //direct extension + case h != nil && start <= last+gap: + //within range of previous lines, add the joiners + addEqualLines(h, lines, last, start) + default: + //need to start a new hunk + if h != nil { + // add the edge to the previous hunk + addEqualLines(h, lines, last, last+contextLines) + u.hunks = append(u.hunks, h) + } + toLine += start - last + h = &hunk{ + fromLine: start + 1, + toLine: toLine + 1, + } + // add the edge to the new hunk + delta := addEqualLines(h, lines, start-contextLines, start) + h.fromLine -= delta + h.toLine -= delta + } + last = start + for i := start; i < end; i++ { + h.lines = append(h.lines, line{kind: opDelete, content: lines[i]}) + last++ + } + if edit.New != "" { + for _, content := range splitLines(edit.New) { + h.lines = append(h.lines, line{kind: opInsert, content: content}) + toLine++ + } + } + } + if h != nil { + // add the edge to the final hunk + addEqualLines(h, lines, last, last+contextLines) + u.hunks = append(u.hunks, h) + } + return u, nil +} + +func splitLines(text string) []string { + lines := strings.SplitAfter(text, "\n") + if lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + return lines +} + +func addEqualLines(h *hunk, lines []string, start, end int) int { + delta := 0 + for i := start; i < end; i++ { + if i < 0 { + continue + } + if i >= len(lines) { + return delta + } + h.lines = append(h.lines, line{kind: opEqual, content: lines[i]}) + delta++ + } + return delta +} + +// String converts a unified diff to the standard textual form for that diff. +// The output of this function can be passed to tools like patch. +func (u unified) String() string { + if len(u.hunks) == 0 { + return "" + } + b := new(strings.Builder) + fmt.Fprintf(b, "--- %s\n", u.from) + fmt.Fprintf(b, "+++ %s\n", u.to) + for _, hunk := range u.hunks { + fromCount, toCount := 0, 0 + for _, l := range hunk.lines { + switch l.kind { + case opDelete: + fromCount++ + case opInsert: + toCount++ + default: + fromCount++ + toCount++ + } + } + fmt.Fprint(b, "@@") + if fromCount > 1 { + fmt.Fprintf(b, " -%d,%d", hunk.fromLine, fromCount) + } else if hunk.fromLine == 1 && fromCount == 0 { + // Match odd GNU diff -u behavior adding to empty file. + fmt.Fprintf(b, " -0,0") + } else { + fmt.Fprintf(b, " -%d", hunk.fromLine) + } + if toCount > 1 { + fmt.Fprintf(b, " +%d,%d", hunk.toLine, toCount) + } else if hunk.toLine == 1 && toCount == 0 { + // Match odd GNU diff -u behavior adding to empty file. + fmt.Fprintf(b, " +0,0") + } else { + fmt.Fprintf(b, " +%d", hunk.toLine) + } + fmt.Fprint(b, " @@\n") + for _, l := range hunk.lines { + switch l.kind { + case opDelete: + fmt.Fprintf(b, "-%s", l.content) + case opInsert: + fmt.Fprintf(b, "+%s", l.content) + default: + fmt.Fprintf(b, " %s", l.content) + } + if !strings.HasSuffix(l.content, "\n") { + fmt.Fprintf(b, "\n\\ No newline at end of file\n") + } + } + } + return b.String() +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go index 77cf8d2181ae46..96ad6c582105e7 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go @@ -12,348 +12,354 @@ type pkginfo struct { } var deps = [...]pkginfo{ - {"archive/tar", "\x03j\x03E5\x01\v\x01#\x01\x01\x02\x05\n\x02\x01\x02\x02\v"}, - {"archive/zip", "\x02\x04`\a\x16\x0205\x01+\x05\x01\x11\x03\x02\r\x04"}, - {"bufio", "\x03j}F\x13"}, - {"bytes", "m+R\x03\fH\x02\x02"}, + {"archive/tar", "\x03k\x03E;\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"}, + {"archive/zip", "\x02\x04a\a\x03\x12\x021;\x01+\x05\x01\x0f\x03\x02\x0e\x04"}, + {"bufio", "\x03k\x83\x01D\x14"}, + {"bytes", "n*Y\x03\fG\x02\x02"}, {"cmp", ""}, - {"compress/bzip2", "\x02\x02\xe6\x01C"}, - {"compress/flate", "\x02k\x03z\r\x025\x01\x03"}, - {"compress/gzip", "\x02\x04`\a\x03\x15eU"}, - {"compress/lzw", "\x02k\x03z"}, - {"compress/zlib", "\x02\x04`\a\x03\x13\x01f"}, - {"container/heap", "\xae\x02"}, + {"compress/bzip2", "\x02\x02\xed\x01A"}, + {"compress/flate", "\x02l\x03\x80\x01\f\x033\x01\x03"}, + {"compress/gzip", "\x02\x04a\a\x03\x14lT"}, + {"compress/lzw", "\x02l\x03\x80\x01"}, + {"compress/zlib", "\x02\x04a\a\x03\x12\x01m"}, + {"container/heap", "\xb3\x02"}, {"container/list", ""}, {"container/ring", ""}, - {"context", "m\\i\x01\f"}, - {"crypto", "\x83\x01gE"}, - {"crypto/aes", "\x10\n\a\x8e\x02"}, - {"crypto/cipher", "\x03\x1e\x01\x01\x1d\x11\x1c,Q"}, - {"crypto/des", "\x10\x13\x1d-,\x96\x01\x03"}, - {"crypto/dsa", "@\x04)}\x0e"}, - {"crypto/ecdh", "\x03\v\f\x0e\x04\x14\x04\r\x1c}"}, - {"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x0e\x16\x01\x04\f\x01\x1c}\x0e\x04L\x01"}, - {"crypto/ed25519", "\x0e\x1c\x16\n\a\x1c}E"}, - {"crypto/elliptic", "0=}\x0e:"}, - {"crypto/fips140", " \x05\x90\x01"}, - {"crypto/hkdf", "-\x12\x01-\x16"}, - {"crypto/hmac", "\x1a\x14\x11\x01\x112"}, + {"context", "n\\m\x01\r"}, + {"crypto", "\x83\x01nC"}, + {"crypto/aes", "\x10\n\a\x93\x02"}, + {"crypto/cipher", "\x03\x1e\x01\x01\x1e\x11\x1c+X"}, + {"crypto/des", "\x10\x13\x1e-+\x9b\x01\x03"}, + {"crypto/dsa", "A\x04)\x83\x01\r"}, + {"crypto/ecdh", "\x03\v\f\x0e\x04\x15\x04\r\x1c\x83\x01"}, + {"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x0e\a\v\x05\x01\x04\f\x01\x1c\x83\x01\r\x05K\x01"}, + {"crypto/ed25519", "\x0e\x1c\x11\x06\n\a\x1c\x83\x01C"}, + {"crypto/elliptic", "0>\x83\x01\r9"}, + {"crypto/fips140", " \x05"}, + {"crypto/hkdf", "-\x13\x01-\x15"}, + {"crypto/hmac", "\x1a\x14\x12\x01\x111"}, {"crypto/internal/boring", "\x0e\x02\rf"}, - {"crypto/internal/boring/bbig", "\x1a\xde\x01M"}, - {"crypto/internal/boring/bcache", "\xb3\x02\x12"}, + {"crypto/internal/boring/bbig", "\x1a\xe4\x01M"}, + {"crypto/internal/boring/bcache", "\xb8\x02\x13"}, {"crypto/internal/boring/sig", ""}, - {"crypto/internal/cryptotest", "\x03\r\n)\x0e\x19\x06\x13\x12#\a\t\x11\x11\x11\x1b\x01\f\r\x05\n"}, - {"crypto/internal/entropy", "E"}, - {"crypto/internal/fips140", ">/}9\r\x15"}, - {"crypto/internal/fips140/aes", "\x03\x1d\x03\x02\x13\x04\x01\x01\x05*\x8c\x016"}, - {"crypto/internal/fips140/aes/gcm", " \x01\x02\x02\x02\x11\x04\x01\x06*\x8a\x01"}, - {"crypto/internal/fips140/alias", "\xc5\x02"}, - {"crypto/internal/fips140/bigmod", "%\x17\x01\x06*\x8c\x01"}, - {"crypto/internal/fips140/check", " \x0e\x06\b\x02\xac\x01["}, - {"crypto/internal/fips140/check/checktest", "%\xfe\x01\""}, - {"crypto/internal/fips140/drbg", "\x03\x1c\x01\x01\x04\x13\x04\b\x01(}\x0f9"}, - {"crypto/internal/fips140/ecdh", "\x03\x1d\x05\x02\t\f1}\x0f9"}, - {"crypto/internal/fips140/ecdsa", "\x03\x1d\x04\x01\x02\a\x02\x067}H"}, - {"crypto/internal/fips140/ed25519", "\x03\x1d\x05\x02\x04\v7\xc2\x01\x03"}, - {"crypto/internal/fips140/edwards25519", "%\a\f\x041\x8c\x019"}, - {"crypto/internal/fips140/edwards25519/field", "%\x13\x041\x8c\x01"}, - {"crypto/internal/fips140/hkdf", "\x03\x1d\x05\t\x069"}, - {"crypto/internal/fips140/hmac", "\x03\x1d\x14\x01\x017"}, - {"crypto/internal/fips140/mlkem", "\x03\x1d\x05\x02\x0e\x03\x041"}, - {"crypto/internal/fips140/nistec", "%\f\a\x041\x8c\x01*\x0f\x13"}, - {"crypto/internal/fips140/nistec/fiat", "%\x135\x8c\x01"}, - {"crypto/internal/fips140/pbkdf2", "\x03\x1d\x05\t\x069"}, - {"crypto/internal/fips140/rsa", "\x03\x1d\x04\x01\x02\r\x01\x01\x025}H"}, - {"crypto/internal/fips140/sha256", "\x03\x1d\x1c\x01\x06*\x8c\x01"}, - {"crypto/internal/fips140/sha3", "\x03\x1d\x18\x04\x010\x8c\x01L"}, - {"crypto/internal/fips140/sha512", "\x03\x1d\x1c\x01\x06*\x8c\x01"}, - {"crypto/internal/fips140/ssh", " \x05"}, - {"crypto/internal/fips140/subtle", "#"}, - {"crypto/internal/fips140/tls12", "\x03\x1d\x05\t\x06\x027"}, - {"crypto/internal/fips140/tls13", "\x03\x1d\x05\b\a\b1"}, + {"crypto/internal/cryptotest", "\x03\r\n\x06$\x0e\x19\x06\x12\x12 \x04\a\t\x16\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"}, + {"crypto/internal/entropy", "F"}, + {"crypto/internal/fips140", "?/\x15\xa7\x01\v\x16"}, + {"crypto/internal/fips140/aes", "\x03\x1d\x03\x02\x13\x05\x01\x01\x05*\x92\x014"}, + {"crypto/internal/fips140/aes/gcm", " \x01\x02\x02\x02\x11\x05\x01\x06*\x8f\x01"}, + {"crypto/internal/fips140/alias", "\xcb\x02"}, + {"crypto/internal/fips140/bigmod", "%\x18\x01\x06*\x92\x01"}, + {"crypto/internal/fips140/check", " \x0e\x06\t\x02\xb2\x01Z"}, + {"crypto/internal/fips140/check/checktest", "%\x85\x02!"}, + {"crypto/internal/fips140/drbg", "\x03\x1c\x01\x01\x04\x13\x05\b\x01(\x83\x01\x0f7"}, + {"crypto/internal/fips140/ecdh", "\x03\x1d\x05\x02\t\r1\x83\x01\x0f7"}, + {"crypto/internal/fips140/ecdsa", "\x03\x1d\x04\x01\x02\a\x02\x068\x15nF"}, + {"crypto/internal/fips140/ed25519", "\x03\x1d\x05\x02\x04\v8\xc6\x01\x03"}, + {"crypto/internal/fips140/edwards25519", "%\a\f\x051\x92\x017"}, + {"crypto/internal/fips140/edwards25519/field", "%\x13\x051\x92\x01"}, + {"crypto/internal/fips140/hkdf", "\x03\x1d\x05\t\x06:\x15"}, + {"crypto/internal/fips140/hmac", "\x03\x1d\x14\x01\x018\x15"}, + {"crypto/internal/fips140/mlkem", "\x03\x1d\x05\x02\x0e\x03\x051"}, + {"crypto/internal/fips140/nistec", "%\f\a\x051\x92\x01*\r\x14"}, + {"crypto/internal/fips140/nistec/fiat", "%\x136\x92\x01"}, + {"crypto/internal/fips140/pbkdf2", "\x03\x1d\x05\t\x06:\x15"}, + {"crypto/internal/fips140/rsa", "\x03\x1d\x04\x01\x02\r\x01\x01\x026\x15nF"}, + {"crypto/internal/fips140/sha256", "\x03\x1d\x1d\x01\x06*\x15}"}, + {"crypto/internal/fips140/sha3", "\x03\x1d\x18\x05\x010\x92\x01K"}, + {"crypto/internal/fips140/sha512", "\x03\x1d\x1d\x01\x06*\x15}"}, + {"crypto/internal/fips140/ssh", "%^"}, + {"crypto/internal/fips140/subtle", "#\x1a\xc3\x01"}, + {"crypto/internal/fips140/tls12", "\x03\x1d\x05\t\x06\x028\x15"}, + {"crypto/internal/fips140/tls13", "\x03\x1d\x05\b\a\t1\x15"}, + {"crypto/internal/fips140cache", "\xaa\x02\r&"}, {"crypto/internal/fips140deps", ""}, {"crypto/internal/fips140deps/byteorder", "\x99\x01"}, - {"crypto/internal/fips140deps/cpu", "\xad\x01\a"}, - {"crypto/internal/fips140deps/godebug", "\xb5\x01"}, - {"crypto/internal/fips140hash", "5\x1a4\xc2\x01"}, - {"crypto/internal/fips140only", "'\r\x01\x01M25"}, + {"crypto/internal/fips140deps/cpu", "\xae\x01\a"}, + {"crypto/internal/fips140deps/godebug", "\xb6\x01"}, + {"crypto/internal/fips140hash", "5\x1b3\xc8\x01"}, + {"crypto/internal/fips140only", "'\r\x01\x01M3;"}, {"crypto/internal/fips140test", ""}, - {"crypto/internal/hpke", "\x0e\x01\x01\x03\x1a\x1d#,`N"}, - {"crypto/internal/impl", "\xb0\x02"}, - {"crypto/internal/randutil", "\xea\x01\x12"}, - {"crypto/internal/sysrand", "mi!\x1f\r\x0f\x01\x01\v\x06"}, - {"crypto/internal/sysrand/internal/seccomp", "m"}, - {"crypto/md5", "\x0e2-\x16\x16`"}, + {"crypto/internal/hpke", "\x0e\x01\x01\x03\x053#+gM"}, + {"crypto/internal/impl", "\xb5\x02"}, + {"crypto/internal/randutil", "\xf1\x01\x12"}, + {"crypto/internal/sysrand", "nn! \r\r\x01\x01\f\x06"}, + {"crypto/internal/sysrand/internal/seccomp", "n"}, + {"crypto/md5", "\x0e3-\x15\x16g"}, {"crypto/mlkem", "/"}, - {"crypto/pbkdf2", "2\r\x01-\x16"}, - {"crypto/rand", "\x1a\x06\a\x19\x04\x01(}\x0eM"}, - {"crypto/rc4", "#\x1d-\xc2\x01"}, - {"crypto/rsa", "\x0e\f\x01\t\x0f\f\x01\x04\x06\a\x1c\x03\x1325\r\x01"}, - {"crypto/sha1", "\x0e\f&-\x16\x16\x14L"}, + {"crypto/pbkdf2", "2\x0e\x01-\x15"}, + {"crypto/rand", "\x1a\x06\a\x1a\x04\x01(\x83\x01\rM"}, + {"crypto/rc4", "#\x1e-\xc6\x01"}, + {"crypto/rsa", "\x0e\f\x01\t\x0f\r\x01\x04\x06\a\x1c\x03\x123;\f\x01"}, + {"crypto/sha1", "\x0e\f'\x03*\x15\x16\x15R"}, {"crypto/sha256", "\x0e\f\x1aO"}, - {"crypto/sha3", "\x0e'N\xc2\x01"}, + {"crypto/sha3", "\x0e'N\xc8\x01"}, {"crypto/sha512", "\x0e\f\x1cM"}, - {"crypto/subtle", "8\x96\x01U"}, - {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x03\x01\a\x01\v\x02\n\x01\b\x05\x03\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x13\x16\x14\b5\x16\x16\r\n\x01\x01\x01\x02\x01\f\x06\x02\x01"}, - {"crypto/tls/internal/fips140tls", " \x93\x02"}, - {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x011\x03\x02\x01\x01\x02\x05\x0e\x06\x02\x02\x03E\x032\x01\x02\t\x01\x01\x01\a\x10\x05\x01\x06\x02\x05\f\x01\x02\r\x02\x01\x01\x02\x03\x01"}, - {"crypto/x509/pkix", "c\x06\a\x88\x01G"}, - {"database/sql", "\x03\nJ\x16\x03z\f\x06\"\x05\n\x02\x03\x01\f\x02\x02\x02"}, - {"database/sql/driver", "\r`\x03\xae\x01\x11\x10"}, - {"debug/buildinfo", "\x03W\x02\x01\x01\b\a\x03`\x18\x02\x01+\x0f "}, - {"debug/dwarf", "\x03c\a\x03z1\x13\x01\x01"}, - {"debug/elf", "\x03\x06P\r\a\x03`\x19\x01,\x19\x01\x15"}, - {"debug/gosym", "\x03c\n\xbe\x01\x01\x01\x02"}, - {"debug/macho", "\x03\x06P\r\n`\x1a,\x19\x01"}, - {"debug/pe", "\x03\x06P\r\a\x03`\x1a,\x19\x01\x15"}, - {"debug/plan9obj", "f\a\x03`\x1a,"}, - {"embed", "m+:\x18\x01T"}, + {"crypto/subtle", "8\x9b\x01W"}, + {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\a\x01\r\n\x01\t\x05\x03\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x12\x16\x15\b;\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"}, + {"crypto/tls/internal/fips140tls", "\x17\xa1\x02"}, + {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x012\x05\x01\x01\x02\x05\x0e\x06\x02\x02\x03E\x038\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\x02\x05\n\x01\x02\x0e\x02\x01\x01\x02\x03\x01"}, + {"crypto/x509/pkix", "d\x06\a\x8d\x01G"}, + {"database/sql", "\x03\nK\x16\x03\x80\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"}, + {"database/sql/driver", "\ra\x03\xb4\x01\x0f\x11"}, + {"debug/buildinfo", "\x03X\x02\x01\x01\b\a\x03e\x19\x02\x01+\x0f\x1f"}, + {"debug/dwarf", "\x03d\a\x03\x80\x011\x11\x01\x01"}, + {"debug/elf", "\x03\x06Q\r\a\x03e\x1a\x01,\x17\x01\x16"}, + {"debug/gosym", "\x03d\n\xc2\x01\x01\x01\x02"}, + {"debug/macho", "\x03\x06Q\r\ne\x1b,\x17\x01"}, + {"debug/pe", "\x03\x06Q\r\a\x03e\x1b,\x17\x01\x16"}, + {"debug/plan9obj", "g\a\x03e\x1b,"}, + {"embed", "n*@\x19\x01S"}, {"embed/internal/embedtest", ""}, {"encoding", ""}, - {"encoding/ascii85", "\xea\x01E"}, - {"encoding/asn1", "\x03j\x03\x87\x01\x01&\x0f\x02\x01\x0f\x03\x01"}, - {"encoding/base32", "\xea\x01C\x02"}, - {"encoding/base64", "\x99\x01QC\x02"}, - {"encoding/binary", "m}\r'\x0f\x05"}, - {"encoding/csv", "\x02\x01j\x03zF\x11\x02"}, - {"encoding/gob", "\x02_\x05\a\x03`\x1a\f\x01\x02\x1d\b\x14\x01\x0e\x02"}, - {"encoding/hex", "m\x03zC\x03"}, - {"encoding/json", "\x03\x01]\x04\b\x03z\r'\x0f\x02\x01\x02\x0f\x01\x01\x02"}, - {"encoding/pem", "\x03b\b}C\x03"}, - {"encoding/xml", "\x02\x01^\f\x03z4\x05\f\x01\x02\x0f\x02"}, - {"errors", "\xc9\x01|"}, - {"expvar", "jK9\t\n\x15\r\n\x02\x03\x01\x10"}, - {"flag", "a\f\x03z,\b\x05\n\x02\x01\x0f"}, - {"fmt", "mE8\r\x1f\b\x0f\x02\x03\x11"}, - {"go/ast", "\x03\x01l\x0f\x01j\x03)\b\x0f\x02\x01"}, - {"go/ast/internal/tests", ""}, - {"go/build", "\x02\x01j\x03\x01\x03\x02\a\x02\x01\x17\x1e\x04\x02\t\x14\x12\x01+\x01\x04\x01\a\n\x02\x01\x11\x02\x02"}, - {"go/build/constraint", "m\xc2\x01\x01\x11\x02"}, - {"go/constant", "p\x10w\x01\x016\x01\x02\x11"}, - {"go/doc", "\x04l\x01\x06\t=-1\x12\x02\x01\x11\x02"}, - {"go/doc/comment", "\x03m\xbd\x01\x01\x01\x01\x11\x02"}, - {"go/format", "\x03m\x01\f\x01\x02jF"}, - {"go/importer", "s\a\x01\x01\x04\x01i9"}, - {"go/internal/gccgoimporter", "\x02\x01W\x13\x03\x05\v\x01g\x02,\x01\x05\x13\x01\v\b"}, - {"go/internal/gcimporter", "\x02n\x10\x01/\x05\x0e',\x17\x03\x02"}, - {"go/internal/srcimporter", "p\x01\x02\n\x03\x01i,\x01\x05\x14\x02\x13"}, - {"go/parser", "\x03j\x03\x01\x03\v\x01j\x01+\x06\x14"}, - {"go/printer", "p\x01\x03\x03\tj\r\x1f\x17\x02\x01\x02\n\x05\x02"}, - {"go/scanner", "\x03m\x10j2\x12\x01\x12\x02"}, - {"go/token", "\x04l\xbd\x01\x02\x03\x01\x0e\x02"}, - {"go/types", "\x03\x01\x06c\x03\x01\x04\b\x03\x02\x15\x1e\x06+\x04\x03\n%\a\n\x01\x01\x01\x02\x01\x0e\x02\x02"}, - {"go/version", "\xba\x01v"}, - {"hash", "\xea\x01"}, - {"hash/adler32", "m\x16\x16"}, - {"hash/crc32", "m\x16\x16\x14\x85\x01\x01\x12"}, - {"hash/crc64", "m\x16\x16\x99\x01"}, - {"hash/fnv", "m\x16\x16`"}, - {"hash/maphash", "\x94\x01\x05\x1b\x03@N"}, - {"html", "\xb0\x02\x02\x11"}, - {"html/template", "\x03g\x06\x19,5\x01\v \x05\x01\x02\x03\x0e\x01\x02\v\x01\x03\x02"}, - {"image", "\x02k\x1f^\x0f6\x03\x01"}, + {"encoding/ascii85", "\xf1\x01C"}, + {"encoding/asn1", "\x03k\x03\x8c\x01\x01'\r\x02\x01\x10\x03\x01"}, + {"encoding/base32", "\xf1\x01A\x02"}, + {"encoding/base64", "\x99\x01XA\x02"}, + {"encoding/binary", "n\x83\x01\f(\r\x05"}, + {"encoding/csv", "\x02\x01k\x03\x80\x01D\x12\x02"}, + {"encoding/gob", "\x02`\x05\a\x03e\x1b\v\x01\x03\x1d\b\x12\x01\x0f\x02"}, + {"encoding/hex", "n\x03\x80\x01A\x03"}, + {"encoding/json", "\x03\x01^\x04\b\x03\x80\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"}, + {"encoding/pem", "\x03c\b\x83\x01A\x03"}, + {"encoding/xml", "\x02\x01_\f\x03\x80\x014\x05\n\x01\x02\x10\x02"}, + {"errors", "\xca\x01\x81\x01"}, + {"expvar", "kK?\b\v\x15\r\b\x02\x03\x01\x11"}, + {"flag", "b\f\x03\x80\x01,\b\x05\b\x02\x01\x10"}, + {"fmt", "nE>\f \b\r\x02\x03\x12"}, + {"go/ast", "\x03\x01m\x0e\x01q\x03)\b\r\x02\x01"}, + {"go/build", "\x02\x01k\x03\x01\x02\x02\a\x02\x01\x17\x1f\x04\x02\t\x19\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"}, + {"go/build/constraint", "n\xc6\x01\x01\x12\x02"}, + {"go/constant", "q\x0f}\x01\x024\x01\x02\x12"}, + {"go/doc", "\x04m\x01\x05\t>31\x10\x02\x01\x12\x02"}, + {"go/doc/comment", "\x03n\xc1\x01\x01\x01\x01\x12\x02"}, + {"go/format", "\x03n\x01\v\x01\x02qD"}, + {"go/importer", "s\a\x01\x01\x04\x01p9"}, + {"go/internal/gccgoimporter", "\x02\x01X\x13\x03\x04\v\x01n\x02,\x01\x05\x11\x01\f\b"}, + {"go/internal/gcimporter", "\x02o\x0f\x010\x05\x0e-,\x15\x03\x02"}, + {"go/internal/srcimporter", "q\x01\x01\n\x03\x01p,\x01\x05\x12\x02\x14"}, + {"go/parser", "\x03k\x03\x01\x02\v\x01q\x01+\x06\x12"}, + {"go/printer", "q\x01\x02\x03\tq\f \x15\x02\x01\x02\v\x05\x02"}, + {"go/scanner", "\x03n\x0fq2\x10\x01\x13\x02"}, + {"go/token", "\x04m\x83\x01>\x02\x03\x01\x0f\x02"}, + {"go/types", "\x03\x01\x06d\x03\x01\x03\b\x03\x02\x15\x1f\x061\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"}, + {"go/version", "\xbb\x01z"}, + {"hash", "\xf1\x01"}, + {"hash/adler32", "n\x15\x16"}, + {"hash/crc32", "n\x15\x16\x15\x89\x01\x01\x13"}, + {"hash/crc64", "n\x15\x16\x9e\x01"}, + {"hash/fnv", "n\x15\x16g"}, + {"hash/maphash", "\x83\x01\x11!\x03\x93\x01"}, + {"html", "\xb5\x02\x02\x12"}, + {"html/template", "\x03h\x06\x18-;\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"}, + {"image", "\x02l\x1ee\x0f4\x03\x01"}, {"image/color", ""}, {"image/color/palette", "\x8c\x01"}, {"image/draw", "\x8b\x01\x01\x04"}, - {"image/gif", "\x02\x01\x05e\x03\x1b\x01\x01\x01\vQ"}, + {"image/gif", "\x02\x01\x05f\x03\x1a\x01\x01\x01\vX"}, {"image/internal/imageutil", "\x8b\x01"}, - {"image/jpeg", "\x02k\x1e\x01\x04Z"}, - {"image/png", "\x02\a]\n\x13\x02\x06\x01^E"}, - {"index/suffixarray", "\x03c\a}\r*\f\x01"}, - {"internal/abi", "\xb4\x01\x91\x01"}, - {"internal/asan", "\xc5\x02"}, - {"internal/bisect", "\xa3\x02\x0f\x01"}, - {"internal/buildcfg", "pG_\x06\x02\x05\f\x01"}, - {"internal/bytealg", "\xad\x01\x98\x01"}, + {"image/jpeg", "\x02l\x1d\x01\x04a"}, + {"image/png", "\x02\a^\n\x12\x02\x06\x01eC"}, + {"index/suffixarray", "\x03d\a\x83\x01\f+\n\x01"}, + {"internal/abi", "\xb5\x01\x96\x01"}, + {"internal/asan", "\xcb\x02"}, + {"internal/bisect", "\xaa\x02\r\x01"}, + {"internal/buildcfg", "qGe\x06\x02\x05\n\x01"}, + {"internal/bytealg", "\xae\x01\x9d\x01"}, {"internal/byteorder", ""}, {"internal/cfg", ""}, - {"internal/chacha8rand", "\x99\x01\x1b\x91\x01"}, + {"internal/cgrouptest", "q[Q\x06\x0f\x02\x01\x04\x01"}, + {"internal/chacha8rand", "\x99\x01\x15\a\x96\x01"}, {"internal/copyright", ""}, {"internal/coverage", ""}, {"internal/coverage/calloc", ""}, - {"internal/coverage/cfile", "j\x06\x17\x16\x01\x02\x01\x01\x01\x01\x01\x01\x01#\x01\x1f,\x06\a\f\x01\x03\f\x06"}, - {"internal/coverage/cformat", "\x04l-\x04I\f7\x01\x02\f"}, - {"internal/coverage/cmerge", "p-Z"}, - {"internal/coverage/decodecounter", "f\n-\v\x02@,\x19\x16"}, - {"internal/coverage/decodemeta", "\x02d\n\x17\x16\v\x02@,"}, - {"internal/coverage/encodecounter", "\x02d\n-\f\x01\x02>\f \x17"}, - {"internal/coverage/encodemeta", "\x02\x01c\n\x13\x04\x16\r\x02>,/"}, - {"internal/coverage/pods", "\x04l-y\x06\x05\f\x02\x01"}, - {"internal/coverage/rtcov", "\xc5\x02"}, - {"internal/coverage/slicereader", "f\nz["}, - {"internal/coverage/slicewriter", "pz"}, - {"internal/coverage/stringtab", "p8\x04>"}, + {"internal/coverage/cfile", "k\x06\x16\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01#\x02$,\x06\a\n\x01\x03\r\x06"}, + {"internal/coverage/cformat", "\x04m-\x04O\v6\x01\x02\r"}, + {"internal/coverage/cmerge", "q-_"}, + {"internal/coverage/decodecounter", "g\n-\v\x02F,\x17\x17"}, + {"internal/coverage/decodemeta", "\x02e\n\x16\x17\v\x02F,"}, + {"internal/coverage/encodecounter", "\x02e\n-\f\x01\x02D\v!\x15"}, + {"internal/coverage/encodemeta", "\x02\x01d\n\x12\x04\x17\r\x02D,."}, + {"internal/coverage/pods", "\x04m-\x7f\x06\x05\n\x02\x01"}, + {"internal/coverage/rtcov", "\xcb\x02"}, + {"internal/coverage/slicereader", "g\n\x80\x01Z"}, + {"internal/coverage/slicewriter", "q\x80\x01"}, + {"internal/coverage/stringtab", "q8\x04D"}, {"internal/coverage/test", ""}, {"internal/coverage/uleb128", ""}, - {"internal/cpu", "\xc5\x02"}, - {"internal/dag", "\x04l\xbd\x01\x03"}, - {"internal/diff", "\x03m\xbe\x01\x02"}, - {"internal/exportdata", "\x02\x01j\x03\x03]\x1a,\x01\x05\x13\x01\x02"}, - {"internal/filepathlite", "m+:\x19B"}, - {"internal/fmtsort", "\x04\x9a\x02\x0f"}, - {"internal/fuzz", "\x03\nA\x18\x04\x03\x03\x01\f\x0355\r\x02\x1d\x01\x05\x02\x05\f\x01\x02\x01\x01\v\x04\x02"}, + {"internal/cpu", "\xcb\x02"}, + {"internal/dag", "\x04m\xc1\x01\x03"}, + {"internal/diff", "\x03n\xc2\x01\x02"}, + {"internal/exportdata", "\x02\x01k\x03\x02c\x1b,\x01\x05\x11\x01\x02"}, + {"internal/filepathlite", "n*@\x1a@"}, + {"internal/fmtsort", "\x04\xa1\x02\r"}, + {"internal/fuzz", "\x03\nB\x18\x04\x03\x03\x01\v\x036;\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"}, {"internal/goarch", ""}, - {"internal/godebug", "\x96\x01 |\x01\x12"}, + {"internal/godebug", "\x96\x01!\x80\x01\x01\x13"}, {"internal/godebugs", ""}, {"internal/goexperiment", ""}, {"internal/goos", ""}, - {"internal/goroot", "\x96\x02\x01\x05\x14\x02"}, + {"internal/goroot", "\x9d\x02\x01\x05\x12\x02"}, {"internal/gover", "\x04"}, {"internal/goversion", ""}, {"internal/itoa", ""}, - {"internal/lazyregexp", "\x96\x02\v\x0f\x02"}, - {"internal/lazytemplate", "\xea\x01,\x1a\x02\v"}, - {"internal/msan", "\xc5\x02"}, + {"internal/lazyregexp", "\x9d\x02\v\r\x02"}, + {"internal/lazytemplate", "\xf1\x01,\x18\x02\f"}, + {"internal/msan", "\xcb\x02"}, {"internal/nettrace", ""}, - {"internal/obscuretestdata", "e\x85\x01,"}, - {"internal/oserror", "m"}, - {"internal/pkgbits", "\x03K\x18\a\x03\x05\vj\x0e\x1e\r\f\x01"}, + {"internal/obscuretestdata", "f\x8b\x01,"}, + {"internal/oserror", "n"}, + {"internal/pkgbits", "\x03L\x18\a\x03\x04\vq\r\x1f\r\n\x01"}, {"internal/platform", ""}, - {"internal/poll", "mO\x1a\x149\x0f\x01\x01\v\x06"}, - {"internal/profile", "\x03\x04f\x03z7\r\x01\x01\x0f"}, + {"internal/poll", "nO\x1f\x159\r\x01\x01\f\x06"}, + {"internal/profile", "\x03\x04g\x03\x80\x017\v\x01\x01\x10"}, {"internal/profilerecord", ""}, - {"internal/race", "\x94\x01\xb1\x01"}, - {"internal/reflectlite", "\x94\x01 3<\""}, - {"internal/runtime/atomic", "\xc5\x02"}, - {"internal/runtime/exithook", "\xca\x01{"}, - {"internal/runtime/maps", "\x94\x01\x01\x1f\v\t\x05\x01w"}, - {"internal/runtime/math", "\xb4\x01"}, - {"internal/runtime/sys", "\xb4\x01\x04"}, - {"internal/runtime/syscall", "\xc5\x02"}, - {"internal/saferio", "\xea\x01["}, - {"internal/singleflight", "\xb2\x02"}, - {"internal/stringslite", "\x98\x01\xad\x01"}, - {"internal/sync", "\x94\x01 \x14k\x12"}, - {"internal/synctest", "\xc5\x02"}, - {"internal/syscall/execenv", "\xb4\x02"}, - {"internal/syscall/unix", "\xa3\x02\x10\x01\x11"}, - {"internal/sysinfo", "\x02\x01\xaa\x01=,\x1a\x02"}, + {"internal/race", "\x94\x01\xb7\x01"}, + {"internal/reflectlite", "\x94\x01!9\b\x13\x01\a\x03E;\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\r\x02\x02\x02\b\x01\x01\x01"}, + {"net/http/cgi", "\x02Q\x1b\x03\x80\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x10\x0e"}, + {"net/http/cookiejar", "\x04j\x03\x96\x01\x01\b\f\x16\x03\x02\x0e\x04"}, + {"net/http/fcgi", "\x02\x01\nZ\a\x03\x80\x01\x16\x01\x01\x14\x18\x02\x0e"}, + {"net/http/httptest", "\x02\x01\nF\x02\x1b\x01\x80\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0e\x0e"}, + {"net/http/httptrace", "\rFnF\x14\n "}, + {"net/http/httputil", "\x02\x01\na\x03\x80\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x0e\x0e"}, + {"net/http/internal", "\x02\x01k\x03\x80\x01"}, + {"net/http/internal/ascii", "\xb5\x02\x12"}, + {"net/http/internal/httpcommon", "\ra\x03\x9c\x01\x0e\x01\x17\x01\x01\x02\x1c\x02"}, + {"net/http/internal/testcert", "\xb5\x02"}, + {"net/http/pprof", "\x02\x01\nd\x18-\x11*\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x10"}, {"net/internal/cgotest", ""}, - {"net/internal/socktest", "p\xc2\x01\x02"}, - {"net/mail", "\x02k\x03z\x04\x0f\x03\x14\x1c\x02\r\x04"}, - {"net/netip", "\x04i+\x01#;\x026\x15"}, - {"net/rpc", "\x02f\x05\x03\x10\n`\x04\x12\x01\x1d\x0f\x03\x02"}, - {"net/rpc/jsonrpc", "j\x03\x03z\x16\x11!"}, - {"net/smtp", "\x19.\v\x13\b\x03z\x16\x14\x1c"}, - {"net/textproto", "\x02\x01j\x03z\r\t/\x01\x02\x13"}, - {"net/url", "m\x03\x86\x01%\x12\x02\x01\x15"}, - {"os", "m+\x01\x18\x03\b\t\r\x03\x01\x04\x10\x018\n\x05\x01\x01\v\x06"}, - {"os/exec", "\x03\n`H \x01\x14\x01+\x06\a\f\x01\x04\v"}, - {"os/exec/internal/fdtest", "\xb4\x02"}, - {"os/signal", "\r\x89\x02\x17\x05\x02"}, - {"os/user", "\x02\x01j\x03z,\r\f\x01\x02"}, - {"path", "m+\xab\x01"}, - {"path/filepath", "m+\x19:+\r\n\x03\x04\x0f"}, - {"plugin", "m"}, - {"reflect", "m'\x04\x1c\b\f\x04\x02\x19\x10,\f\x03\x0f\x02\x02"}, + {"net/internal/socktest", "q\xc6\x01\x02"}, + {"net/mail", "\x02l\x03\x80\x01\x04\x0f\x03\x14\x1a\x02\x0e\x04"}, + {"net/netip", "\x04j*\x01$@\x034\x16"}, + {"net/rpc", "\x02g\x05\x03\x0f\ng\x04\x12\x01\x1d\r\x03\x02"}, + {"net/rpc/jsonrpc", "k\x03\x03\x80\x01\x16\x11\x1f"}, + {"net/smtp", "\x19/\v\x13\b\x03\x80\x01\x16\x14\x1a"}, + {"net/textproto", "\x02\x01k\x03\x80\x01\f\n-\x01\x02\x14"}, + {"net/url", "n\x03\x8b\x01&\x10\x02\x01\x16"}, + {"os", "n*\x01\x19\x03\b\t\x12\x03\x01\x05\x10\x018\b\x05\x01\x01\f\x06"}, + {"os/exec", "\x03\naH%\x01\x15\x01+\x06\a\n\x01\x04\f"}, + {"os/exec/internal/fdtest", "\xb9\x02"}, + {"os/signal", "\r\x90\x02\x15\x05\x02"}, + {"os/user", "\x02\x01k\x03\x80\x01,\r\n\x01\x02"}, + {"path", "n*\xb1\x01"}, + {"path/filepath", "n*\x1a@+\r\b\x03\x04\x10"}, + {"plugin", "n"}, + {"reflect", "n&\x04\x1d\b\f\x06\x04\x1b\x06\t-\n\x03\x10\x02\x02"}, {"reflect/internal/example1", ""}, {"reflect/internal/example2", ""}, - {"regexp", "\x03\xe7\x018\v\x02\x01\x02\x0f\x02"}, - {"regexp/syntax", "\xad\x02\x01\x01\x01\x11\x02"}, - {"runtime", "\x94\x01\x04\x01\x02\f\x06\a\x02\x01\x01\x0f\x03\x01\x01\x01\x01\x01\x03\x0fd"}, - {"runtime/coverage", "\x9f\x01K"}, - {"runtime/debug", "pUQ\r\n\x02\x01\x0f\x06"}, - {"runtime/internal/startlinetest", ""}, - {"runtime/internal/wasitest", ""}, - {"runtime/metrics", "\xb6\x01A,\""}, - {"runtime/pprof", "\x02\x01\x01\x03\x06Y\a\x03$3#\r\x1f\r\n\x01\x01\x01\x02\x02\b\x03\x06"}, - {"runtime/race", "\xab\x02"}, + {"regexp", "\x03\xee\x018\t\x02\x01\x02\x10\x02"}, + {"regexp/syntax", "\xb2\x02\x01\x01\x01\x02\x10\x02"}, + {"runtime", "\x94\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0f\x03\x01\x01\x01\x01\x01\x02\x01\x01\x04\x10c"}, + {"runtime/coverage", "\xa0\x01Q"}, + {"runtime/debug", "qUW\r\b\x02\x01\x10\x06"}, + {"runtime/metrics", "\xb7\x01F-!"}, + {"runtime/pprof", "\x02\x01\x01\x03\x06Z\a\x03#4)\f \r\b\x01\x01\x01\x02\x02\t\x03\x06"}, + {"runtime/race", "\xb0\x02"}, {"runtime/race/internal/amd64v1", ""}, - {"runtime/trace", "\rcz9\x0f\x01\x12"}, - {"slices", "\x04\xe9\x01\fL"}, - {"sort", "\xc9\x0104"}, - {"strconv", "m+:%\x02J"}, - {"strings", "m'\x04:\x18\x03\f9\x0f\x02\x02"}, + {"runtime/trace", "\ra\x03w\t9\b\x05\x01\r\x06"}, + {"slices", "\x04\xf0\x01\fK"}, + {"sort", "\xca\x0162"}, + {"strconv", "n*@%\x03I"}, + {"strings", "n&\x04@\x19\x03\f7\x10\x02\x02"}, {"structs", ""}, - {"sync", "\xc8\x01\vP\x10\x12"}, - {"sync/atomic", "\xc5\x02"}, - {"syscall", "m(\x03\x01\x1b\b\x03\x03\x06\aT\n\x05\x01\x12"}, - {"testing", "\x03\n`\x02\x01X\x0f\x13\r\x04\x1b\x06\x02\x05\x02\a\x01\x02\x01\x02\x01\f\x02\x02\x02"}, - {"testing/fstest", "m\x03z\x01\v%\x12\x03\b\a"}, - {"testing/internal/testdeps", "\x02\v\xa6\x01'\x10,\x03\x05\x03\b\a\x02\r"}, - {"testing/iotest", "\x03j\x03z\x04"}, - {"testing/quick", "o\x01\x87\x01\x04#\x12\x0f"}, - {"testing/slogtest", "\r`\x03\x80\x01.\x05\x12\n"}, - {"text/scanner", "\x03mz,+\x02"}, - {"text/tabwriter", "pzY"}, - {"text/template", "m\x03B8\x01\v\x1f\x01\x05\x01\x02\x05\r\x02\f\x03\x02"}, - {"text/template/parse", "\x03m\xb3\x01\f\x01\x11\x02"}, - {"time", "m+\x1d\x1d'*\x0f\x02\x11"}, - {"time/tzdata", "m\xc7\x01\x11"}, + {"sync", "\xc9\x01\x10\x01P\x0e\x13"}, + {"sync/atomic", "\xcb\x02"}, + {"syscall", "n'\x03\x01\x1c\b\x03\x03\x06\vV\b\x05\x01\x13"}, + {"testing", "\x03\na\x02\x01X\x14\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\r\x02\x02\x02"}, + {"testing/fstest", "n\x03\x80\x01\x01\n&\x10\x03\b\b"}, + {"testing/internal/testdeps", "\x02\v\xa7\x01-\x10,\x03\x05\x03\x06\a\x02\x0e"}, + {"testing/iotest", "\x03k\x03\x80\x01\x04"}, + {"testing/quick", "p\x01\x8c\x01\x05#\x10\x10"}, + {"testing/slogtest", "\ra\x03\x86\x01.\x05\x10\v"}, + {"testing/synctest", "\xda\x01`\x11"}, + {"text/scanner", "\x03n\x80\x01,*\x02"}, + {"text/tabwriter", "q\x80\x01X"}, + {"text/template", "n\x03B>\x01\n \x01\x05\x01\x02\x05\v\x02\r\x03\x02"}, + {"text/template/parse", "\x03n\xb9\x01\n\x01\x12\x02"}, + {"time", "n*\x1e\"(*\r\x02\x12"}, + {"time/tzdata", "n\xcb\x01\x12"}, {"unicode", ""}, {"unicode/utf16", ""}, {"unicode/utf8", ""}, - {"unique", "\x94\x01>\x01P\x0f\x13\x12"}, + {"unique", "\x94\x01!#\x01Q\r\x01\x13\x12"}, {"unsafe", ""}, - {"vendor/golang.org/x/crypto/chacha20", "\x10V\a\x8c\x01*'"}, - {"vendor/golang.org/x/crypto/chacha20poly1305", "\x10V\a\xd9\x01\x04\x01\a"}, - {"vendor/golang.org/x/crypto/cryptobyte", "c\n\x03\x88\x01&!\n"}, + {"vendor/golang.org/x/crypto/chacha20", "\x10W\a\x92\x01*&"}, + {"vendor/golang.org/x/crypto/chacha20poly1305", "\x10W\a\xde\x01\x04\x01\a"}, + {"vendor/golang.org/x/crypto/cryptobyte", "d\n\x03\x8d\x01' \n"}, {"vendor/golang.org/x/crypto/cryptobyte/asn1", ""}, - {"vendor/golang.org/x/crypto/internal/alias", "\xc5\x02"}, - {"vendor/golang.org/x/crypto/internal/poly1305", "Q\x15\x93\x01"}, - {"vendor/golang.org/x/net/dns/dnsmessage", "m"}, - {"vendor/golang.org/x/net/http/httpguts", "\x80\x02\x14\x1c\x13\r"}, - {"vendor/golang.org/x/net/http/httpproxy", "m\x03\x90\x01\x15\x01\x1a\x13\r"}, - {"vendor/golang.org/x/net/http2/hpack", "\x03j\x03zH"}, - {"vendor/golang.org/x/net/idna", "p\x87\x019\x13\x10\x02\x01"}, - {"vendor/golang.org/x/net/nettest", "\x03c\a\x03z\x11\x05\x16\x01\f\f\x01\x02\x02\x01\n"}, - {"vendor/golang.org/x/sys/cpu", "\x96\x02\r\f\x01\x15"}, - {"vendor/golang.org/x/text/secure/bidirule", "m\xd6\x01\x11\x01"}, - {"vendor/golang.org/x/text/transform", "\x03j}Y"}, - {"vendor/golang.org/x/text/unicode/bidi", "\x03\be~@\x15"}, - {"vendor/golang.org/x/text/unicode/norm", "f\nzH\x11\x11"}, - {"weak", "\x94\x01\x8f\x01\""}, + {"vendor/golang.org/x/crypto/internal/alias", "\xcb\x02"}, + {"vendor/golang.org/x/crypto/internal/poly1305", "R\x15\x99\x01"}, + {"vendor/golang.org/x/net/dns/dnsmessage", "n"}, + {"vendor/golang.org/x/net/http/httpguts", "\x87\x02\x14\x1a\x14\r"}, + {"vendor/golang.org/x/net/http/httpproxy", "n\x03\x96\x01\x10\x05\x01\x18\x14\r"}, + {"vendor/golang.org/x/net/http2/hpack", "\x03k\x03\x80\x01F"}, + {"vendor/golang.org/x/net/idna", "q\x8c\x018\x14\x10\x02\x01"}, + {"vendor/golang.org/x/net/nettest", "\x03d\a\x03\x80\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\v"}, + {"vendor/golang.org/x/sys/cpu", "\x9d\x02\r\n\x01\x16"}, + {"vendor/golang.org/x/text/secure/bidirule", "n\xdb\x01\x11\x01"}, + {"vendor/golang.org/x/text/transform", "\x03k\x83\x01X"}, + {"vendor/golang.org/x/text/unicode/bidi", "\x03\bf\x84\x01>\x16"}, + {"vendor/golang.org/x/text/unicode/norm", "g\n\x80\x01F\x12\x11"}, + {"weak", "\x94\x01\x96\x01!"}, } diff --git a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go index 64f0326b644d99..c1faa50d367c19 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go @@ -502,6 +502,7 @@ var PackageSymbols = map[string][]Symbol{ {"MD4", Const, 0, ""}, {"MD5", Const, 0, ""}, {"MD5SHA1", Const, 0, ""}, + {"MessageSigner", Type, 25, ""}, {"PrivateKey", Type, 0, ""}, {"PublicKey", Type, 2, ""}, {"RIPEMD160", Const, 0, ""}, @@ -517,6 +518,7 @@ var PackageSymbols = map[string][]Symbol{ {"SHA512", Const, 0, ""}, {"SHA512_224", Const, 5, ""}, {"SHA512_256", Const, 5, ""}, + {"SignMessage", Func, 25, "func(signer Signer, rand io.Reader, msg []byte, opts SignerOpts) (signature []byte, err error)"}, {"Signer", Type, 4, ""}, {"SignerOpts", Type, 4, ""}, }, @@ -600,10 +602,12 @@ var PackageSymbols = map[string][]Symbol{ {"X25519", Func, 20, "func() Curve"}, }, "crypto/ecdsa": { + {"(*PrivateKey).Bytes", Method, 25, ""}, {"(*PrivateKey).ECDH", Method, 20, ""}, {"(*PrivateKey).Equal", Method, 15, ""}, {"(*PrivateKey).Public", Method, 4, ""}, {"(*PrivateKey).Sign", Method, 4, ""}, + {"(*PublicKey).Bytes", Method, 25, ""}, {"(*PublicKey).ECDH", Method, 20, ""}, {"(*PublicKey).Equal", Method, 15, ""}, {"(PrivateKey).Add", Method, 0, ""}, @@ -619,6 +623,8 @@ var PackageSymbols = map[string][]Symbol{ {"(PublicKey).ScalarBaseMult", Method, 0, ""}, {"(PublicKey).ScalarMult", Method, 0, ""}, {"GenerateKey", Func, 0, "func(c elliptic.Curve, rand io.Reader) (*PrivateKey, error)"}, + {"ParseRawPrivateKey", Func, 25, "func(curve elliptic.Curve, data []byte) (*PrivateKey, error)"}, + {"ParseUncompressedPublicKey", Func, 25, "func(curve elliptic.Curve, data []byte) (*PublicKey, error)"}, {"PrivateKey", Type, 0, ""}, {"PrivateKey.D", Field, 0, ""}, {"PrivateKey.PublicKey", Field, 0, ""}, @@ -815,6 +821,7 @@ var PackageSymbols = map[string][]Symbol{ "crypto/sha3": { {"(*SHA3).AppendBinary", Method, 24, ""}, {"(*SHA3).BlockSize", Method, 24, ""}, + {"(*SHA3).Clone", Method, 25, ""}, {"(*SHA3).MarshalBinary", Method, 24, ""}, {"(*SHA3).Reset", Method, 24, ""}, {"(*SHA3).Size", Method, 24, ""}, @@ -967,6 +974,7 @@ var PackageSymbols = map[string][]Symbol{ {"Config.GetCertificate", Field, 4, ""}, {"Config.GetClientCertificate", Field, 8, ""}, {"Config.GetConfigForClient", Field, 8, ""}, + {"Config.GetEncryptedClientHelloKeys", Field, 25, ""}, {"Config.InsecureSkipVerify", Field, 0, ""}, {"Config.KeyLogWriter", Field, 8, ""}, {"Config.MaxVersion", Field, 2, ""}, @@ -5463,6 +5471,7 @@ var PackageSymbols = map[string][]Symbol{ {"ParenExpr.X", Field, 0, ""}, {"Pkg", Const, 0, ""}, {"Preorder", Func, 23, "func(root Node) iter.Seq[Node]"}, + {"PreorderStack", Func, 25, "func(root Node, stack []Node, f func(n Node, stack []Node) bool)"}, {"Print", Func, 0, "func(fset *token.FileSet, x any) error"}, {"RECV", Const, 0, ""}, {"RangeStmt", Type, 0, ""}, @@ -5933,6 +5942,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*File).SetLines", Method, 0, ""}, {"(*File).SetLinesForContent", Method, 0, ""}, {"(*File).Size", Method, 0, ""}, + {"(*FileSet).AddExistingFiles", Method, 25, ""}, {"(*FileSet).AddFile", Method, 0, ""}, {"(*FileSet).Base", Method, 0, ""}, {"(*FileSet).File", Method, 0, ""}, @@ -6382,7 +6392,7 @@ var PackageSymbols = map[string][]Symbol{ {"Label", Type, 5, ""}, {"LocalVar", Const, 25, ""}, {"LookupFieldOrMethod", Func, 5, "func(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool)"}, - {"LookupSelection", Func, 25, ""}, + {"LookupSelection", Func, 25, "func(T Type, addressable bool, pkg *Package, name string) (Selection, bool)"}, {"Map", Type, 5, ""}, {"MethodExpr", Const, 5, ""}, {"MethodSet", Type, 5, ""}, @@ -6490,9 +6500,11 @@ var PackageSymbols = map[string][]Symbol{ {"Lang", Func, 22, "func(x string) string"}, }, "hash": { + {"Cloner", Type, 25, ""}, {"Hash", Type, 0, ""}, {"Hash32", Type, 0, ""}, {"Hash64", Type, 0, ""}, + {"XOF", Type, 25, ""}, }, "hash/adler32": { {"Checksum", Func, 0, "func(data []byte) uint32"}, @@ -6533,6 +6545,7 @@ var PackageSymbols = map[string][]Symbol{ }, "hash/maphash": { {"(*Hash).BlockSize", Method, 14, ""}, + {"(*Hash).Clone", Method, 25, ""}, {"(*Hash).Reset", Method, 14, ""}, {"(*Hash).Seed", Method, 14, ""}, {"(*Hash).SetSeed", Method, 14, ""}, @@ -7133,7 +7146,7 @@ var PackageSymbols = map[string][]Symbol{ {"FormatFileInfo", Func, 21, "func(info FileInfo) string"}, {"Glob", Func, 16, "func(fsys FS, pattern string) (matches []string, err error)"}, {"GlobFS", Type, 16, ""}, - {"Lstat", Func, 25, ""}, + {"Lstat", Func, 25, "func(fsys FS, name string) (FileInfo, error)"}, {"ModeAppend", Const, 16, ""}, {"ModeCharDevice", Const, 16, ""}, {"ModeDevice", Const, 16, ""}, @@ -7158,7 +7171,7 @@ var PackageSymbols = map[string][]Symbol{ {"ReadDirFile", Type, 16, ""}, {"ReadFile", Func, 16, "func(fsys FS, name string) ([]byte, error)"}, {"ReadFileFS", Type, 16, ""}, - {"ReadLink", Func, 25, ""}, + {"ReadLink", Func, 25, "func(fsys FS, name string) (string, error)"}, {"ReadLinkFS", Type, 25, ""}, {"SkipAll", Var, 20, ""}, {"SkipDir", Var, 16, ""}, @@ -7275,6 +7288,7 @@ var PackageSymbols = map[string][]Symbol{ {"(Record).Attrs", Method, 21, ""}, {"(Record).Clone", Method, 21, ""}, {"(Record).NumAttrs", Method, 21, ""}, + {"(Record).Source", Method, 25, ""}, {"(Value).Any", Method, 21, ""}, {"(Value).Bool", Method, 21, ""}, {"(Value).Duration", Method, 21, ""}, @@ -7306,6 +7320,7 @@ var PackageSymbols = map[string][]Symbol{ {"Float64", Func, 21, "func(key string, v float64) Attr"}, {"Float64Value", Func, 21, "func(v float64) Value"}, {"Group", Func, 21, "func(key string, args ...any) Attr"}, + {"GroupAttrs", Func, 25, "func(key string, attrs ...Attr) Attr"}, {"GroupValue", Func, 21, "func(as ...Attr) Value"}, {"Handler", Type, 21, ""}, {"HandlerOptions", Type, 21, ""}, @@ -7916,7 +7931,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Writer).WriteField", Method, 0, ""}, {"ErrMessageTooLarge", Var, 9, ""}, {"File", Type, 0, ""}, - {"FileContentDisposition", Func, 25, ""}, + {"FileContentDisposition", Func, 25, "func(fieldname string, filename string) string"}, {"FileHeader", Type, 0, ""}, {"FileHeader.Filename", Field, 0, ""}, {"FileHeader.Header", Field, 0, ""}, @@ -8294,6 +8309,11 @@ var PackageSymbols = map[string][]Symbol{ {"(*Client).PostForm", Method, 0, ""}, {"(*Cookie).String", Method, 0, ""}, {"(*Cookie).Valid", Method, 18, ""}, + {"(*CrossOriginProtection).AddInsecureBypassPattern", Method, 25, ""}, + {"(*CrossOriginProtection).AddTrustedOrigin", Method, 25, ""}, + {"(*CrossOriginProtection).Check", Method, 25, ""}, + {"(*CrossOriginProtection).Handler", Method, 25, ""}, + {"(*CrossOriginProtection).SetDenyHandler", Method, 25, ""}, {"(*MaxBytesError).Error", Method, 19, ""}, {"(*ProtocolError).Error", Method, 0, ""}, {"(*ProtocolError).Is", Method, 21, ""}, @@ -8388,6 +8408,7 @@ var PackageSymbols = map[string][]Symbol{ {"Cookie.Unparsed", Field, 0, ""}, {"Cookie.Value", Field, 0, ""}, {"CookieJar", Type, 0, ""}, + {"CrossOriginProtection", Type, 25, ""}, {"DefaultClient", Var, 0, ""}, {"DefaultMaxHeaderBytes", Const, 0, ""}, {"DefaultMaxIdleConnsPerHost", Const, 0, ""}, @@ -8460,6 +8481,7 @@ var PackageSymbols = map[string][]Symbol{ {"MethodPost", Const, 6, ""}, {"MethodPut", Const, 6, ""}, {"MethodTrace", Const, 6, ""}, + {"NewCrossOriginProtection", Func, 25, "func() *CrossOriginProtection"}, {"NewFileTransport", Func, 0, "func(fs FileSystem) RoundTripper"}, {"NewFileTransportFS", Func, 22, "func(fsys fs.FS) RoundTripper"}, {"NewRequest", Func, 0, "func(method string, url string, body io.Reader) (*Request, error)"}, @@ -9174,15 +9196,19 @@ var PackageSymbols = map[string][]Symbol{ {"(*Root).Link", Method, 25, ""}, {"(*Root).Lstat", Method, 24, ""}, {"(*Root).Mkdir", Method, 24, ""}, + {"(*Root).MkdirAll", Method, 25, ""}, {"(*Root).Name", Method, 24, ""}, {"(*Root).Open", Method, 24, ""}, {"(*Root).OpenFile", Method, 24, ""}, {"(*Root).OpenRoot", Method, 24, ""}, + {"(*Root).ReadFile", Method, 25, ""}, {"(*Root).Readlink", Method, 25, ""}, {"(*Root).Remove", Method, 24, ""}, + {"(*Root).RemoveAll", Method, 25, ""}, {"(*Root).Rename", Method, 25, ""}, {"(*Root).Stat", Method, 24, ""}, {"(*Root).Symlink", Method, 25, ""}, + {"(*Root).WriteFile", Method, 25, ""}, {"(*SyscallError).Error", Method, 0, ""}, {"(*SyscallError).Timeout", Method, 10, ""}, {"(*SyscallError).Unwrap", Method, 13, ""}, @@ -9623,6 +9649,7 @@ var PackageSymbols = map[string][]Symbol{ {"StructTag", Type, 0, ""}, {"Swapper", Func, 8, "func(slice any) func(i int, j int)"}, {"Type", Type, 0, ""}, + {"TypeAssert", Func, 25, "func[T any](v Value) (T, bool)"}, {"TypeFor", Func, 22, "func[T any]() Type"}, {"TypeOf", Func, 0, "func(i any) Type"}, {"Uint", Const, 0, ""}, @@ -9909,6 +9936,7 @@ var PackageSymbols = map[string][]Symbol{ {"SetBlockProfileRate", Func, 1, "func(rate int)"}, {"SetCPUProfileRate", Func, 0, "func(hz int)"}, {"SetCgoTraceback", Func, 7, "func(version int, traceback unsafe.Pointer, context unsafe.Pointer, symbolizer unsafe.Pointer)"}, + {"SetDefaultGOMAXPROCS", Func, 25, "func()"}, {"SetFinalizer", Func, 0, "func(obj any, finalizer any)"}, {"SetMutexProfileFraction", Func, 8, "func(rate int) int"}, {"Stack", Func, 0, "func(buf []byte, all bool) int"}, @@ -10021,11 +10049,20 @@ var PackageSymbols = map[string][]Symbol{ {"WriteHeapProfile", Func, 0, "func(w io.Writer) error"}, }, "runtime/trace": { + {"(*FlightRecorder).Enabled", Method, 25, ""}, + {"(*FlightRecorder).Start", Method, 25, ""}, + {"(*FlightRecorder).Stop", Method, 25, ""}, + {"(*FlightRecorder).WriteTo", Method, 25, ""}, {"(*Region).End", Method, 11, ""}, {"(*Task).End", Method, 11, ""}, + {"FlightRecorder", Type, 25, ""}, + {"FlightRecorderConfig", Type, 25, ""}, + {"FlightRecorderConfig.MaxBytes", Field, 25, ""}, + {"FlightRecorderConfig.MinAge", Field, 25, ""}, {"IsEnabled", Func, 11, "func() bool"}, {"Log", Func, 11, "func(ctx context.Context, category string, message string)"}, {"Logf", Func, 11, "func(ctx context.Context, category string, format string, args ...any)"}, + {"NewFlightRecorder", Func, 25, "func(cfg FlightRecorderConfig) *FlightRecorder"}, {"NewTask", Func, 11, "func(pctx context.Context, taskType string) (ctx context.Context, task *Task)"}, {"Region", Type, 11, ""}, {"Start", Func, 5, "func(w io.Writer) error"}, @@ -16642,6 +16679,7 @@ var PackageSymbols = map[string][]Symbol{ {"ValueOf", Func, 0, ""}, }, "testing": { + {"(*B).Attr", Method, 25, ""}, {"(*B).Chdir", Method, 24, ""}, {"(*B).Cleanup", Method, 14, ""}, {"(*B).Context", Method, 24, ""}, @@ -16658,6 +16696,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*B).Logf", Method, 0, ""}, {"(*B).Loop", Method, 24, ""}, {"(*B).Name", Method, 8, ""}, + {"(*B).Output", Method, 25, ""}, {"(*B).ReportAllocs", Method, 1, ""}, {"(*B).ReportMetric", Method, 13, ""}, {"(*B).ResetTimer", Method, 0, ""}, @@ -16674,6 +16713,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*B).StopTimer", Method, 0, ""}, {"(*B).TempDir", Method, 15, ""}, {"(*F).Add", Method, 18, ""}, + {"(*F).Attr", Method, 25, ""}, {"(*F).Chdir", Method, 24, ""}, {"(*F).Cleanup", Method, 18, ""}, {"(*F).Context", Method, 24, ""}, @@ -16689,6 +16729,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*F).Log", Method, 18, ""}, {"(*F).Logf", Method, 18, ""}, {"(*F).Name", Method, 18, ""}, + {"(*F).Output", Method, 25, ""}, {"(*F).Setenv", Method, 18, ""}, {"(*F).Skip", Method, 18, ""}, {"(*F).SkipNow", Method, 18, ""}, @@ -16697,6 +16738,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*F).TempDir", Method, 18, ""}, {"(*M).Run", Method, 4, ""}, {"(*PB).Next", Method, 3, ""}, + {"(*T).Attr", Method, 25, ""}, {"(*T).Chdir", Method, 24, ""}, {"(*T).Cleanup", Method, 14, ""}, {"(*T).Context", Method, 24, ""}, @@ -16712,6 +16754,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*T).Log", Method, 0, ""}, {"(*T).Logf", Method, 0, ""}, {"(*T).Name", Method, 8, ""}, + {"(*T).Output", Method, 25, ""}, {"(*T).Parallel", Method, 0, ""}, {"(*T).Run", Method, 7, ""}, {"(*T).Setenv", Method, 17, ""}, @@ -16834,6 +16877,10 @@ var PackageSymbols = map[string][]Symbol{ {"Run", Func, 22, "func(t *testing.T, newHandler func(*testing.T) slog.Handler, result func(*testing.T) map[string]any)"}, {"TestHandler", Func, 21, "func(h slog.Handler, results func() []map[string]any) error"}, }, + "testing/synctest": { + {"Test", Func, 25, "func(t *testing.T, f func(*testing.T))"}, + {"Wait", Func, 25, "func()"}, + }, "text/scanner": { {"(*Position).IsValid", Method, 0, ""}, {"(*Scanner).Init", Method, 0, ""}, @@ -17347,6 +17394,7 @@ var PackageSymbols = map[string][]Symbol{ {"CaseRange.Lo", Field, 0, ""}, {"CaseRanges", Var, 0, ""}, {"Categories", Var, 0, ""}, + {"CategoryAliases", Var, 25, ""}, {"Caucasian_Albanian", Var, 4, ""}, {"Cc", Var, 0, ""}, {"Cf", Var, 0, ""}, @@ -17354,6 +17402,7 @@ var PackageSymbols = map[string][]Symbol{ {"Cham", Var, 0, ""}, {"Cherokee", Var, 0, ""}, {"Chorasmian", Var, 16, ""}, + {"Cn", Var, 25, ""}, {"Co", Var, 0, ""}, {"Common", Var, 0, ""}, {"Coptic", Var, 0, ""}, @@ -17432,6 +17481,7 @@ var PackageSymbols = map[string][]Symbol{ {"Khojki", Var, 4, ""}, {"Khudawadi", Var, 4, ""}, {"L", Var, 0, ""}, + {"LC", Var, 25, ""}, {"Lao", Var, 0, ""}, {"Latin", Var, 0, ""}, {"Lepcha", Var, 0, ""}, diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 457fc9d619f6ca..ebdac1a8b7706c 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -73,8 +73,8 @@ golang.org/x/text/internal/tag golang.org/x/text/language golang.org/x/text/transform golang.org/x/text/unicode/norm -# golang.org/x/tools v0.36.1-0.20250808220315-8866876b956f -## explicit; go 1.23.0 +# golang.org/x/tools v0.36.1-0.20250904192731-a09a2fba1c08 +## explicit; go 1.24.0 golang.org/x/tools/cmd/bisect golang.org/x/tools/cover golang.org/x/tools/go/analysis @@ -128,6 +128,8 @@ golang.org/x/tools/internal/analysisinternal golang.org/x/tools/internal/analysisinternal/typeindex golang.org/x/tools/internal/astutil golang.org/x/tools/internal/bisect +golang.org/x/tools/internal/diff +golang.org/x/tools/internal/diff/lcs golang.org/x/tools/internal/facts golang.org/x/tools/internal/fmtstr golang.org/x/tools/internal/stdlib From d767064170aa3469404d25608d9ff9fa48962337 Mon Sep 17 00:00:00 2001 From: Jake Bailey Date: Fri, 5 Sep 2025 12:59:33 -0700 Subject: [PATCH 35/40] cmd/compile: mark abi.PtrType.Elem sym as used CL 700336 let the compiler see into the abi.PtrType.Elem field, but forgot the MarkTypeSymUsedInInterface to ensure that the symbol is marked as referenced. I am not sure how to write a test for this, but I noticed this when working on further optimizations where I "fixed" this issue and confusingly failed toolstash -cmp, with diffs like: @@ -70582,6 +70582,7 @@ reflect.groupAndSlotOf<1> STEXT size=696 args=0x20 locals=0x1e0 funcid=0x0 align rel 3+0 t=R_USEIFACE type:*reflect.rtype<0>+0 rel 3+0 t=R_USEIFACE type:*reflect.rtype<0>+0 rel 3+0 t=R_USEIFACE type:*uint64<0>+0 + rel 3+0 t=R_USEIFACE type:uint64<0>+0 rel 71+0 t=R_CALLIND +0 rel 92+4 t=R_PCREL go:itab.*reflect.rtype,reflect.Type<0>+0 rel 114+4 t=R_CALL reflect.(*rtype).ptrTo<1>+0 Updates #75203 Change-Id: Ib8de8a32aeb8a7ea6fcf5d728a2e4944ef227ab2 Reviewed-on: https://go-review.googlesource.com/c/go/+/701296 LUCI-TryBot-Result: Go LUCI Auto-Submit: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Michael Pratt Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/_gen/generic.rules | 8 ++++---- src/cmd/compile/internal/ssa/rewrite.go | 6 ++++-- src/cmd/compile/internal/ssa/rewritegeneric.go | 16 ++++++++-------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules index c5e2507a14fd68..6255045c6f50d5 100644 --- a/src/cmd/compile/internal/ssa/_gen/generic.rules +++ b/src/cmd/compile/internal/ssa/_gen/generic.rules @@ -2778,10 +2778,10 @@ (Load (ITab (IMake (Convert (Addr {s} sb) _) _)) _) && isFixedSym(s, 0) => (Addr {fixedSym(b.Func, s, 0)} sb) // Loading constant values from abi.PtrType.Elem. -(Load (OffPtr [off] (Addr {s} sb) ) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(s, off)} sb) -(Load (OffPtr [off] (Convert (Addr {s} sb) _) ) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(s, off)} sb) -(Load (OffPtr [off] (ITab (IMake (Addr {s} sb) _))) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(s, off)} sb) -(Load (OffPtr [off] (ITab (IMake (Convert (Addr {s} sb) _) _))) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(s, off)} sb) +(Load (OffPtr [off] (Addr {s} sb) ) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(b.Func, s, off)} sb) +(Load (OffPtr [off] (Convert (Addr {s} sb) _) ) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(b.Func, s, off)} sb) +(Load (OffPtr [off] (ITab (IMake (Addr {s} sb) _))) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(b.Func, s, off)} sb) +(Load (OffPtr [off] (ITab (IMake (Convert (Addr {s} sb) _) _))) _) && t.IsPtr() && isPtrElem(s, off) => (Addr {ptrElem(b.Func, s, off)} sb) // Loading constant values from runtime._type.hash. (Load (OffPtr [off] (Addr {sym} _) ) _) && t.IsInteger() && t.Size() == 4 && isFixed32(config, sym, off) => (Const32 [fixed32(config, sym, off)]) diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index 8f331c283a66e0..a4242c2141d8f9 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -2029,14 +2029,16 @@ func isPtrElem(sym Sym, off int64) bool { } return false } -func ptrElem(sym Sym, off int64) Sym { +func ptrElem(f *Func, sym Sym, off int64) Sym { lsym := sym.(*obj.LSym) if strings.HasPrefix(lsym.Name, "type:*") { if ti, ok := (*lsym.Extra).(*obj.TypeInfo); ok { t := ti.Type.(*types.Type) if t.Kind() == types.TPTR { if off == rttype.PtrType.OffsetOf("Elem") { - return reflectdata.TypeLinksym(t.Elem()) + elemSym := reflectdata.TypeLinksym(t.Elem()) + reflectdata.MarkTypeSymUsedInInterface(elemSym, f.fe.Func().Linksym()) + return elemSym } } } diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index a0a4960397d809..e776ea5301bb6e 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -15083,7 +15083,7 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { } // match: (Load (OffPtr [off] (Addr {s} sb) ) _) // cond: t.IsPtr() && isPtrElem(s, off) - // result: (Addr {ptrElem(s, off)} sb) + // result: (Addr {ptrElem(b.Func, s, off)} sb) for { t := v.Type if v_0.Op != OpOffPtr { @@ -15100,13 +15100,13 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { break } v.reset(OpAddr) - v.Aux = symToAux(ptrElem(s, off)) + v.Aux = symToAux(ptrElem(b.Func, s, off)) v.AddArg(sb) return true } // match: (Load (OffPtr [off] (Convert (Addr {s} sb) _) ) _) // cond: t.IsPtr() && isPtrElem(s, off) - // result: (Addr {ptrElem(s, off)} sb) + // result: (Addr {ptrElem(b.Func, s, off)} sb) for { t := v.Type if v_0.Op != OpOffPtr { @@ -15127,13 +15127,13 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { break } v.reset(OpAddr) - v.Aux = symToAux(ptrElem(s, off)) + v.Aux = symToAux(ptrElem(b.Func, s, off)) v.AddArg(sb) return true } // match: (Load (OffPtr [off] (ITab (IMake (Addr {s} sb) _))) _) // cond: t.IsPtr() && isPtrElem(s, off) - // result: (Addr {ptrElem(s, off)} sb) + // result: (Addr {ptrElem(b.Func, s, off)} sb) for { t := v.Type if v_0.Op != OpOffPtr { @@ -15158,13 +15158,13 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { break } v.reset(OpAddr) - v.Aux = symToAux(ptrElem(s, off)) + v.Aux = symToAux(ptrElem(b.Func, s, off)) v.AddArg(sb) return true } // match: (Load (OffPtr [off] (ITab (IMake (Convert (Addr {s} sb) _) _))) _) // cond: t.IsPtr() && isPtrElem(s, off) - // result: (Addr {ptrElem(s, off)} sb) + // result: (Addr {ptrElem(b.Func, s, off)} sb) for { t := v.Type if v_0.Op != OpOffPtr { @@ -15193,7 +15193,7 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { break } v.reset(OpAddr) - v.Aux = symToAux(ptrElem(s, off)) + v.Aux = symToAux(ptrElem(b.Func, s, off)) v.AddArg(sb) return true } From e8126bce9e511b92b914643d30f96846bbc5c783 Mon Sep 17 00:00:00 2001 From: Guoqi Chen Date: Thu, 4 Sep 2025 17:02:01 +0800 Subject: [PATCH 36/40] runtime/cgo: save and restore R31 for crosscall1 on loong64 According to the Loong64 procedure call standard [1], R31 is a static register and therefore needs to be saved and restored. Also, the R2 (thread pointer) register has been removed here, as it is not involved in allocation. [1]: https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc Change-Id: I02e5d4bedf131e491f1a262aa3cbc0896cbc9488 Reviewed-on: https://go-review.googlesource.com/c/go/+/700817 LUCI-TryBot-Result: Go LUCI Reviewed-by: sophie zhao Reviewed-by: Meidan Li Reviewed-by: Cherry Mui Reviewed-by: Michael Pratt --- src/runtime/cgo/gcc_loong64.S | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/runtime/cgo/gcc_loong64.S b/src/runtime/cgo/gcc_loong64.S index c84a3715b25655..d2b062f49f9ce5 100644 --- a/src/runtime/cgo/gcc_loong64.S +++ b/src/runtime/cgo/gcc_loong64.S @@ -8,23 +8,23 @@ * void crosscall1(void (*fn)(void), void (*setg_gcc)(void *g), void *g) * * Calling into the gc tool chain, where all registers are caller save. - * Called from standard lp64d ABI, where $r1, $r3, $r23-$r30, and $f24-$f31 + * Called from standard lp64d ABI, where $r1, $r3, $r22-$r31, and $f24-$f31 * are callee-save, so they must be saved explicitly, along with $r1 (LR). */ .globl crosscall1 crosscall1: addi.d $r3, $r3, -160 st.d $r1, $r3, 0 - st.d $r23, $r3, 8 - st.d $r24, $r3, 16 - st.d $r25, $r3, 24 - st.d $r26, $r3, 32 - st.d $r27, $r3, 40 - st.d $r28, $r3, 48 - st.d $r29, $r3, 56 - st.d $r30, $r3, 64 - st.d $r2, $r3, 72 - st.d $r22, $r3, 80 + st.d $r22, $r3, 8 + st.d $r23, $r3, 16 + st.d $r24, $r3, 24 + st.d $r25, $r3, 32 + st.d $r26, $r3, 40 + st.d $r27, $r3, 48 + st.d $r28, $r3, 56 + st.d $r29, $r3, 64 + st.d $r30, $r3, 72 + st.d $r31, $r3, 80 fst.d $f24, $r3, 88 fst.d $f25, $r3, 96 fst.d $f26, $r3, 104 @@ -40,16 +40,16 @@ crosscall1: jirl $r1, $r5, 0 // call setg_gcc (clobbers R4) jirl $r1, $r23, 0 // call fn - ld.d $r23, $r3, 8 - ld.d $r24, $r3, 16 - ld.d $r25, $r3, 24 - ld.d $r26, $r3, 32 - ld.d $r27, $r3, 40 - ld.d $r28, $r3, 48 - ld.d $r29, $r3, 56 - ld.d $r30, $r3, 64 - ld.d $r2, $r3, 72 - ld.d $r22, $r3, 80 + ld.d $r22, $r3, 8 + ld.d $r23, $r3, 16 + ld.d $r24, $r3, 24 + ld.d $r25, $r3, 32 + ld.d $r26, $r3, 40 + ld.d $r27, $r3, 48 + ld.d $r28, $r3, 56 + ld.d $r29, $r3, 64 + ld.d $r30, $r3, 72 + ld.d $r31, $r3, 80 fld.d $f24, $r3, 88 fld.d $f25, $r3, 96 fld.d $f26, $r3, 104 From a6144613d3b601be1db6aa2fdaa79c954fdfe02c Mon Sep 17 00:00:00 2001 From: database64128 Date: Fri, 29 Aug 2025 17:52:54 +0800 Subject: [PATCH 37/40] crypto/tls: use context.AfterFunc in handshakeContext This saves a goroutine when ctx can be canceled but is not canceled during the handshakeContext call. Use ctx consistently, because in this path (c.quic == nil) handshakeCtx will only be canceled when ctx is canceled. Change-Id: I7f4565119f30d589dce026b0d7ef3c324220525a Reviewed-on: https://go-review.googlesource.com/c/go/+/699895 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Daniel McCarney Reviewed-by: Michael Pratt --- src/crypto/tls/conn.go | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index b36fcaa64830d2..d4d68c0744daa8 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -1524,7 +1524,7 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) { } handshakeCtx, cancel := context.WithCancel(ctx) - // Note: defer this before starting the "interrupter" goroutine + // Note: defer this before calling context.AfterFunc // so that we can tell the difference between the input being canceled and // this cancellation. In the former case, we need to close the connection. defer cancel() @@ -1533,28 +1533,14 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) { c.quic.cancelc = handshakeCtx.Done() c.quic.cancel = cancel } else if ctx.Done() != nil { - // Start the "interrupter" goroutine, if this context might be canceled. - // (The background context cannot). - // - // The interrupter goroutine waits for the input context to be done and - // closes the connection if this happens before the function returns. - done := make(chan struct{}) - interruptRes := make(chan error, 1) + // Close the connection if ctx is canceled before the function returns. + stop := context.AfterFunc(ctx, func() { + _ = c.conn.Close() + }) defer func() { - close(done) - if ctxErr := <-interruptRes; ctxErr != nil { + if !stop() { // Return context error to user. - ret = ctxErr - } - }() - go func() { - select { - case <-handshakeCtx.Done(): - // Close the connection, discarding the error - _ = c.conn.Close() - interruptRes <- handshakeCtx.Err() - case <-done: - interruptRes <- nil + ret = ctx.Err() } }() } From 57769b5532e96a8f6b705035a39ee056a22e04c3 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Thu, 4 Sep 2025 11:42:56 +0100 Subject: [PATCH 38/40] os: reject OpenDir of a non-directory file in Plan 9 Check that the path argument to OpenDir in Plan 9 is a directory, and return error syscall.ENOTDIR if it is not. Fixes #75196 Change-Id: I3bec6b6b40a38c21264b5d22ff3e7dfbf8c1c6d7 Reviewed-on: https://go-review.googlesource.com/c/go/+/700855 Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Reviewed-by: David du Colombier <0intro@gmail.com> --- src/os/file_plan9.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/os/file_plan9.go b/src/os/file_plan9.go index 17026409eb6021..e563d123efa2c8 100644 --- a/src/os/file_plan9.go +++ b/src/os/file_plan9.go @@ -135,7 +135,20 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { } func openDirNolog(name string) (*File, error) { - return openFileNolog(name, O_RDONLY, 0) + f, e := openFileNolog(name, O_RDONLY, 0) + if e != nil { + return nil, e + } + d, e := f.Stat() + if e != nil { + f.Close() + return nil, e + } + if !d.IsDir() { + f.Close() + return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOTDIR} + } + return f, nil } // Close closes the File, rendering it unusable for I/O. From 861c90c907db1129dcd1540eecd3c66b6309db7a Mon Sep 17 00:00:00 2001 From: Alexander Yastrebov Date: Wed, 3 Sep 2025 10:09:08 +0000 Subject: [PATCH 39/40] net/http: pool transport gzip readers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit goos: linux goarch: amd64 pkg: net/http │ HEAD~1 │ HEAD │ │ sec/op │ sec/op vs base │ ClientGzip-8 621.0µ ± 2% 616.3µ ± 10% ~ (p=0.971 n=10) │ HEAD~1 │ HEAD │ │ B/op │ B/op vs base │ ClientGzip-8 49.765Ki ± 0% 9.514Ki ± 2% -80.88% (p=0.000 n=10) │ HEAD~1 │ HEAD │ │ allocs/op │ allocs/op vs base │ ClientGzip-8 57.00 ± 0% 52.00 ± 0% -8.77% (p=0.000 n=10) Allocation saving comes from absent compress/flate.(*dictDecoder).init This change also improves concurrent body read detection by returning an explicit error. Updates #61353 Change-Id: I380acfca912dc009b3b9c8283e27b3526cedd546 GitHub-Last-Rev: df12f6a48af4854ba686fe431a9aeb6d9ba3c303 GitHub-Pull-Request: golang/go#61390 Reviewed-on: https://go-review.googlesource.com/c/go/+/510255 Reviewed-by: Sean Liao Auto-Submit: Michael Pratt Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/net/http/serve_test.go | 102 +++++++++++++++++++++++++++---------- src/net/http/transport.go | 89 +++++++++++++++++++++++++++----- 2 files changed, 151 insertions(+), 40 deletions(-) diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index 7e3e490af32679..aee6288f3b253b 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -12,6 +12,7 @@ import ( "compress/gzip" "compress/zlib" "context" + crand "crypto/rand" "crypto/tls" "crypto/x509" "encoding/json" @@ -5281,8 +5282,8 @@ func benchmarkClientServerParallel(b *testing.B, parallelism int, mode testMode) func BenchmarkServer(b *testing.B) { b.ReportAllocs() // Child process mode; - if url := os.Getenv("TEST_BENCH_SERVER_URL"); url != "" { - n, err := strconv.Atoi(os.Getenv("TEST_BENCH_CLIENT_N")) + if url := os.Getenv("GO_TEST_BENCH_SERVER_URL"); url != "" { + n, err := strconv.Atoi(os.Getenv("GO_TEST_BENCH_CLIENT_N")) if err != nil { panic(err) } @@ -5316,8 +5317,8 @@ func BenchmarkServer(b *testing.B) { cmd := testenv.Command(b, os.Args[0], "-test.run=^$", "-test.bench=^BenchmarkServer$") cmd.Env = append([]string{ - fmt.Sprintf("TEST_BENCH_CLIENT_N=%d", b.N), - fmt.Sprintf("TEST_BENCH_SERVER_URL=%s", ts.URL), + fmt.Sprintf("GO_TEST_BENCH_CLIENT_N=%d", b.N), + fmt.Sprintf("GO_TEST_BENCH_SERVER_URL=%s", ts.URL), }, os.Environ()...) out, err := cmd.CombinedOutput() if err != nil { @@ -5338,30 +5339,54 @@ func getNoBody(urlStr string) (*Response, error) { // A benchmark for profiling the client without the HTTP server code. // The server code runs in a subprocess. func BenchmarkClient(b *testing.B) { + var data = []byte("Hello world.\n") + + url := startClientBenchmarkServer(b, HandlerFunc(func(w ResponseWriter, _ *Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write(data) + })) + + // Do b.N requests to the server. + b.StartTimer() + for i := 0; i < b.N; i++ { + res, err := Get(url) + if err != nil { + b.Fatalf("Get: %v", err) + } + body, err := io.ReadAll(res.Body) + res.Body.Close() + if err != nil { + b.Fatalf("ReadAll: %v", err) + } + if !bytes.Equal(body, data) { + b.Fatalf("Got body: %q", body) + } + } + b.StopTimer() +} + +func startClientBenchmarkServer(b *testing.B, handler Handler) string { b.ReportAllocs() b.StopTimer() - defer afterTest(b) - var data = []byte("Hello world.\n") - if server := os.Getenv("TEST_BENCH_SERVER"); server != "" { + if server := os.Getenv("GO_TEST_BENCH_SERVER"); server != "" { // Server process mode. - port := os.Getenv("TEST_BENCH_SERVER_PORT") // can be set by user + port := os.Getenv("GO_TEST_BENCH_SERVER_PORT") // can be set by user if port == "" { port = "0" } ln, err := net.Listen("tcp", "localhost:"+port) if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + log.Fatal(err) } fmt.Println(ln.Addr().String()) + HandleFunc("/", func(w ResponseWriter, r *Request) { r.ParseForm() if r.Form.Get("stop") != "" { os.Exit(0) } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Write(data) + handler.ServeHTTP(w, r) }) var srv Server log.Fatal(srv.Serve(ln)) @@ -5369,8 +5394,8 @@ func BenchmarkClient(b *testing.B) { // Start server process. ctx, cancel := context.WithCancel(context.Background()) - cmd := testenv.CommandContext(b, ctx, os.Args[0], "-test.run=^$", "-test.bench=^BenchmarkClient$") - cmd.Env = append(cmd.Environ(), "TEST_BENCH_SERVER=yes") + cmd := testenv.CommandContext(b, ctx, os.Args[0], "-test.run=^$", "-test.bench=^"+b.Name()+"$") + cmd.Env = append(cmd.Environ(), "GO_TEST_BENCH_SERVER=yes") cmd.Stderr = os.Stderr stdout, err := cmd.StdoutPipe() if err != nil { @@ -5385,10 +5410,6 @@ func BenchmarkClient(b *testing.B) { done <- cmd.Wait() close(done) }() - defer func() { - cancel() - <-done - }() // Wait for the server in the child process to respond and tell us // its listening address, once it's started listening: @@ -5401,6 +5422,39 @@ func BenchmarkClient(b *testing.B) { b.Fatalf("initial probe of child process failed: %v", err) } + // Instruct server process to stop. + b.Cleanup(func() { + getNoBody(url + "?stop=yes") + if err := <-done; err != nil { + b.Fatalf("subprocess failed: %v", err) + } + + cancel() + <-done + + afterTest(b) + }) + + return url +} + +func BenchmarkClientGzip(b *testing.B) { + const responseSize = 1024 * 1024 + + var buf bytes.Buffer + gz := gzip.NewWriter(&buf) + if _, err := io.CopyN(gz, crand.Reader, responseSize); err != nil { + b.Fatal(err) + } + gz.Close() + + data := buf.Bytes() + + url := startClientBenchmarkServer(b, HandlerFunc(func(w ResponseWriter, _ *Request) { + w.Header().Set("Content-Encoding", "gzip") + w.Write(data) + })) + // Do b.N requests to the server. b.StartTimer() for i := 0; i < b.N; i++ { @@ -5408,22 +5462,16 @@ func BenchmarkClient(b *testing.B) { if err != nil { b.Fatalf("Get: %v", err) } - body, err := io.ReadAll(res.Body) + n, err := io.Copy(io.Discard, res.Body) res.Body.Close() if err != nil { b.Fatalf("ReadAll: %v", err) } - if !bytes.Equal(body, data) { - b.Fatalf("Got body: %q", body) + if n != responseSize { + b.Fatalf("ReadAll: expected %d bytes, got %d", responseSize, n) } } b.StopTimer() - - // Instruct server process to stop. - getNoBody(url + "?stop=yes") - if err := <-done; err != nil { - b.Fatalf("subprocess failed: %v", err) - } } func BenchmarkServerFakeConnNoKeepAlive(b *testing.B) { diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 572c16a6d838d8..c5b1a87c5cb112 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -11,6 +11,7 @@ package http import ( "bufio" + "compress/flate" "compress/gzip" "container/list" "context" @@ -2988,6 +2989,7 @@ type bodyEOFSignal struct { } var errReadOnClosedResBody = errors.New("http: read on closed response body") +var errConcurrentReadOnResBody = errors.New("http: concurrent read on response body") func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { es.mu.Lock() @@ -3037,37 +3039,98 @@ func (es *bodyEOFSignal) condfn(err error) error { } // gzipReader wraps a response body so it can lazily -// call gzip.NewReader on the first call to Read +// get gzip.Reader from the pool on the first call to Read. +// After Close is called it puts gzip.Reader to the pool immediately +// if there is no Read in progress or later when Read completes. type gzipReader struct { _ incomparable body *bodyEOFSignal // underlying HTTP/1 response body framing - zr *gzip.Reader // lazily-initialized gzip reader - zerr error // any error from gzip.NewReader; sticky + mu sync.Mutex // guards zr and zerr + zr *gzip.Reader + zerr error } -func (gz *gzipReader) Read(p []byte) (n int, err error) { +type eofReader struct{} + +func (eofReader) Read([]byte) (int, error) { return 0, io.EOF } +func (eofReader) ReadByte() (byte, error) { return 0, io.EOF } + +var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }} + +// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r. +func gzipPoolGet(r io.Reader) (*gzip.Reader, error) { + zr := gzipPool.Get().(*gzip.Reader) + if err := zr.Reset(r); err != nil { + gzipPoolPut(zr) + return nil, err + } + return zr, nil +} + +// gzipPoolPut puts a gzip.Reader back into the pool. +func gzipPoolPut(zr *gzip.Reader) { + // Reset will allocate bufio.Reader if we pass it anything + // other than a flate.Reader, so ensure that it's getting one. + var r flate.Reader = eofReader{} + zr.Reset(r) + gzipPool.Put(zr) +} + +// acquire returns a gzip.Reader for reading response body. +// The reader must be released after use. +func (gz *gzipReader) acquire() (*gzip.Reader, error) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr != nil { + return nil, gz.zerr + } if gz.zr == nil { - if gz.zerr == nil { - gz.zr, gz.zerr = gzip.NewReader(gz.body) - } + gz.zr, gz.zerr = gzipPoolGet(gz.body) if gz.zerr != nil { - return 0, gz.zerr + return nil, gz.zerr } } + ret := gz.zr + gz.zr, gz.zerr = nil, errConcurrentReadOnResBody + return ret, nil +} + +// release returns the gzip.Reader to the pool if Close was called during Read. +func (gz *gzipReader) release(zr *gzip.Reader) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == errConcurrentReadOnResBody { + gz.zr, gz.zerr = zr, nil + } else { // errReadOnClosedResBody + gzipPoolPut(zr) + } +} - gz.body.mu.Lock() - if gz.body.closed { - err = errReadOnClosedResBody +// close returns the gzip.Reader to the pool immediately or +// signals release to do so after Read completes. +func (gz *gzipReader) close() { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == nil && gz.zr != nil { + gzipPoolPut(gz.zr) + gz.zr = nil } - gz.body.mu.Unlock() + gz.zerr = errReadOnClosedResBody +} +func (gz *gzipReader) Read(p []byte) (n int, err error) { + zr, err := gz.acquire() if err != nil { return 0, err } - return gz.zr.Read(p) + defer gz.release(zr) + + return zr.Read(p) } func (gz *gzipReader) Close() error { + gz.close() + return gz.body.Close() } From a25a189d324250ea64c89176cf96ad566d4b4750 Mon Sep 17 00:00:00 2001 From: canoriz Date: Sun, 7 Sep 2025 22:56:24 +0800 Subject: [PATCH 40/40] net/http: allow reuse connection if entire unread body is in buffer --- src/net/http/client_test.go | 1 + src/net/http/transport.go | 32 ++++++++++++++++------ src/net/http/transport_test.go | 50 ++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/net/http/client_test.go b/src/net/http/client_test.go index 94fddb508e0e38..60d9325170ead6 100644 --- a/src/net/http/client_test.go +++ b/src/net/http/client_test.go @@ -845,6 +845,7 @@ func testClientInsecureTransport(t *testing.T, mode testMode) { if res != nil { res.Body.Close() } + c.CloseIdleConnections() } cst.close() diff --git a/src/net/http/transport.go b/src/net/http/transport.go index c5b1a87c5cb112..86bb7999c6bdbd 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -2359,13 +2359,27 @@ func (pc *persistConn) readLoop() { } waitForBodyRead := make(chan bool, 2) + body := &bodyEOFSignal{ body: resp.Body, - earlyCloseFn: func() error { - waitForBodyRead <- false + earlyCloseFn: func(r io.ReadCloser) error { + isEOF := false + if b, ok := r.(*body); ok { + if lr, ok := b.src.(*io.LimitedReader); ok { + if br, ok := (lr.R).(*bufio.Reader); ok { + if lr.N == int64(br.Buffered()) { + // if bufio.Reader buffer have all bytes remaining in LimitReader, + // discard the buffer then reuse connection, set EOF flag. + b.sawEOF = true + br.Discard(br.Buffered()) + isEOF = true + } + } + } + } + waitForBodyRead <- isEOF <-eofc // will be closed by deferred call at the end of the function return nil - }, fn: func(err error) error { isEOF := err == io.EOF @@ -2981,11 +2995,11 @@ func canonicalAddr(url *url.URL) string { // the return value from Close. type bodyEOFSignal struct { body io.ReadCloser - mu sync.Mutex // guards following 4 fields - closed bool // whether Close has been called - rerr error // sticky Read error - fn func(error) error // err will be nil on Read io.EOF - earlyCloseFn func() error // optional alt Close func used if io.EOF not seen + mu sync.Mutex // guards following 4 fields + closed bool // whether Close has been called + rerr error // sticky Read error + fn func(error) error // err will be nil on Read io.EOF + earlyCloseFn func(io.ReadCloser) error // optional alt Close func used if io.EOF not seen } var errReadOnClosedResBody = errors.New("http: read on closed response body") @@ -3022,7 +3036,7 @@ func (es *bodyEOFSignal) Close() error { } es.closed = true if es.earlyCloseFn != nil && es.rerr != io.EOF { - return es.earlyCloseFn() + return es.earlyCloseFn(es.body) } err := es.body.Close() return es.condfn(err) diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 810f21f3a517e6..867b193b04312a 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -474,6 +474,56 @@ func testTransportReadToEndReusesConn(t *testing.T, mode testMode) { } } +// Tests that the HTTP transport re-uses connections when a client +// early closes a response Body but the content is fully read into the underlying +// buffer. So we can discard the body buffer and reuse the connection. +func TestTransportReusesEarlyCloseButAllReceivedConn(t *testing.T) { + run(t, testTransportReusesEarlyCloseButAllReceivedConn) +} +func testTransportReusesEarlyCloseButAllReceivedConn(t *testing.T, mode testMode) { + const msg = "foobar" + + var addrSeen map[string]int + ts := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { + addrSeen[r.RemoteAddr]++ + w.Header().Set("Content-Length", strconv.Itoa(len(msg))) + w.WriteHeader(200) + w.Write([]byte(msg)) + })).ts + + wantLen := len(msg) + addrSeen = make(map[string]int) + total := 5 + for i := 0; i < total; i++ { + res, err := ts.Client().Get(ts.URL + path) + if err != nil { + t.Errorf("Get %s: %v", path, err) + continue + } + + if res.ContentLength != int64(wantLen) { + t.Errorf("%s res.ContentLength = %d; want %d", path, res.ContentLength, wantLen) + } + + if i+1 < total { + // Close body directly. The body is small enough, so probably the underlying bufio.Reader + // has read entire body into buffer. Thus even if the body is not read, the buffer is discarded + // then connection is reused. + res.Body.Close() + } else { + // when reading body, everything should be same. + got, err := io.ReadAll(res.Body) + if string(got) != msg || err != nil { + t.Errorf("%s ReadAll(Body) = %q, %v; want %q, nil", path, string(got), err, msg) + } + } + } + + if len(addrSeen) != 1 { + t.Errorf("for %s, server saw %d distinct client addresses; want 1", path, len(addrSeen)) + } +} + func TestTransportMaxPerHostIdleConns(t *testing.T) { run(t, testTransportMaxPerHostIdleConns, []testMode{http1Mode}) }