diff --git a/api/next/79946.txt b/api/next/79946.txt new file mode 100644 index 00000000000000..af588b25c02c2a --- /dev/null +++ b/api/next/79946.txt @@ -0,0 +1 @@ +pkg net/url, func MustParse(string) *URL #79946 diff --git a/doc/next/6-stdlib/99-minor/encoding/base32/20235.md b/doc/next/6-stdlib/99-minor/encoding/base32/20235.md new file mode 100644 index 00000000000000..2408795607f5d8 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/encoding/base32/20235.md @@ -0,0 +1,2 @@ +[Encoding.EncodedLen] now panics if the encoded length overflows int, +rather than returning a negative number. diff --git a/doc/next/6-stdlib/99-minor/encoding/base64/20235.md b/doc/next/6-stdlib/99-minor/encoding/base64/20235.md new file mode 100644 index 00000000000000..2408795607f5d8 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/encoding/base64/20235.md @@ -0,0 +1,2 @@ +[Encoding.EncodedLen] now panics if the encoded length overflows int, +rather than returning a negative number. diff --git a/doc/next/6-stdlib/99-minor/net/url/79946.md b/doc/next/6-stdlib/99-minor/net/url/79946.md new file mode 100644 index 00000000000000..5797ee261e2dce --- /dev/null +++ b/doc/next/6-stdlib/99-minor/net/url/79946.md @@ -0,0 +1,5 @@ + +The new [MustParse] function returns the parsed [URL] +or panics on error. +It can simplify some common uses of [Parse]. +For instance, initializing variables with valid URL string constants. diff --git a/src/bytes/example_test.go b/src/bytes/example_test.go index c489b950e59a91..253ea5bcddca0c 100644 --- a/src/bytes/example_test.go +++ b/src/bytes/example_test.go @@ -123,6 +123,35 @@ func ExampleBuffer_ReadByte() { // bcde } +func ExampleBuffer_Peek() { + var b bytes.Buffer + b.WriteString("Hello, Gophers!") + + data, err := b.Peek(5) + if err != nil { + panic(err) + } + fmt.Printf("First peek: %s\n", data) + + fmt.Printf("Buffer: %s\n", b.String()) + + // Advance past "Hello, ". + if _, err := b.Read(make([]byte, 7)); err != nil { + panic(err) + } + + data, err = b.Peek(7) + if err != nil { + panic(err) + } + fmt.Printf("Second peek: %s\n", data) + + // Output: + // First peek: Hello + // Buffer: Hello, Gophers! + // Second peek: Gophers +} + func ExampleClone() { b := []byte("abc") clone := bytes.Clone(b) @@ -244,6 +273,18 @@ func ExampleCut() { // Cut("Gopher", "Badger") = "Gopher", "", false } +func ExampleCutLast() { + show := func(s, sep string) { + before, after, found := bytes.CutLast([]byte(s), []byte(sep)) + fmt.Printf("CutLast(%q, %q) = %q, %q, %v\n", s, sep, before, after, found) + } + show("root/user/docs", "/") + show("Gopher", "/") + // Output: + // CutLast("root/user/docs", "/") = "root/user", "docs", true + // CutLast("Gopher", "/") = "Gopher", "", false +} + func ExampleCutPrefix() { show := func(s, prefix string) { after, found := bytes.CutPrefix([]byte(s), []byte(prefix)) diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index cd5a2be7057c59..4dadede1b1c72e 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -739,25 +739,9 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { ssa.OpAMD64LEAW1, ssa.OpAMD64LEAW2, ssa.OpAMD64LEAW4, ssa.OpAMD64LEAW8: p := s.Prog(v.Op.Asm()) memIdx(&p.From, v) - o := v.Reg() - p.To.Type = obj.TYPE_REG - p.To.Reg = o - if v.AuxInt != 0 && v.Aux == nil { - // Emit an additional LEA to add the displacement instead of creating a slow 3 operand LEA. - switch v.Op { - case ssa.OpAMD64LEAQ1, ssa.OpAMD64LEAQ2, ssa.OpAMD64LEAQ4, ssa.OpAMD64LEAQ8: - p = s.Prog(x86.ALEAQ) - case ssa.OpAMD64LEAL1, ssa.OpAMD64LEAL2, ssa.OpAMD64LEAL4, ssa.OpAMD64LEAL8: - p = s.Prog(x86.ALEAL) - case ssa.OpAMD64LEAW1, ssa.OpAMD64LEAW2, ssa.OpAMD64LEAW4, ssa.OpAMD64LEAW8: - p = s.Prog(x86.ALEAW) - } - p.From.Type = obj.TYPE_MEM - p.From.Reg = o - p.To.Type = obj.TYPE_REG - p.To.Reg = o - } ssagen.AddAux(&p.From, v) + p.To.Type = obj.TYPE_REG + p.To.Reg = v.Reg() case ssa.OpAMD64LEAQ, ssa.OpAMD64LEAL, ssa.OpAMD64LEAW: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_MEM diff --git a/src/cmd/compile/internal/arm64/galign.go b/src/cmd/compile/internal/arm64/galign.go index 3ebd860de8f887..3b0aeddb9599b3 100644 --- a/src/cmd/compile/internal/arm64/galign.go +++ b/src/cmd/compile/internal/arm64/galign.go @@ -24,4 +24,5 @@ func Init(arch *ssagen.ArchInfo) { arch.SSAGenBlock = ssaGenBlock arch.LoadRegResult = loadRegResult arch.SpillArgReg = spillArgReg + arch.SSAGenFinish = ssaGenFinish } diff --git a/src/cmd/compile/internal/arm64/pair.go b/src/cmd/compile/internal/arm64/pair.go new file mode 100644 index 00000000000000..5a9dec40ab7cc2 --- /dev/null +++ b/src/cmd/compile/internal/arm64/pair.go @@ -0,0 +1,194 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arm64 + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/objw" + "cmd/internal/obj" + "cmd/internal/obj/arm64" + "cmd/internal/src" +) + +// movdImmMemInfo describes a plain "MOVD reg, off(base)" or "MOVD off(base), reg" +// instruction. base, off, name, and sym record the memory operand's base +// register, offset, addressing class (NAME_AUTO or NAME_PARAM), and symbol; +// reg is the register stored or loaded, and isLoad distinguishes the two +// forms. +type movdImmMemInfo struct { + base, reg int16 + off int64 + name obj.AddrName + sym *obj.LSym + isLoad bool +} + +// movdImmMem decodes p into a movdImmMemInfo. ok reports whether p is a +// plain MOVD load or store using an immediate-offset addressing form +// suitable for LDP/STP coalescing. +func movdImmMem(p *obj.Prog) (info movdImmMemInfo, ok bool) { + if p.As != arm64.AMOVD || p.Scond != 0 || p.Reg != 0 { + return movdImmMemInfo{}, false + } + var a *obj.Addr + switch { + case p.From.Type == obj.TYPE_MEM && p.To.Type == obj.TYPE_REG: + // Plain load: MOVD mem, reg. + a = &p.From + info.reg = p.To.Reg + info.isLoad = true + case p.From.Type == obj.TYPE_REG && p.To.Type == obj.TYPE_MEM: + // Plain store: MOVD reg, mem. + a = &p.To + info.reg = p.From.Reg + default: + return movdImmMemInfo{}, false + } + if a.Index != 0 || (a.Name != obj.NAME_AUTO && a.Name != obj.NAME_PARAM) { + return movdImmMemInfo{}, false + } + info.base, info.off, info.name, info.sym = a.Reg, a.Offset, a.Name, a.Sym + return info, true +} + +// ssaGenFinish runs after genssa has emitted all of a function's Progs, +// resolved its branch and jump-table targets, and finalized the frame size +// in defframe. +func ssaGenFinish(pp *objw.Progs) { + if base.Flag.N != 0 { + // Keep unoptimized builds unoptimized. + return + } + pairSpills(pp.Text, pp.Text.To.Offset, pp.CurFunc.LSym.Func().JumpTables) +} + +// pairSpills fuses strictly-adjacent MOVD load or store pairs that target the +// same base register at consecutive 8-byte offsets into a single LDP or STP. +// text is the function's first Prog, framesize its final frame size, and +// jumpTables its resolved jump tables. +// +// This recovers spill/reload pairings that the SSA-level pair pass +// (cmd/compile/internal/ssa/pair.go) cannot perform: that pass runs before +// regalloc and only sees source-level loads/stores, so the STR/LDR pairs that +// regalloc later inserts for OpStoreReg/OpLoadReg never get a chance to be +// paired. Doing the fusion here, as the last step of code generation, keeps the +// optimization in the compiler so the assembler can stay a simple translator. +func pairSpills(text *obj.Prog, framesize int64, jumpTables []obj.JumpTable) { + // Branch and jump-table targets, collected lazily once a candidate pair + // survives the cheaper checks; most functions have no candidates. + // Fusing into the first of a pair whose second instruction is a target + // would silently change control flow: paths that jump directly to the + // second instruction would skip the work the LDP/STP now does at the + // first instruction's position. + var isTarget map[*obj.Prog]bool + targets := func() map[*obj.Prog]bool { + if isTarget == nil { + isTarget = map[*obj.Prog]bool{} + for p := text; p != nil; p = p.Link { + if t := p.To.Target(); t != nil { + isTarget[t] = true + } + } + for _, jt := range jumpTables { + for _, t := range jt.Targets { + isTarget[t] = true + } + } + } + return isTarget + } + for p := text; p != nil; p = p.Link { + q := p.Link + if q == nil { + continue + } + a, ok := movdImmMem(p) + if !ok { + continue + } + b, ok := movdImmMem(q) + if !ok || a.isLoad != b.isLoad { + continue + } + if a.base != b.base || a.name != b.name { + continue + } + if a.reg == b.reg { + // LDP with Rt1 == Rt2 is CONSTRAINED UNPREDICTABLE. (STP with + // identical source registers would be fine, but spills never + // store the same register twice, so don't bother.) + continue + } + if a.isLoad && a.reg == a.base { + // The first load overwrites the base register: executed + // sequentially, the second load computes its address from the + // just-loaded value, while LDP computes both addresses from + // the original base. (The second load overwriting the base is + // fine; no address depends on it.) + continue + } + if q.Pos.IsStmt() == src.PosIsStmt { + // q carries a statement boundary, and may also be registered + // as an inline mark: genssa promotes instructions it reuses + // as inline marks to statements. Reducing q to a 0-byte ANOP + // would drop the statement from the line tables and violate + // the invariant that inline marks are never zero-sized (they + // are identified by PC). + continue + } + lo, hi := a, b + if lo.off > hi.off { + lo, hi = hi, lo + } + if hi.off != lo.off+8 { + continue + } + // Fuse only when the result will encode as a single LDP/STP, whose + // signed 7-bit immediate scaled by 8 covers offsets [-512, 504]. + // The assembler resolves a NAME_AUTO offset to off+framesize+8 + // (the 8 is the saved LR) and a NAME_PARAM offset to + // off+framesize+24 or +32 depending on frame alignment padding — + // or off+8 in a frameless leaf, which is not decided until + // assembly, so a frameless PARAM must fit both. An out-of-range + // LDP/STP needs an assembler-synthesized address (ADD + LDP), + // which is no smaller than the original pair and serializes the + // accesses through REGTMP. + var biasLo, biasHi int64 + switch { + case lo.name == obj.NAME_AUTO: + // Autos exist only in functions with a frame, and their offsets + // are negative from FP, so framesize+8 rebases them onto the + // non-negative SP-relative range the LDP/STP immediate must reach. + biasLo = framesize + 8 + biasHi = biasLo + case framesize == 0: + biasLo, biasHi = 8, 24 + case framesize%16 == 0: + biasLo = framesize + 24 + biasHi = biasLo + default: + biasLo = framesize + 32 + biasHi = biasLo + } + if lo.off%8 != 0 || lo.off+biasLo < -512 || lo.off+biasHi > 504 { + continue + } + if targets()[q] { + continue + } + mem := obj.Addr{Type: obj.TYPE_MEM, Reg: lo.base, Offset: lo.off, Name: lo.name, Sym: lo.sym} + regs := obj.Addr{Type: obj.TYPE_REGREG, Reg: lo.reg, Offset: int64(hi.reg)} + if a.isLoad { + p.As = arm64.ALDP + p.From, p.To = mem, regs + } else { + p.As = arm64.ASTP + p.From, p.To = regs, mem + } + // Reduce q to a 0-byte ANOP rather than unlinking it so that + // branch targets referencing q remain valid. + obj.Nopout(q) + } +} diff --git a/src/cmd/compile/internal/arm64/pair_test.go b/src/cmd/compile/internal/arm64/pair_test.go new file mode 100644 index 00000000000000..1da7009a38491e --- /dev/null +++ b/src/cmd/compile/internal/arm64/pair_test.go @@ -0,0 +1,419 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arm64 + +import ( + "cmd/internal/obj" + "cmd/internal/obj/arm64" + "cmd/internal/src" + "testing" +) + +func TestPairSpills(t *testing.T) { + movdLoad := func(dst, base int16, off int64) *obj.Prog { + return &obj.Prog{ + As: arm64.AMOVD, + From: obj.Addr{Type: obj.TYPE_MEM, Reg: base, Offset: off, Name: obj.NAME_AUTO}, + To: obj.Addr{Type: obj.TYPE_REG, Reg: dst}, + } + } + movdStore := func(src, base int16, off int64) *obj.Prog { + return &obj.Prog{ + As: arm64.AMOVD, + From: obj.Addr{Type: obj.TYPE_REG, Reg: src}, + To: obj.Addr{Type: obj.TYPE_MEM, Reg: base, Offset: off, Name: obj.NAME_AUTO}, + } + } + // param rewrites p's memory operand to NAME_PARAM. + param := func(p *obj.Prog) *obj.Prog { + if p.From.Type == obj.TYPE_MEM { + p.From.Name = obj.NAME_PARAM + } else { + p.To.Name = obj.NAME_PARAM + } + return p + } + chain := func(progs ...*obj.Prog) *obj.Prog { + for i := 0; i < len(progs)-1; i++ { + progs[i].Link = progs[i+1] + } + return progs[0] + } + countAs := func(head *obj.Prog) map[obj.As]int { + m := map[obj.As]int{} + for p := head; p != nil; p = p.Link { + m[p.As]++ + } + return m + } + + // pairWant describes the expected operands of the fused LDP/STP: + // MOVDs of lo and hi (lo at the lower address off, hi at off+8) + // against base, with the memory operand's addressing class name. + type pairWant struct { + as obj.As + base int16 + off int64 + lo, hi int16 + name obj.AddrName + } + + tests := []struct { + name string + framesize int64 + setup func() (*obj.Prog, []obj.JumpTable) + wantLDP int + wantSTP int + wantMOVD int + wantNOP int + wantPair *pairWant + }{ + { + name: "adjacent LDRs fuse to LDP", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "adjacent STRs fuse to STP", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdStore(arm64.REG_R0, arm64.REGSP, 16), + movdStore(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantSTP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ASTP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "reverse-order LDRs fuse to LDP", + setup: func() (*obj.Prog, []obj.JumpTable) { + // p loads from the higher address, q from the lower. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 24), + movdLoad(arm64.REG_R1, arm64.REGSP, 16), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R1, arm64.REG_R0, obj.NAME_AUTO}, + }, + { + name: "reverse-order STRs fuse to STP", + setup: func() (*obj.Prog, []obj.JumpTable) { + // p stores to the higher address, q to the lower. + return chain( + movdStore(arm64.REG_R0, arm64.REGSP, 24), + movdStore(arm64.REG_R1, arm64.REGSP, 16), + ), nil + }, + wantSTP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ASTP, arm64.REGSP, 16, arm64.REG_R1, arm64.REG_R0, obj.NAME_AUTO}, + }, + { + name: "PARAM pair fuses", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + param(movdLoad(arm64.REG_R0, arm64.REGSP, 16)), + param(movdLoad(arm64.REG_R1, arm64.REGSP, 24)), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_PARAM}, + }, + { + name: "mixed AUTO and PARAM do not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + param(movdLoad(arm64.REG_R1, arm64.REGSP, 24)), + ), nil + }, + wantMOVD: 2, + }, + { + name: "non-adjacent offsets do not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REGSP, 32), + ), nil + }, + wantMOVD: 2, + }, + { + name: "different base registers do not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REG_R28, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "load followed by store does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdStore(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "same destination register does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R0, arm64.REGSP, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "first load writing the second's base does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Executed sequentially, the second load computes its + // address from the value the first load just wrote into + // R1; LDP would compute both addresses from the original + // R1. + return chain( + movdLoad(arm64.REG_R1, arm64.REG_R1, 16), + movdLoad(arm64.REG_R2, arm64.REG_R1, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "second load writing the base fuses", + setup: func() (*obj.Prog, []obj.JumpTable) { + // No address depends on the base after the second load + // overwrites it, so LDP has identical semantics. + return chain( + movdLoad(arm64.REG_R0, arm64.REG_R5, 16), + movdLoad(arm64.REG_R5, arm64.REG_R5, 24), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REG_R5, 16, arm64.REG_R0, arm64.REG_R5, obj.NAME_AUTO}, + }, + { + name: "store whose source is the base fuses", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Stores never write registers, so there is no base + // hazard for STP. + return chain( + movdStore(arm64.REG_R5, arm64.REG_R5, 16), + movdStore(arm64.REG_R1, arm64.REG_R5, 24), + ), nil + }, + wantSTP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ASTP, arm64.REG_R5, 16, arm64.REG_R5, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "skip when second instruction is a branch target", + setup: func() (*obj.Prog, []obj.JumpTable) { + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24) + br := &obj.Prog{As: arm64.AB, To: obj.Addr{Type: obj.TYPE_BRANCH}} + br.To.SetTarget(p2) + return chain(br, p1, p2), nil + }, + wantMOVD: 2, + }, + { + name: "skip when second instruction is a backward-branch target", + setup: func() (*obj.Prog, []obj.JumpTable) { + // The branch sits after the pair, so target collection + // must consider the whole Prog list, not just what + // precedes the pair. + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24) + br := &obj.Prog{As: arm64.AB, To: obj.Addr{Type: obj.TYPE_BRANCH}} + br.To.SetTarget(p2) + return chain(p1, p2, br), nil + }, + wantMOVD: 2, + }, + { + name: "skip when second instruction is a jump-table target", + setup: func() (*obj.Prog, []obj.JumpTable) { + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24) + return chain(p1, p2), []obj.JumpTable{{Targets: []*obj.Prog{p2}}} + }, + wantMOVD: 2, + }, + { + name: "skip when second instruction is a statement boundary", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Statement-marked instructions (including those genssa + // reuses as inline marks, which it promotes to + // statements) must not become zero-sized. The statement + // bit needs a known position to stick to. + var tab src.PosTable + pos := tab.XPos(src.MakePos(src.NewFileBase("f.go", "f.go"), 1, 1)) + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24) + p2.Pos = pos.WithIsStmt() + return chain(p1, p2), nil + }, + wantMOVD: 2, + }, + { + name: "at the encodable bound fuses", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Resolved offset 496+0+8 = 504, LDP's maximum. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 496), + movdLoad(arm64.REG_R1, arm64.REGSP, 504), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 496, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "resolved offset out of LDP range does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + // Resolved offset 504+0+8 = 512, just past the bound. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 504), + movdLoad(arm64.REG_R1, arm64.REGSP, 512), + ), nil + }, + wantMOVD: 2, + }, + { + name: "large frame pushes resolved offset out of range", + framesize: 1024, + setup: func() (*obj.Prog, []obj.JumpTable) { + // 16+1024+8 = 1048: fusing would force an + // assembler-synthesized address, no smaller than the + // original pair. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "deep spill slots fuse in a large frame", + framesize: 1024, + setup: func() (*obj.Prog, []obj.JumpTable) { + // -528+1024+8 = 504: encodable even though the + // pre-resolution offset is far below -512. + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, -528), + movdLoad(arm64.REG_R1, arm64.REGSP, -520), + ), nil + }, + wantLDP: 1, wantNOP: 1, + wantPair: &pairWant{arm64.ALDP, arm64.REGSP, -528, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO}, + }, + { + name: "misaligned offsets do not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, -12), + movdLoad(arm64.REG_R1, arm64.REGSP, -4), + ), nil + }, + wantMOVD: 2, + }, + { + name: "three adjacent loads fuse greedily", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + movdLoad(arm64.REG_R1, arm64.REGSP, 24), + movdLoad(arm64.REG_R2, arm64.REGSP, 32), + ), nil + }, + wantLDP: 1, wantNOP: 1, wantMOVD: 1, + }, + { + name: "intervening instruction blocks fusion", + setup: func() (*obj.Prog, []obj.JumpTable) { + return chain( + movdLoad(arm64.REG_R0, arm64.REGSP, 16), + &obj.Prog{As: arm64.AHINT}, + movdLoad(arm64.REG_R1, arm64.REGSP, 24), + ), nil + }, + wantMOVD: 2, + }, + { + name: "pre-indexed addressing does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p1.Scond = arm64.C_XPRE + return chain(p1, movdLoad(arm64.REG_R1, arm64.REGSP, 24)), nil + }, + wantMOVD: 2, + }, + { + name: "post-indexed addressing does not fuse", + setup: func() (*obj.Prog, []obj.JumpTable) { + p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16) + p1.Scond = arm64.C_XPOST + return chain(p1, movdLoad(arm64.REG_R1, arm64.REGSP, 24)), nil + }, + wantMOVD: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + head, jumpTables := tt.setup() + pairSpills(head, tt.framesize, jumpTables) + got := countAs(head) + if got[arm64.ALDP] != tt.wantLDP { + t.Errorf("ALDP count = %d, want %d", got[arm64.ALDP], tt.wantLDP) + } + if got[arm64.ASTP] != tt.wantSTP { + t.Errorf("ASTP count = %d, want %d", got[arm64.ASTP], tt.wantSTP) + } + if got[arm64.AMOVD] != tt.wantMOVD { + t.Errorf("AMOVD count = %d, want %d", got[arm64.AMOVD], tt.wantMOVD) + } + if got[obj.ANOP] != tt.wantNOP { + t.Errorf("ANOP count = %d, want %d", got[obj.ANOP], tt.wantNOP) + } + if w := tt.wantPair; w != nil { + var fused *obj.Prog + for p := head; p != nil; p = p.Link { + if p.As == arm64.ALDP || p.As == arm64.ASTP { + fused = p + break + } + } + if fused == nil { + t.Fatal("no LDP/STP emitted") + } + mem, regs := &fused.From, &fused.To // LDP order + if fused.As == arm64.ASTP { + regs, mem = &fused.From, &fused.To + } + if fused.As != w.as { + t.Errorf("fused As = %v, want %v", fused.As, w.as) + } + if mem.Type != obj.TYPE_MEM || mem.Reg != w.base || mem.Offset != w.off || mem.Name != w.name { + t.Errorf("memory operand = {Type %v, Reg %v, Offset %d, Name %v}, want {TYPE_MEM, %v, %d, %v}", + mem.Type, mem.Reg, mem.Offset, mem.Name, w.base, w.off, w.name) + } + if regs.Type != obj.TYPE_REGREG || regs.Reg != w.lo || regs.Offset != int64(w.hi) { + t.Errorf("register pair = {Type %v, Reg %v, Offset %v}, want {TYPE_REGREG, %v, %v}", + regs.Type, regs.Reg, regs.Offset, w.lo, w.hi) + } + } + }) + } +} diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go index 44bf01dfa47db2..04fd0555bfb9a7 100644 --- a/src/cmd/compile/internal/arm64/ssa.go +++ b/src/cmd/compile/internal/arm64/ssa.go @@ -466,7 +466,7 @@ func simdV11Narrow(s *ssagen.State, v *ssa.Value, arrangement int16) *obj.Prog { return p } -// simdV21Narrow2 generates a a destructive (updating upper half only) narrow "2" instruction, +// simdV21Narrow2 generates a destructive (updating upper half only) narrow "2" instruction, // e.g. XTN2 V1.4S, V0.8H. The arrangement parameter specifies the source arrangement. func simdV21Narrow2(s *ssagen.State, v *ssa.Value, arrangement int16) *obj.Prog { p := s.Prog(v.Op.Asm()) diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 9b4577bd37f332..e45b1d6bb3020b 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -935,6 +935,9 @@ FindRHS: return nil } if rhs == nil { + if n.AutoTemp() { + return nil + } base.FatalfAt(defn.Pos(), "RHS is nil: %v", defn) } diff --git a/src/cmd/compile/internal/ir/reassignment.go b/src/cmd/compile/internal/ir/reassignment.go index ff3d08cd5b1bbc..19926dfacb483a 100644 --- a/src/cmd/compile/internal/ir/reassignment.go +++ b/src/cmd/compile/internal/ir/reassignment.go @@ -221,6 +221,9 @@ FindRHS: return nil } if rhs == nil { + if n.AutoTemp() { + return nil + } base.FatalfAt(defn.Pos(), "RHS is nil: %v", defn) } diff --git a/src/cmd/compile/internal/midway/rewrite.go b/src/cmd/compile/internal/midway/rewrite.go index e80930bcd79346..9858d44f9cca09 100644 --- a/src/cmd/compile/internal/midway/rewrite.go +++ b/src/cmd/compile/internal/midway/rewrite.go @@ -15,7 +15,7 @@ import ( // "Midway" rewriting // -// Go attempts to provide a package similar to the the "Highway" library +// Go attempts to provide a package similar to the "Highway" library // for C++ (https://google.github.io/highway). The library package is "simd" // and defines vector types with unspecified widths that are bound to particular // machine dependent types as late as program execution. This is accomplished @@ -26,7 +26,7 @@ import ( // The rewriting takes place early in the compiler, after type checking but // before conversion to "unified" IR. To ensure that types are correctly set // on the modified version of the code, type checking information is reset and -// the type checking phase is re-run. The places some limits on the shape of +// the type checking phase is re-run. This places some limits on the shape of // the rewrites, but it also ensures that the rewritten code is well-formed. // // Rewritten code does not reference "archsimd" types directly, but instead diff --git a/src/cmd/compile/internal/ppc64/ssa.go b/src/cmd/compile/internal/ppc64/ssa.go index a0d81d31d6de7d..316d18d4f7bd27 100644 --- a/src/cmd/compile/internal/ppc64/ssa.go +++ b/src/cmd/compile/internal/ppc64/ssa.go @@ -146,8 +146,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { r1 := v.Args[1].Reg() // LWSYNC - Assuming shared data not write-through-required nor // caching-inhibited. See Appendix B.2.2.2 in the ISA 2.07b. - plwsync := s.Prog(ppc64.ALWSYNC) - plwsync.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ALWSYNC) // LBAR or LWAR p := s.Prog(ld) p.From.Type = obj.TYPE_MEM @@ -194,9 +193,8 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { r0 := v.Args[0].Reg() r1 := v.Args[1].Reg() out := v.Reg0() - // LWSYNC - Provide acquire ordering to pair with the - // release (pre-LWSYNC) above, making the operation - // sequentially consistent. + // LWSYNC - Assuming shared data not write-through-required nor + // caching-inhibited. See Appendix B.2.2.2 in the ISA 2.07b. s.Prog(ppc64.ALWSYNC) // LDAR or LWAR p := s.Prog(ld) @@ -232,8 +230,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // LWSYNC - Provide acquire ordering to pair with the // release (pre-LWSYNC) above, making the operation // sequentially consistent. - plwsync2 := s.Prog(ppc64.ALWSYNC) - plwsync2.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ALWSYNC) case ssa.OpPPC64LoweredAtomicExchange8, ssa.OpPPC64LoweredAtomicExchange32, @@ -258,8 +255,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { out := v.Reg0() // LWSYNC - Assuming shared data not write-through-required nor // caching-inhibited. See Appendix B.2.2.2 in the ISA 2.07b. - plwsync := s.Prog(ppc64.ALWSYNC) - plwsync.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ALWSYNC) // L[B|W|D]AR p := s.Prog(ld) p.From.Type = obj.TYPE_MEM @@ -277,8 +273,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p2.To.Type = obj.TYPE_BRANCH p2.To.SetTarget(p) // ISYNC - pisync := s.Prog(ppc64.AISYNC) - pisync.To.Type = obj.TYPE_NONE + s.Prog(ppc64.AISYNC) case ssa.OpPPC64LoweredAtomicLoad8, ssa.OpPPC64LoweredAtomicLoad32, @@ -302,8 +297,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { out := v.Reg0() // SYNC when AuxInt == 1; otherwise, load-acquire if v.AuxInt == 1 { - psync := s.Prog(ppc64.ASYNC) - psync.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ASYNC) } // Load p := s.Prog(ld) @@ -322,7 +316,6 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p2.To.Type = obj.TYPE_BRANCH // ISYNC pisync := s.Prog(ppc64.AISYNC) - pisync.To.Type = obj.TYPE_NONE p2.To.SetTarget(pisync) case ssa.OpPPC64LoweredAtomicStore8, @@ -345,8 +338,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { if v.AuxInt == 0 { syncOp = ppc64.ALWSYNC } - psync := s.Prog(syncOp) - psync.To.Type = obj.TYPE_NONE + s.Prog(syncOp) // Store p := s.Prog(st) p.To.Type = obj.TYPE_MEM @@ -387,8 +379,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Reg = out // LWSYNC - Assuming shared data not write-through-required nor // caching-inhibited. See Appendix B.2.2.2 in the ISA 2.07b. - plwsync1 := s.Prog(ppc64.ALWSYNC) - plwsync1.To.Type = obj.TYPE_NONE + s.Prog(ppc64.ALWSYNC) // LDAR or LWAR p0 := s.Prog(ld) p0.From.Type = obj.TYPE_MEM @@ -430,7 +421,6 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // If the operation is a CAS-Release, then synchronization is not necessary. if v.AuxInt != 0 { plwsync2 := s.Prog(ppc64.ALWSYNC) - plwsync2.To.Type = obj.TYPE_NONE p2.To.SetTarget(plwsync2) } else { // done (label) diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index 1fca65469b17b4..bf2957bad80c89 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -15,7 +15,6 @@ import ( "cmd/compile/internal/base" "cmd/compile/internal/bitvec" - "cmd/compile/internal/compare" "cmd/compile/internal/ir" "cmd/compile/internal/objw" "cmd/compile/internal/rttype" @@ -57,7 +56,7 @@ type typeSig struct { func commonSize() int { return int(rttype.Type.Size()) } // Sizeof(runtime._type{}) func uncommonSize(t *types.Type) int { // Sizeof(runtime.uncommontype{}) - if t.Sym() == nil && len(methods(t)) == 0 { + if t.TFlag()&abi.TFlagUncommon == 0 { return 0 } return int(rttype.UncommonType.Size()) @@ -464,20 +463,6 @@ func dcommontype(c rttype.Cursor, t *types.Type) { c.Field("PtrBytes").WriteUintptr(uint64(ptrdata)) c.Field("Hash").WriteUint32(types.TypeHash(t)) - var tflag abi.TFlag - if uncommonSize(t) != 0 { - tflag |= abi.TFlagUncommon - } - if t.Sym() != nil && t.Sym().Name != "" { - tflag |= abi.TFlagNamed - } - if compare.IsRegularMemory(t) { - tflag |= abi.TFlagRegularMemory - } - if onDemand { - tflag |= abi.TFlagGCMaskOnDemand - } - exported := false p := t.NameString() // If we're writing out type T, @@ -485,9 +470,8 @@ func dcommontype(c rttype.Cursor, t *types.Type) { // Use the string "*T"[1:] for "T", so that the two // share storage. This is a cheap way to reduce the // amount of space taken up by reflect strings. - if !strings.HasPrefix(p, "*") { + if t.TFlag()&abi.TFlagExtraStar != 0 { p = "*" + p - tflag |= abi.TFlagExtraStar if t.Sym() != nil { exported = types.IsExported(t.Sym().Name) } @@ -496,15 +480,8 @@ func dcommontype(c rttype.Cursor, t *types.Type) { exported = types.IsExported(t.Elem().Sym().Name) } } - if types.IsDirectIface(t) { - tflag |= abi.TFlagDirectIface - } - if tflag != abi.TFlag(uint8(tflag)) { - // this should optimize away completely - panic("Unexpected change in size of abi.TFlag") - } - c.Field("TFlag").WriteUint8(uint8(tflag)) + c.Field("TFlag").WriteUint8(uint8(t.TFlag())) // runtime (and common sense) expects alignment to be a power of two. i := int(uint8(t.Alignment())) @@ -1254,7 +1231,7 @@ func GCSym(t *types.Type, onDemandAllowed bool) (lsym *obj.LSym, ptrdata int64) // When write is true, it writes the symbol data. func dgcsym(t *types.Type, write, onDemandAllowed bool) (lsym *obj.LSym, onDemand bool, ptrdata int64) { ptrdata = types.PtrDataSize(t) - if !onDemandAllowed || ptrdata/int64(types.PtrSize) <= abi.MaxPtrmaskBytes*8 { + if !onDemandAllowed || t.TFlag()&abi.TFlagGCMaskOnDemand == 0 { lsym = dgcptrmask(t, write) return } diff --git a/src/cmd/compile/internal/ssa/_gen/AMD64.rules b/src/cmd/compile/internal/ssa/_gen/AMD64.rules index 36c1e3354d6f06..bb6732d57dfcf3 100644 --- a/src/cmd/compile/internal/ssa/_gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/_gen/AMD64.rules @@ -48,7 +48,10 @@ (ADCQconst x [c] (InvertFlags f)) => (ADCQconst x [c] (Select1 (NEGLflags (MOVBQZX (SETA f))))) (SBBQ x y (InvertFlags f)) => (SBBQ x y (Select1 (NEGLflags (MOVBQZX (SETA f))))) (SBBQconst x [c] (InvertFlags f)) => (SBBQconst x [c] (Select1 (NEGLflags (MOVBQZX (SETA f))))) - +// ADDQ/SUBQ an from a carry flag into ADCQ/SBBQ +// TODO: maybe add ADCL and SBBL ? +(ADDQ x (MOVBQZX (SETB flags))) => (Select0 (ADCQconst [0] x flags)) +(SUBQ x (MOVBQZX (SETB flags))) => (Select0 (SBBQconst [0] x flags)) (Mul64uhilo ...) => (MULQU2 ...) (Div128u ...) => (DIVQU2 ...) diff --git a/src/cmd/compile/internal/ssa/_gen/ARM.rules b/src/cmd/compile/internal/ssa/_gen/ARM.rules index 6020992a7de2c6..6af6cb7b55d26e 100644 --- a/src/cmd/compile/internal/ssa/_gen/ARM.rules +++ b/src/cmd/compile/internal/ssa/_gen/ARM.rules @@ -689,10 +689,6 @@ (UGE (InvertFlags cmp) yes no) => (ULE cmp yes no) (EQ (InvertFlags cmp) yes no) => (EQ cmp yes no) (NE (InvertFlags cmp) yes no) => (NE cmp yes no) -(LTnoov (InvertFlags cmp) yes no) => (GTnoov cmp yes no) -(GEnoov (InvertFlags cmp) yes no) => (LEnoov cmp yes no) -(LEnoov (InvertFlags cmp) yes no) => (GEnoov cmp yes no) -(GTnoov (InvertFlags cmp) yes no) => (LTnoov cmp yes no) // absorb flag constants into boolean values (Equal (FlagConstant [fc])) => (MOVWconst [b2i32(fc.eq())]) @@ -1339,24 +1335,6 @@ (NE (CMPconst [0] l:(XORshiftLLreg x y z)) yes no) && l.Uses==1 => (NE (TEQshiftLLreg x y z) yes no) (NE (CMPconst [0] l:(XORshiftRLreg x y z)) yes no) && l.Uses==1 => (NE (TEQshiftRLreg x y z) yes no) (NE (CMPconst [0] l:(XORshiftRAreg x y z)) yes no) && l.Uses==1 => (NE (TEQshiftRAreg x y z) yes no) -(LT (CMPconst [0] l:(SUB x y)) yes no) && l.Uses==1 => (LTnoov (CMP x y) yes no) -(LT (CMPconst [0] l:(MULS x y a)) yes no) && l.Uses==1 => (LTnoov (CMP a (MUL x y)) yes no) -(LT (CMPconst [0] l:(SUBconst [c] x)) yes no) && l.Uses==1 => (LTnoov (CMPconst [c] x) yes no) -(LT (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) && l.Uses==1 => (LTnoov (CMPshiftLL x y [c]) yes no) -(LT (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) && l.Uses==1 => (LTnoov (CMPshiftRL x y [c]) yes no) -(LT (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) && l.Uses==1 => (LTnoov (CMPshiftRA x y [c]) yes no) -(LT (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) && l.Uses==1 => (LTnoov (CMPshiftLLreg x y z) yes no) -(LT (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) && l.Uses==1 => (LTnoov (CMPshiftRLreg x y z) yes no) -(LT (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) && l.Uses==1 => (LTnoov (CMPshiftRAreg x y z) yes no) -(LE (CMPconst [0] l:(SUB x y)) yes no) && l.Uses==1 => (LEnoov (CMP x y) yes no) -(LE (CMPconst [0] l:(MULS x y a)) yes no) && l.Uses==1 => (LEnoov (CMP a (MUL x y)) yes no) -(LE (CMPconst [0] l:(SUBconst [c] x)) yes no) && l.Uses==1 => (LEnoov (CMPconst [c] x) yes no) -(LE (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) && l.Uses==1 => (LEnoov (CMPshiftLL x y [c]) yes no) -(LE (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) && l.Uses==1 => (LEnoov (CMPshiftRL x y [c]) yes no) -(LE (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) && l.Uses==1 => (LEnoov (CMPshiftRA x y [c]) yes no) -(LE (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) && l.Uses==1 => (LEnoov (CMPshiftLLreg x y z) yes no) -(LE (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) && l.Uses==1 => (LEnoov (CMPshiftRLreg x y z) yes no) -(LE (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) && l.Uses==1 => (LEnoov (CMPshiftRAreg x y z) yes no) (LT (CMPconst [0] l:(ADD x y)) yes no) && l.Uses==1 => (LTnoov (CMN x y) yes no) (LT (CMPconst [0] l:(MULA x y a)) yes no) && l.Uses==1 => (LTnoov (CMN a (MUL x y)) yes no) (LT (CMPconst [0] l:(ADDconst [c] x)) yes no) && l.Uses==1 => (LTnoov (CMNconst [c] x) yes no) @@ -1407,24 +1385,6 @@ (LE (CMPconst [0] l:(XORshiftLLreg x y z)) yes no) && l.Uses==1 => (LEnoov (TEQshiftLLreg x y z) yes no) (LE (CMPconst [0] l:(XORshiftRLreg x y z)) yes no) && l.Uses==1 => (LEnoov (TEQshiftRLreg x y z) yes no) (LE (CMPconst [0] l:(XORshiftRAreg x y z)) yes no) && l.Uses==1 => (LEnoov (TEQshiftRAreg x y z) yes no) -(GT (CMPconst [0] l:(SUB x y)) yes no) && l.Uses==1 => (GTnoov (CMP x y) yes no) -(GT (CMPconst [0] l:(MULS x y a)) yes no) && l.Uses==1 => (GTnoov (CMP a (MUL x y)) yes no) -(GT (CMPconst [0] l:(SUBconst [c] x)) yes no) && l.Uses==1 => (GTnoov (CMPconst [c] x) yes no) -(GT (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) && l.Uses==1 => (GTnoov (CMPshiftLL x y [c]) yes no) -(GT (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) && l.Uses==1 => (GTnoov (CMPshiftRL x y [c]) yes no) -(GT (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) && l.Uses==1 => (GTnoov (CMPshiftRA x y [c]) yes no) -(GT (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) && l.Uses==1 => (GTnoov (CMPshiftLLreg x y z) yes no) -(GT (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) && l.Uses==1 => (GTnoov (CMPshiftRLreg x y z) yes no) -(GT (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) && l.Uses==1 => (GTnoov (CMPshiftRAreg x y z) yes no) -(GE (CMPconst [0] l:(SUB x y)) yes no) && l.Uses==1 => (GEnoov (CMP x y) yes no) -(GE (CMPconst [0] l:(MULS x y a)) yes no) && l.Uses==1 => (GEnoov (CMP a (MUL x y)) yes no) -(GE (CMPconst [0] l:(SUBconst [c] x)) yes no) && l.Uses==1 => (GEnoov (CMPconst [c] x) yes no) -(GE (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) && l.Uses==1 => (GEnoov (CMPshiftLL x y [c]) yes no) -(GE (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) && l.Uses==1 => (GEnoov (CMPshiftRL x y [c]) yes no) -(GE (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) && l.Uses==1 => (GEnoov (CMPshiftRA x y [c]) yes no) -(GE (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) && l.Uses==1 => (GEnoov (CMPshiftLLreg x y z) yes no) -(GE (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) && l.Uses==1 => (GEnoov (CMPshiftRLreg x y z) yes no) -(GE (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) && l.Uses==1 => (GEnoov (CMPshiftRAreg x y z) yes no) (GT (CMPconst [0] l:(ADD x y)) yes no) && l.Uses==1 => (GTnoov (CMN x y) yes no) (GT (CMPconst [0] l:(ADDconst [c] x)) yes no) && l.Uses==1 => (GTnoov (CMNconst [c] x) yes no) (GT (CMPconst [0] l:(ADDshiftLL x y [c])) yes no) && l.Uses==1 => (GTnoov (CMNshiftLL x y [c]) yes no) diff --git a/src/cmd/compile/internal/ssa/_gen/ARM64.rules b/src/cmd/compile/internal/ssa/_gen/ARM64.rules index 4c6c437f26b9fe..db65737ed390e7 100644 --- a/src/cmd/compile/internal/ssa/_gen/ARM64.rules +++ b/src/cmd/compile/internal/ssa/_gen/ARM64.rules @@ -590,9 +590,15 @@ ((Equal|NotEqual|LessThan|GreaterEqual) (CMPconst [0] z:(ADD x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMN x y)) ((Equal|NotEqual|LessThan|GreaterEqual) (CMPWconst [0] z:(ADD x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMNW x y)) ((Equal|NotEqual|LessThan|GreaterEqual) (CMPconst [0] z:(MADD a x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMN a (MUL x y))) -((Equal|NotEqual|LessThan|GreaterEqual) (CMPconst [0] z:(MSUB a x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMP a (MUL x y))) ((Equal|NotEqual|LessThan|GreaterEqual) (CMPWconst [0] z:(MADDW a x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMNW a (MULW x y))) -((Equal|NotEqual|LessThan|GreaterEqual) (CMPWconst [0] z:(MSUBW a x y))) && z.Uses == 1 => ((Equal|NotEqual|LessThanNoov|GreaterEqualNoov) (CMPW a (MULW x y))) + +// Skip LessThan/GreaterEqual in the following rewrites. +// MSUB[W] a x y is a-x*y, which folds to a CMP. +// That can end up being reversed into InvertFlags. +// But absorbing InvertFlags into a no-overflow comparison flips < and >, +// which is wrong when the difference is the minimum int. +((Equal|NotEqual) (CMPconst [0] z:(MSUB a x y))) && z.Uses == 1 => ((Equal|NotEqual) (CMP a (MUL x y))) +((Equal|NotEqual) (CMPWconst [0] z:(MSUBW a x y))) && z.Uses == 1 => ((Equal|NotEqual) (CMPW a (MULW x y))) ((CMPconst|CMNconst) [c] y) && c < 0 && c != -1<<63 => ((CMNconst|CMPconst) [-c] y) ((CMPWconst|CMNWconst) [c] y) && c < 0 && c != -1<<31 => ((CMNWconst|CMPWconst) [-c] y) @@ -609,9 +615,10 @@ ((ZW|NZW) sub:(SUBconst [c] y)) && sub.Uses == 1 => ((EQ|NE) (CMPWconst [int32(c)] y)) ((EQ|NE|LT|LE|GT|GE) (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => ((EQ|NE|LTnoov|LEnoov|GTnoov|GEnoov) (CMN a (MUL x y)) yes no) -((EQ|NE|LT|LE|GT|GE) (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => ((EQ|NE|LTnoov|LEnoov|GTnoov|GEnoov) (CMP a (MUL x y)) yes no) ((EQ|NE|LT|LE|GT|GE) (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => ((EQ|NE|LTnoov|LEnoov|GTnoov|GEnoov) (CMNW a (MULW x y)) yes no) -((EQ|NE|LT|LE|GT|GE) (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => ((EQ|NE|LTnoov|LEnoov|GTnoov|GEnoov) (CMPW a (MULW x y)) yes no) +// No ordering ops here; see comments above about MSUB[W]. +((EQ|NE) (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => ((EQ|NE) (CMP a (MUL x y)) yes no) +((EQ|NE) (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => ((EQ|NE) (CMPW a (MULW x y)) yes no) // Absorb bit-tests into block (Z (ANDconst [c] x) yes no) && oneBit(c) => (TBZ [int64(ntz64(c))] x yes no) @@ -1343,10 +1350,6 @@ (FGT (InvertFlags cmp) yes no) => (FLT cmp yes no) (FLE (InvertFlags cmp) yes no) => (FGE cmp yes no) (FGE (InvertFlags cmp) yes no) => (FLE cmp yes no) -(LTnoov (InvertFlags cmp) yes no) => (GTnoov cmp yes no) -(GEnoov (InvertFlags cmp) yes no) => (LEnoov cmp yes no) -(LEnoov (InvertFlags cmp) yes no) => (GEnoov cmp yes no) -(GTnoov (InvertFlags cmp) yes no) => (LTnoov cmp yes no) // absorb InvertFlags into conditional instructions (CSEL [cc] x y (InvertFlags cmp)) => (CSEL [arm64Invert(cc)] x y cmp) @@ -1385,8 +1388,6 @@ (LessEqualF (InvertFlags x)) => (GreaterEqualF x) (GreaterThanF (InvertFlags x)) => (LessThanF x) (GreaterEqualF (InvertFlags x)) => (LessEqualF x) -(LessThanNoov (InvertFlags x)) => (CSEL0 [OpARM64NotEqual] (GreaterEqualNoov x) x) -(GreaterEqualNoov (InvertFlags x)) => (CSINC [OpARM64NotEqual] (LessThanNoov x) (MOVDconst [0]) x) // Don't bother extending if we're not using the higher bits. (MOV(B|BU)reg x) && v.Type.Size() <= 1 => x diff --git a/src/cmd/compile/internal/ssa/copyelim.go b/src/cmd/compile/internal/ssa/copyelim.go index 762ffe1bd2d84c..10abb8c2149df1 100644 --- a/src/cmd/compile/internal/ssa/copyelim.go +++ b/src/cmd/compile/internal/ssa/copyelim.go @@ -22,7 +22,7 @@ func copyelim(f *Func) { // Update named values. for _, name := range f.Names { - values := f.NamedValues[*name] + values := f.NamedValues[name] for i, v := range values { if v.Op == OpCopy { values[i] = v.Args[0] diff --git a/src/cmd/compile/internal/ssa/deadcode.go b/src/cmd/compile/internal/ssa/deadcode.go index aa85097c29d63e..b2d59579cd1ea0 100644 --- a/src/cmd/compile/internal/ssa/deadcode.go +++ b/src/cmd/compile/internal/ssa/deadcode.go @@ -213,7 +213,7 @@ func deadcode(f *Func) { for _, name := range f.Names { j := 0 s.clear() - values := f.NamedValues[*name] + values := f.NamedValues[name] for _, v := range values { if live[v.ID] && !s.contains(v.ID) { values[j] = v @@ -222,14 +222,14 @@ func deadcode(f *Func) { } } if j == 0 { - delete(f.NamedValues, *name) + delete(f.NamedValues, name) } else { f.Names[i] = name i++ for k := len(values) - 1; k >= j; k-- { values[k] = nil } - f.NamedValues[*name] = values[:j] + f.NamedValues[name] = values[:j] } } clear(f.Names[i:]) diff --git a/src/cmd/compile/internal/ssa/debug.go b/src/cmd/compile/internal/ssa/debug.go index f45bb4d388922b..d1c736af32a804 100644 --- a/src/cmd/compile/internal/ssa/debug.go +++ b/src/cmd/compile/internal/ssa/debug.go @@ -441,7 +441,7 @@ func PopulateABIInRegArgOps(f *Func) { // slots that is type-insenstitive. sc := newSlotCanonicalizer() for _, sl := range f.Names { - sc.lookup(*sl) + sc.lookup(sl) } // Add slot -> value entry to f.NamedValues if not already present. @@ -449,8 +449,7 @@ func PopulateABIInRegArgOps(f *Func) { values, ok := f.NamedValues[sl] if !ok { // Haven't seen this slot yet. - sla := f.localSlotAddr(sl) - f.Names = append(f.Names, sla) + f.Names = append(f.Names, sl) } else { for _, ev := range values { if v == ev { @@ -592,14 +591,14 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingLevel int, stackOffset func( state.slots = state.slots[:0] state.vars = state.vars[:0] for i, slot := range f.Names { - state.slots = append(state.slots, *slot) + state.slots = append(state.slots, slot) if ir.IsSynthetic(slot.N) || !IsVarWantedForDebug(slot.N) { continue } topSlot := slot for topSlot.SplitOf != nil { - topSlot = topSlot.SplitOf + topSlot = *topSlot.SplitOf } if _, ok := state.varParts[topSlot.N]; !ok { state.vars = append(state.vars, topSlot.N) @@ -660,7 +659,7 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingLevel int, stackOffset func( if ir.IsSynthetic(slot.N) || !IsVarWantedForDebug(slot.N) { continue } - for _, value := range f.NamedValues[*slot] { + for _, value := range f.NamedValues[slot] { state.valueNames[value.ID] = append(state.valueNames[value.ID], SlotID(i)) } } diff --git a/src/cmd/compile/internal/ssa/decompose.go b/src/cmd/compile/internal/ssa/decompose.go index 6ea69da0169fa3..ee1f1401e28a56 100644 --- a/src/cmd/compile/internal/ssa/decompose.go +++ b/src/cmd/compile/internal/ssa/decompose.go @@ -37,14 +37,14 @@ func decomposeBuiltin(f *Func) { // accumulate new LocalSlots in newNames for addition after the iteration. This decomposition is for // builtin types with leaf components, and thus there is no need to reprocess the newly create LocalSlots. var toDelete []namedVal - var newNames []*LocalSlot + var newNames []LocalSlot for i, name := range f.Names { t := name.Type switch { case t.IsInteger() && t.Size() > f.Config.RegSize: - hiName, loName := f.SplitInt64(name) + hiName, loName := f.SplitInt64(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, hiName, loName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpInt64Make { continue } @@ -53,9 +53,9 @@ func decomposeBuiltin(f *Func) { toDelete = append(toDelete, namedVal{i, j}) } case t.IsComplex(): - rName, iName := f.SplitComplex(name) + rName, iName := f.SplitComplex(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, rName, iName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpComplexMake { continue } @@ -64,9 +64,9 @@ func decomposeBuiltin(f *Func) { toDelete = append(toDelete, namedVal{i, j}) } case t.IsString(): - ptrName, lenName := f.SplitString(name) + ptrName, lenName := f.SplitString(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, ptrName, lenName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpStringMake { continue } @@ -75,10 +75,10 @@ func decomposeBuiltin(f *Func) { toDelete = append(toDelete, namedVal{i, j}) } case t.IsSlice(): - ptrName, lenName, capName := f.SplitSlice(name) + ptrName, lenName, capName := f.SplitSlice(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, ptrName, lenName) newNames = maybeAppend(f, newNames, capName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpSliceMake { continue } @@ -88,9 +88,9 @@ func decomposeBuiltin(f *Func) { toDelete = append(toDelete, namedVal{i, j}) } case t.IsInterface(): - typeName, dataName := f.SplitInterface(name) + typeName, dataName := f.SplitInterface(f.localSlotAddr(name)) newNames = maybeAppend2(f, newNames, typeName, dataName) - for j, v := range f.NamedValues[*name] { + for j, v := range f.NamedValues[name] { if v.Op != OpIMake { continue } @@ -109,15 +109,15 @@ func decomposeBuiltin(f *Func) { f.Names = append(f.Names, newNames...) } -func maybeAppend(f *Func, ss []*LocalSlot, s *LocalSlot) []*LocalSlot { +func maybeAppend(f *Func, ss []LocalSlot, s *LocalSlot) []LocalSlot { if _, ok := f.NamedValues[*s]; !ok { f.NamedValues[*s] = nil - return append(ss, s) + return append(ss, *s) } return ss } -func maybeAppend2(f *Func, ss []*LocalSlot, s1, s2 *LocalSlot) []*LocalSlot { +func maybeAppend2(f *Func, ss []LocalSlot, s1, s2 *LocalSlot) []LocalSlot { return maybeAppend(f, maybeAppend(f, ss, s1), s2) } @@ -244,14 +244,14 @@ func decomposeUser(f *Func) { } // Split up named values into their components. i := 0 - var newNames []*LocalSlot + var newNames []LocalSlot for _, name := range f.Names { t := name.Type switch { case isStructNotSIMD(t): - newNames = decomposeUserStructInto(f, name, newNames) + newNames = decomposeUserStructInto(f, f.localSlotAddr(name), newNames) case t.IsArray(): - newNames = decomposeUserArrayInto(f, name, newNames) + newNames = decomposeUserArrayInto(f, f.localSlotAddr(name), newNames) default: f.Names[i] = name i++ @@ -264,7 +264,7 @@ func decomposeUser(f *Func) { // decomposeUserArrayInto creates names for the element(s) of arrays referenced // by name where possible, and appends those new names to slots, which is then // returned. -func decomposeUserArrayInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*LocalSlot { +func decomposeUserArrayInto(f *Func, name *LocalSlot, slots []LocalSlot) []LocalSlot { t := name.Type if t.Size() == 0 { // TODO(khr): Not sure what to do here. Probably nothing. @@ -297,13 +297,13 @@ func decomposeUserArrayInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*Loc return decomposeUserStructInto(f, elemName, slots) } - return append(slots, elemName) + return append(slots, *elemName) } // decomposeUserStructInto creates names for the fields(s) of structs referenced // by name where possible, and appends those new names to slots, which is then // returned. -func decomposeUserStructInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*LocalSlot { +func decomposeUserStructInto(f *Func, name *LocalSlot, slots []LocalSlot) []LocalSlot { fnames := []*LocalSlot{} // slots for struct in name t := name.Type n := t.NumFields() @@ -429,19 +429,19 @@ func deleteNamedVals(f *Func, toDelete []namedVal) { // Get rid of obsolete names for _, d := range toDelete { loc := f.Names[d.locIndex] - vals := f.NamedValues[*loc] + vals := f.NamedValues[loc] l := len(vals) - 1 if l > 0 { vals[d.valIndex] = vals[l] } vals[l] = nil - f.NamedValues[*loc] = vals[:l] + f.NamedValues[loc] = vals[:l] } // Delete locations with no values attached. end := len(f.Names) for i := len(f.Names) - 1; i >= 0; i-- { loc := f.Names[i] - vals := f.NamedValues[*loc] + vals := f.NamedValues[loc] last := len(vals) for j := len(vals) - 1; j >= 0; j-- { if vals[j].Op == OpInvalid { @@ -451,13 +451,13 @@ func deleteNamedVals(f *Func, toDelete []namedVal) { } } if last < len(vals) { - f.NamedValues[*loc] = vals[:last] + f.NamedValues[loc] = vals[:last] } if len(vals) == 0 { - delete(f.NamedValues, *loc) + delete(f.NamedValues, loc) end-- f.Names[i] = f.Names[end] - f.Names[end] = nil + f.Names[end] = LocalSlot{} } } f.Names = f.Names[:end] diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 1c67691547a99c..0b4c26c2dc0b52 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -60,7 +60,7 @@ type Func struct { NamedValues map[LocalSlot][]*Value // Names is a copy of NamedValues.Keys. We keep a separate list // of keys to make iteration order deterministic. - Names []*LocalSlot + Names []LocalSlot // Canonicalize root/top-level local slots, and canonicalize their pieces. // Because LocalSlot pieces refer to their parents with a pointer, this ensures that equivalent slots really are equal. CanonicalLocalSlots map[LocalSlot]*LocalSlot @@ -181,6 +181,8 @@ func (f *Func) retPoset(po *poset) { f.Cache.scrPoset = append(f.Cache.scrPoset, po) } +// localSlotAddr returns a stable canonical *LocalSlot for slot, created on +// first use. SplitOf parents need it: f.Names holds values, not pointers. func (f *Func) localSlotAddr(slot LocalSlot) *LocalSlot { a, ok := f.CanonicalLocalSlots[slot] if !ok { diff --git a/src/cmd/compile/internal/ssa/memcombine.go b/src/cmd/compile/internal/ssa/memcombine.go index 288b7f145ab423..fd627c790c854d 100644 --- a/src/cmd/compile/internal/ssa/memcombine.go +++ b/src/cmd/compile/internal/ssa/memcombine.go @@ -13,20 +13,21 @@ import ( ) // memcombine combines smaller loads and stores into larger ones. -// We ensure this generates good code for encoding/binary operations. -// It may help other cases also. +// This produces good code for encoding/binary operations and may help other +// cases too. On architectures that do not allow unaligned accesses, the pass +// uses pointer alignment facts to avoid introducing unaligned wider operations. func memcombine(f *Func) { - // This optimization requires that the architecture has - // unaligned loads and unaligned stores. + var ptrAlignments []int8 if !f.Config.unalignedOK { - return + ptrAlignments = f.Cache.allocInt8Slice(f.NumValues()) + defer f.Cache.freeInt8Slice(ptrAlignments) + computePtrAlignments(f, ptrAlignments) } - - memcombineLoads(f) - memcombineStores(f) + memcombineLoads(f, ptrAlignments) + memcombineStores(f, ptrAlignments) } -func memcombineLoads(f *Func) { +func memcombineLoads(f *Func, ptrAlignments []int8) { // Find "OR trees" to start with. mark := f.newSparseSet(f.NumValues()) defer f.retSparseSet(mark) @@ -77,7 +78,7 @@ func memcombineLoads(f *Func) { continue } for n := max; n > 1; n /= 2 { - if combineLoads(v, n) { + if combineLoads(v, n, ptrAlignments) { break } } @@ -195,7 +196,90 @@ func splitPtr(ptr *Value) (BaseAddress, int64) { return BaseAddress{ptr: ptr, idx: idx}, off } -func combineLoads(root *Value, n int64) bool { +// computePtrAlignments computes pointer alignment facts from typed base pointers +// and constant offsets. +func computePtrAlignments(f *Func, ptrAlignments []int8) { + for _, b := range slices.Backward(f.postorder()) { + for _, v := range b.Values { + ptrAlignments[v.ID] = int8(valuePtrAlignment(v, ptrAlignments)) + } + } +} + +// ptrAlignment only reads already-computed facts. Zero/not-yet-known means +// alignment 1, avoiding recursive Phi/cycle walks. +func ptrAlignment(ptr *Value, ptrAlignments []int8) int64 { + if align := ptrAlignments[ptr.ID]; align > 0 { + return int64(align) + } + return 1 +} + +// valuePtrAlignment computes one entry in ptrAlignments. +func valuePtrAlignment(v *Value, ptrAlignments []int8) int64 { + // computePtrAlignments visits every SSA value, not just pointer values. + if !v.Type.IsPtr() { + return 1 + } + + switch v.Op { + case OpOffPtr: + return offsetAlignment(ptrAlignment(v.Args[0], ptrAlignments), v.AuxInt) + case OpCopy, OpNilCheck: + return ptrAlignment(v.Args[0], ptrAlignments) + case OpAddr, OpLocalAddr, OpArg, OpArgIntReg: + return typeAlignment(v.Type.Elem()) + case OpPhi: + align := ptrAlignment(v.Args[0], ptrAlignments) + for _, arg := range v.Args[1:] { + if argAlign := ptrAlignment(arg, ptrAlignments); argAlign < align { + align = argAlign + } + } + return align + } + return 1 +} + +// typeAlignment returns a conservative alignment for t without calling +// Type.Alignment, which may try to calculate type sizes while the compiler +// back end is running concurrently. +func typeAlignment(t *types.Type) int64 { + switch t.Kind() { + case types.TBOOL, types.TINT8, types.TUINT8: + return 1 + case types.TINT16, types.TUINT16: + return 2 + case types.TINT32, types.TUINT32, types.TFLOAT32, types.TCOMPLEX64: + return 4 + case types.TINT64, types.TUINT64, types.TFLOAT64, types.TCOMPLEX128: + return 8 + case types.TINT, types.TUINT, types.TUINTPTR, types.TPTR, types.TUNSAFEPTR, types.TSTRING, types.TSLICE, types.TFUNC, types.TMAP, types.TCHAN: + return int64(types.PtrSize) + case types.TARRAY: + return typeAlignment(t.Elem()) + case types.TSTRUCT: + align := int64(1) + for _, f := range t.Fields() { + fieldAlign := typeAlignment(f.Type) + if fieldAlign > align { + align = fieldAlign + } + } + return align + } + return 1 +} + +func offsetAlignment(align, off int64) int64 { + off &= align - 1 + if off == 0 { + return align + } + return off & -off +} + +func combineLoads(root *Value, n int64, ptrAlignments []int8) bool { orOp := root.Op var shiftOp Op switch orOp { @@ -307,6 +391,9 @@ func combineLoads(root *Value, n int64) bool { return false } } + if !root.Block.Func.Config.unalignedOK && ptrAlignment(r[0].load.Args[0], ptrAlignments) < n*size { + return false + } // Check for reads in little-endian or big-endian order. shift0 := r[0].shift @@ -394,7 +481,7 @@ func combineLoads(root *Value, n int64) bool { return true } -func memcombineStores(f *Func) { +func memcombineStores(f *Func, ptrAlignments []int8) { mark := f.newSparseSet(f.NumValues()) defer f.retSparseSet(mark) var order []*Value @@ -438,13 +525,13 @@ func memcombineStores(f *Func) { continue } - combineStores(v) + combineStores(v, ptrAlignments) } } } // combineStores tries to combine the stores ending in root. -func combineStores(root *Value) { +func combineStores(root *Value, ptrAlignments []int8) { // Helper functions. maxRegSize := root.Block.Func.Config.RegSize type StoreRecord struct { @@ -625,6 +712,9 @@ func combineStores(root *Value) { } // Memory location we're going to write at (the lowest one). ptr := a[0].store.Args[0] + if !root.Block.Func.Config.unalignedOK && ptrAlignment(ptr, ptrAlignments) < aTotalSize { + return + } // Check for constant stores isConst := true @@ -718,6 +808,9 @@ func combineStores(root *Value) { if loadMem != nil { // Modify the first load to do a larger load instead. load := a[0].store.Args[1] + if !root.Block.Func.Config.unalignedOK && ptrAlignment(load.Args[0], ptrAlignments) < aTotalSize { + return + } switch aTotalSize { case 2: load.Type = types.Types[types.TUINT16] diff --git a/src/cmd/compile/internal/ssa/print.go b/src/cmd/compile/internal/ssa/print.go index ed7f1542497cf3..4dd869124dedaa 100644 --- a/src/cmd/compile/internal/ssa/print.go +++ b/src/cmd/compile/internal/ssa/print.go @@ -187,6 +187,6 @@ func fprintFunc(p funcPrinter, f *Func) { p.endBlock(b, reachable[b.ID]) } for _, name := range f.Names { - p.named(*name, f.NamedValues[*name]) + p.named(name, f.NamedValues[name]) } } diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index 6f2143271115ff..a91eae73b5f0ee 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -259,18 +259,32 @@ func noLimitForBitsize(bitsize uint) limit { } func convertIntWithBitsize[Target uint64 | int64, Source uint64 | int64](x Source, bitsize uint) Target { - switch bitsize { - case 64: - return Target(x) - case 32: - return Target(int32(x)) - case 16: - return Target(int16(x)) - case 8: - return Target(int8(x)) - default: - panic("unreachable") - } + if Target(0)-1 < 0 { + // Signed target: sign-extend the low bitsize bits. + switch bitsize { + case 64: + return Target(int64(x)) + case 32: + return Target(int32(x)) + case 16: + return Target(int16(x)) + case 8: + return Target(int8(x)) + } + } else { + // Unsigned target: zero-extend the low bitsize bits. + switch bitsize { + case 64: + return Target(uint64(x)) + case 32: + return Target(uint32(x)) + case 16: + return Target(uint16(x)) + case 8: + return Target(uint8(x)) + } + } + panic("unreachable") } // unsignedFixedLeadingBits extracts the all the most significant fixed bits from the limit. diff --git a/src/cmd/compile/internal/ssa/prove_test.go b/src/cmd/compile/internal/ssa/prove_test.go index 42a60dcb40d11b..e9357a064b6db7 100644 --- a/src/cmd/compile/internal/ssa/prove_test.go +++ b/src/cmd/compile/internal/ssa/prove_test.go @@ -85,3 +85,19 @@ func TestLimitBitlenUnsigned(t *testing.T) { func TestLimitPopcountUnsigned(t *testing.T) { testLimitUnaryOpUnsigned8(t, "popcount", limit{-128, 127, 0, 8}, limit.popcount, func(x uint8) uint8 { return uint8(bits.OnesCount8(x)) }) } + +func TestConvertIntWithBitsize(t *testing.T) { + if got := convertIntWithBitsize[int64, uint64](255, 8); got != -1 { + t.Errorf("convertIntWithBitsize(255, 8) = %d; want -1", got) + } + if got := convertIntWithBitsize[uint64, int64](-1, 8); got != 255 { + t.Errorf("convertIntWithBitsize(-1, 8) = %d; want 255", got) + } + + if got := convertIntWithBitsize[int64, uint64](127, 8); got != 127 { + t.Errorf("convertIntWithBitsize(127, 8) = %d; want 127", got) + } + if got := convertIntWithBitsize[uint64, int64](127, 8); got != 127 { + t.Errorf("convertIntWithBitsize(127, 8) = %d; want 127", got) + } +} diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index 17448799db3792..e9091745487615 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -2119,7 +2119,7 @@ func isFixedLoad(v *Value, sym Sym, off int64) bool { for _, f := range rttype.Type.Fields() { if f.Offset == off && copyCompatibleType(v.Type, f.Type) { switch f.Sym.Name { - case "Size_", "PtrBytes", "Hash", "Kind_", "GCData": + case "Size_", "PtrBytes", "Hash", "Kind_", "GCData", "TFlag": return true default: // fmt.Println("unknown field", f.Sym.Name) @@ -2195,6 +2195,10 @@ func rewriteFixedLoad(v *Value, sym Sym, sb *Value, off int64) *Value { v.reset(OpConst32) v.AuxInt = int64(int32(types.TypeHash(t))) return v + case "TFlag": + v.reset(OpConst8) + v.AuxInt = int64(t.TFlag()) + return v case "Kind_": v.reset(OpConst8) v.AuxInt = int64(int8(reflectdata.ABIKindOfType(t))) diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index e37459035b8b0d..303db4728f8c12 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -8143,6 +8143,30 @@ func rewriteValueAMD64_OpAMD64ADDLmodify(v *Value) bool { func rewriteValueAMD64_OpAMD64ADDQ(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (ADDQ x (MOVBQZX (SETB flags))) + // result: (Select0 (ADCQconst [0] x flags)) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + x := v_0 + if v_1.Op != OpAMD64MOVBQZX { + continue + } + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64SETB { + continue + } + flags := v_1_0.Args[0] + v.reset(OpSelect0) + v0 := b.NewValue0(v.Pos, OpAMD64ADCQconst, types.NewTuple(typ.UInt64, types.TypeFlags)) + v0.AuxInt = int32ToAuxInt(0) + v0.AddArg2(x, flags) + v.AddArg(v0) + return true + } + break + } // match: (ADDQ (SHRQconst [1] x) (SHRQconst [1] x)) // result: (ANDQconst [-2] x) for { @@ -40322,6 +40346,26 @@ func rewriteValueAMD64_OpAMD64SUBQ(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + typ := &b.Func.Config.Types + // match: (SUBQ x (MOVBQZX (SETB flags))) + // result: (Select0 (SBBQconst [0] x flags)) + for { + x := v_0 + if v_1.Op != OpAMD64MOVBQZX { + break + } + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64SETB { + break + } + flags := v_1_0.Args[0] + v.reset(OpSelect0) + v0 := b.NewValue0(v.Pos, OpAMD64SBBQconst, types.NewTuple(typ.UInt64, types.TypeFlags)) + v0.AuxInt = int32ToAuxInt(0) + v0.AddArg2(x, flags) + v.AddArg(v0) + return true + } // match: (SUBQ x (MOVQconst [c])) // cond: is32Bit(c) // result: (SUBQconst x [int32(c)]) diff --git a/src/cmd/compile/internal/ssa/rewriteARM.go b/src/cmd/compile/internal/ssa/rewriteARM.go index da7dc28f97b42e..55cbda3e9a6dd5 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM.go +++ b/src/cmd/compile/internal/ssa/rewriteARM.go @@ -17183,217 +17183,6 @@ func rewriteBlockARM(b *Block) bool { b.resetWithControl(BlockARMLE, cmp) return true } - // match: (GE (CMPconst [0] l:(SUB x y)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMP x y) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUB { - break - } - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(MULS x y a)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMMULS { - break - } - a := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARMMUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBconst [c] x)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPconst [c] x) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBconst { - break - } - c := auxIntToInt32(l.AuxInt) - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPconst, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg(x) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftLL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftRL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftRA x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRA { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRA, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftLLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftRLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (CMPshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRAreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } // match: (GE (CMPconst [0] l:(ADD x y)) yes no) // cond: l.Uses==1 // result: (GEnoov (CMN x y) yes no) @@ -17949,272 +17738,16 @@ func rewriteBlockARM(b *Block) bool { b.resetWithControl(BlockARMGEnoov, v0) return true } - // match: (GE (CMPconst [0] l:(XORshiftRLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (TEQshiftRLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMXORshiftRLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMTEQshiftRLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - // match: (GE (CMPconst [0] l:(XORshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GEnoov (TEQshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMXORshiftRAreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMTEQshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGEnoov, v0) - return true - } - case BlockARMGEnoov: - // match: (GEnoov (FlagConstant [fc]) yes no) - // cond: fc.geNoov() - // result: (First yes no) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(fc.geNoov()) { - break - } - b.Reset(BlockFirst) - return true - } - // match: (GEnoov (FlagConstant [fc]) yes no) - // cond: !fc.geNoov() - // result: (First no yes) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(!fc.geNoov()) { - break - } - b.Reset(BlockFirst) - b.swapSuccessors() - return true - } - // match: (GEnoov (InvertFlags cmp) yes no) - // result: (LEnoov cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMLEnoov, cmp) - return true - } - case BlockARMGT: - // match: (GT (FlagConstant [fc]) yes no) - // cond: fc.gt() - // result: (First yes no) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(fc.gt()) { - break - } - b.Reset(BlockFirst) - return true - } - // match: (GT (FlagConstant [fc]) yes no) - // cond: !fc.gt() - // result: (First no yes) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(!fc.gt()) { - break - } - b.Reset(BlockFirst) - b.swapSuccessors() - return true - } - // match: (GT (InvertFlags cmp) yes no) - // result: (LT cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMLT, cmp) - return true - } - // match: (GT (CMPconst [0] l:(SUB x y)) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMP x y) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUB { - break - } - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(MULS x y a)) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMMULS { - break - } - a := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARMMUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBconst [c] x)) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPconst [c] x) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBconst { - break - } - c := auxIntToInt32(l.AuxInt) - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPconst, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg(x) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPshiftLL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPshiftRL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPshiftRA x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRA { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRA, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMGTnoov, v0) - return true - } - // match: (GT (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) + // match: (GE (CMPconst [0] l:(XORshiftRLreg x y z)) yes no) // cond: l.Uses==1 - // result: (GTnoov (CMPshiftLLreg x y z) yes no) + // result: (GEnoov (TEQshiftRLreg x y z) yes no) for b.Controls[0].Op == OpARMCMPconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { break } l := v_0.Args[0] - if l.Op != OpARMSUBshiftLLreg { + if l.Op != OpARMXORshiftRLreg { break } z := l.Args[2] @@ -18223,21 +17756,21 @@ func rewriteBlockARM(b *Block) bool { if !(l.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLLreg, types.TypeFlags) + v0 := b.NewValue0(v_0.Pos, OpARMTEQshiftRLreg, types.TypeFlags) v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGTnoov, v0) + b.resetWithControl(BlockARMGEnoov, v0) return true } - // match: (GT (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) + // match: (GE (CMPconst [0] l:(XORshiftRAreg x y z)) yes no) // cond: l.Uses==1 - // result: (GTnoov (CMPshiftRLreg x y z) yes no) + // result: (GEnoov (TEQshiftRAreg x y z) yes no) for b.Controls[0].Op == OpARMCMPconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { break } l := v_0.Args[0] - if l.Op != OpARMSUBshiftRLreg { + if l.Op != OpARMXORshiftRAreg { break } z := l.Args[2] @@ -18246,32 +17779,69 @@ func rewriteBlockARM(b *Block) bool { if !(l.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRLreg, types.TypeFlags) + v0 := b.NewValue0(v_0.Pos, OpARMTEQshiftRAreg, types.TypeFlags) v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGTnoov, v0) + b.resetWithControl(BlockARMGEnoov, v0) return true } - // match: (GT (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (GTnoov (CMPshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { + case BlockARMGEnoov: + // match: (GEnoov (FlagConstant [fc]) yes no) + // cond: fc.geNoov() + // result: (First yes no) + for b.Controls[0].Op == OpARMFlagConstant { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(fc.geNoov()) { break } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRAreg { + b.Reset(BlockFirst) + return true + } + // match: (GEnoov (FlagConstant [fc]) yes no) + // cond: !fc.geNoov() + // result: (First no yes) + for b.Controls[0].Op == OpARMFlagConstant { + v_0 := b.Controls[0] + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(!fc.geNoov()) { break } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { + b.Reset(BlockFirst) + b.swapSuccessors() + return true + } + case BlockARMGT: + // match: (GT (FlagConstant [fc]) yes no) + // cond: fc.gt() + // result: (First yes no) + for b.Controls[0].Op == OpARMFlagConstant { + v_0 := b.Controls[0] + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(fc.gt()) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMGTnoov, v0) + b.Reset(BlockFirst) + return true + } + // match: (GT (FlagConstant [fc]) yes no) + // cond: !fc.gt() + // result: (First no yes) + for b.Controls[0].Op == OpARMFlagConstant { + v_0 := b.Controls[0] + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(!fc.gt()) { + break + } + b.Reset(BlockFirst) + b.swapSuccessors() + return true + } + // match: (GT (InvertFlags cmp) yes no) + // result: (LT cmp yes no) + for b.Controls[0].Op == OpARMInvertFlags { + v_0 := b.Controls[0] + cmp := v_0.Args[0] + b.resetWithControl(BlockARMLT, cmp) return true } // match: (GT (CMPconst [0] l:(ADD x y)) yes no) @@ -18901,14 +18471,6 @@ func rewriteBlockARM(b *Block) bool { b.swapSuccessors() return true } - // match: (GTnoov (InvertFlags cmp) yes no) - // result: (LTnoov cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMLTnoov, cmp) - return true - } case BlockIf: // match: (If (Equal cc) yes no) // result: (EQ cc yes no) @@ -19006,243 +18568,32 @@ func rewriteBlockARM(b *Block) bool { // result: (First yes no) for b.Controls[0].Op == OpARMFlagConstant { v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(fc.le()) { - break - } - b.Reset(BlockFirst) - return true - } - // match: (LE (FlagConstant [fc]) yes no) - // cond: !fc.le() - // result: (First no yes) - for b.Controls[0].Op == OpARMFlagConstant { - v_0 := b.Controls[0] - fc := auxIntToFlagConstant(v_0.AuxInt) - if !(!fc.le()) { - break - } - b.Reset(BlockFirst) - b.swapSuccessors() - return true - } - // match: (LE (InvertFlags cmp) yes no) - // result: (GE cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMGE, cmp) - return true - } - // match: (LE (CMPconst [0] l:(SUB x y)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMP x y) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUB { - break - } - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(MULS x y a)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMMULS { - break - } - a := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARMMUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBconst [c] x)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPconst [c] x) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBconst { - break - } - c := auxIntToInt32(l.AuxInt) - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPconst, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg(x) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftLL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftRL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftRA x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRA { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRA, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLEnoov, v0) - return true - } - // match: (LE (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftLLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(fc.le()) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLEnoov, v0) + b.Reset(BlockFirst) return true } - // match: (LE (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftRLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { + // match: (LE (FlagConstant [fc]) yes no) + // cond: !fc.le() + // result: (First no yes) + for b.Controls[0].Op == OpARMFlagConstant { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { + fc := auxIntToFlagConstant(v_0.AuxInt) + if !(!fc.le()) { break } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLEnoov, v0) + b.Reset(BlockFirst) + b.swapSuccessors() return true } - // match: (LE (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LEnoov (CMPshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { + // match: (LE (InvertFlags cmp) yes no) + // result: (GE cmp yes no) + for b.Controls[0].Op == OpARMInvertFlags { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRAreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLEnoov, v0) + cmp := v_0.Args[0] + b.resetWithControl(BlockARMGE, cmp) return true } // match: (LE (CMPconst [0] l:(ADD x y)) yes no) @@ -19872,14 +19223,6 @@ func rewriteBlockARM(b *Block) bool { b.swapSuccessors() return true } - // match: (LEnoov (InvertFlags cmp) yes no) - // result: (GEnoov cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMGEnoov, cmp) - return true - } case BlockARMLT: // match: (LT (FlagConstant [fc]) yes no) // cond: fc.lt() @@ -19914,217 +19257,6 @@ func rewriteBlockARM(b *Block) bool { b.resetWithControl(BlockARMGT, cmp) return true } - // match: (LT (CMPconst [0] l:(SUB x y)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMP x y) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUB { - break - } - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(MULS x y a)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMMULS { - break - } - a := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARMMUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBconst [c] x)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPconst [c] x) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBconst { - break - } - c := auxIntToInt32(l.AuxInt) - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPconst, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg(x) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftLL x y [c])) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftLL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftRL x y [c])) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftRL x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRL { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRL, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftRA x y [c])) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftRA x y [c]) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRA { - break - } - c := auxIntToInt32(l.AuxInt) - y := l.Args[1] - x := l.Args[0] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRA, types.TypeFlags) - v0.AuxInt = int32ToAuxInt(c) - v0.AddArg2(x, y) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftLLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftLLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftLLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftLLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftRLreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftRLreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRLreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRLreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } - // match: (LT (CMPconst [0] l:(SUBshiftRAreg x y z)) yes no) - // cond: l.Uses==1 - // result: (LTnoov (CMPshiftRAreg x y z) yes no) - for b.Controls[0].Op == OpARMCMPconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - l := v_0.Args[0] - if l.Op != OpARMSUBshiftRAreg { - break - } - z := l.Args[2] - x := l.Args[0] - y := l.Args[1] - if !(l.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARMCMPshiftRAreg, types.TypeFlags) - v0.AddArg3(x, y, z) - b.resetWithControl(BlockARMLTnoov, v0) - return true - } // match: (LT (CMPconst [0] l:(ADD x y)) yes no) // cond: l.Uses==1 // result: (LTnoov (CMN x y) yes no) @@ -20752,14 +19884,6 @@ func rewriteBlockARM(b *Block) bool { b.swapSuccessors() return true } - // match: (LTnoov (InvertFlags cmp) yes no) - // result: (GTnoov cmp yes no) - for b.Controls[0].Op == OpARMInvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARMGTnoov, cmp) - return true - } case BlockARMNE: // match: (NE (CMPconst [0] (Equal cc)) yes no) // result: (EQ cc yes no) diff --git a/src/cmd/compile/internal/ssa/rewriteARM64.go b/src/cmd/compile/internal/ssa/rewriteARM64.go index 26d94b9ebf83fd..149de06c0aba9b 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM64.go +++ b/src/cmd/compile/internal/ssa/rewriteARM64.go @@ -5778,15 +5778,15 @@ func rewriteValueARM64_OpARM64Equal(v *Value) bool { v.AddArg(v0) return true } - // match: (Equal (CMPconst [0] z:(MSUB a x y))) + // match: (Equal (CMPWconst [0] z:(MADDW a x y))) // cond: z.Uses == 1 - // result: (Equal (CMP a (MUL x y))) + // result: (Equal (CMNW a (MULW x y))) for { - if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { + if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MSUB { + if z.Op != OpARM64MADDW { break } y := z.Args[2] @@ -5796,22 +5796,22 @@ func rewriteValueARM64_OpARM64Equal(v *Value) bool { break } v.reset(OpARM64Equal) - v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) + v0 := b.NewValue0(v.Pos, OpARM64CMNW, types.TypeFlags) + v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) v.AddArg(v0) return true } - // match: (Equal (CMPWconst [0] z:(MADDW a x y))) + // match: (Equal (CMPconst [0] z:(MSUB a x y))) // cond: z.Uses == 1 - // result: (Equal (CMNW a (MULW x y))) + // result: (Equal (CMP a (MUL x y))) for { - if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { + if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MADDW { + if z.Op != OpARM64MSUB { break } y := z.Args[2] @@ -5821,8 +5821,8 @@ func rewriteValueARM64_OpARM64Equal(v *Value) bool { break } v.reset(OpARM64Equal) - v0 := b.NewValue0(v.Pos, OpARM64CMNW, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) + v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) + v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) v.AddArg(v0) @@ -7689,31 +7689,6 @@ func rewriteValueARM64_OpARM64GreaterEqual(v *Value) bool { v.AddArg(v0) return true } - // match: (GreaterEqual (CMPconst [0] z:(MSUB a x y))) - // cond: z.Uses == 1 - // result: (GreaterEqualNoov (CMP a (MUL x y))) - for { - if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v.reset(OpARM64GreaterEqualNoov) - v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - v.AddArg(v0) - return true - } // match: (GreaterEqual (CMPWconst [0] z:(MADDW a x y))) // cond: z.Uses == 1 // result: (GreaterEqualNoov (CMNW a (MULW x y))) @@ -7739,31 +7714,6 @@ func rewriteValueARM64_OpARM64GreaterEqual(v *Value) bool { v.AddArg(v0) return true } - // match: (GreaterEqual (CMPWconst [0] z:(MSUBW a x y))) - // cond: z.Uses == 1 - // result: (GreaterEqualNoov (CMPW a (MULW x y))) - for { - if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v.reset(OpARM64GreaterEqualNoov) - v0 := b.NewValue0(v.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - v.AddArg(v0) - return true - } // match: (GreaterEqual (FlagConstant [fc])) // result: (MOVDconst [b2i(fc.ge())]) for { @@ -7805,8 +7755,6 @@ func rewriteValueARM64_OpARM64GreaterEqualF(v *Value) bool { } func rewriteValueARM64_OpARM64GreaterEqualNoov(v *Value) bool { v_0 := v.Args[0] - b := v.Block - typ := &b.Func.Config.Types // match: (GreaterEqualNoov (FlagConstant [fc])) // result: (MOVDconst [b2i(fc.geNoov())]) for { @@ -7818,22 +7766,6 @@ func rewriteValueARM64_OpARM64GreaterEqualNoov(v *Value) bool { v.AuxInt = int64ToAuxInt(b2i(fc.geNoov())) return true } - // match: (GreaterEqualNoov (InvertFlags x)) - // result: (CSINC [OpARM64NotEqual] (LessThanNoov x) (MOVDconst [0]) x) - for { - if v_0.Op != OpARM64InvertFlags { - break - } - x := v_0.Args[0] - v.reset(OpARM64CSINC) - v.AuxInt = opToAuxInt(OpARM64NotEqual) - v0 := b.NewValue0(v.Pos, OpARM64LessThanNoov, typ.Bool) - v0.AddArg(x) - v1 := b.NewValue0(v.Pos, OpARM64MOVDconst, typ.UInt64) - v1.AuxInt = int64ToAuxInt(0) - v.AddArg3(v0, v1, x) - return true - } return false } func rewriteValueARM64_OpARM64GreaterEqualU(v *Value) bool { @@ -8436,31 +8368,6 @@ func rewriteValueARM64_OpARM64LessThan(v *Value) bool { v.AddArg(v0) return true } - // match: (LessThan (CMPconst [0] z:(MSUB a x y))) - // cond: z.Uses == 1 - // result: (LessThanNoov (CMP a (MUL x y))) - for { - if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v.reset(OpARM64LessThanNoov) - v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - v.AddArg(v0) - return true - } // match: (LessThan (CMPWconst [0] z:(MADDW a x y))) // cond: z.Uses == 1 // result: (LessThanNoov (CMNW a (MULW x y))) @@ -8486,31 +8393,6 @@ func rewriteValueARM64_OpARM64LessThan(v *Value) bool { v.AddArg(v0) return true } - // match: (LessThan (CMPWconst [0] z:(MSUBW a x y))) - // cond: z.Uses == 1 - // result: (LessThanNoov (CMPW a (MULW x y))) - for { - if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v.reset(OpARM64LessThanNoov) - v0 := b.NewValue0(v.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - v.AddArg(v0) - return true - } // match: (LessThan (FlagConstant [fc])) // result: (MOVDconst [b2i(fc.lt())]) for { @@ -8552,8 +8434,6 @@ func rewriteValueARM64_OpARM64LessThanF(v *Value) bool { } func rewriteValueARM64_OpARM64LessThanNoov(v *Value) bool { v_0 := v.Args[0] - b := v.Block - typ := &b.Func.Config.Types // match: (LessThanNoov (FlagConstant [fc])) // result: (MOVDconst [b2i(fc.ltNoov())]) for { @@ -8565,20 +8445,6 @@ func rewriteValueARM64_OpARM64LessThanNoov(v *Value) bool { v.AuxInt = int64ToAuxInt(b2i(fc.ltNoov())) return true } - // match: (LessThanNoov (InvertFlags x)) - // result: (CSEL0 [OpARM64NotEqual] (GreaterEqualNoov x) x) - for { - if v_0.Op != OpARM64InvertFlags { - break - } - x := v_0.Args[0] - v.reset(OpARM64CSEL0) - v.AuxInt = opToAuxInt(OpARM64NotEqual) - v0 := b.NewValue0(v.Pos, OpARM64GreaterEqualNoov, typ.Bool) - v0.AddArg(x) - v.AddArg2(v0, x) - return true - } return false } func rewriteValueARM64_OpARM64LessThanU(v *Value) bool { @@ -14843,15 +14709,15 @@ func rewriteValueARM64_OpARM64NotEqual(v *Value) bool { v.AddArg(v0) return true } - // match: (NotEqual (CMPconst [0] z:(MSUB a x y))) + // match: (NotEqual (CMPWconst [0] z:(MADDW a x y))) // cond: z.Uses == 1 - // result: (NotEqual (CMP a (MUL x y))) + // result: (NotEqual (CMNW a (MULW x y))) for { - if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { + if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MSUB { + if z.Op != OpARM64MADDW { break } y := z.Args[2] @@ -14861,22 +14727,22 @@ func rewriteValueARM64_OpARM64NotEqual(v *Value) bool { break } v.reset(OpARM64NotEqual) - v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) + v0 := b.NewValue0(v.Pos, OpARM64CMNW, types.TypeFlags) + v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) v.AddArg(v0) return true } - // match: (NotEqual (CMPWconst [0] z:(MADDW a x y))) + // match: (NotEqual (CMPconst [0] z:(MSUB a x y))) // cond: z.Uses == 1 - // result: (NotEqual (CMNW a (MULW x y))) + // result: (NotEqual (CMP a (MUL x y))) for { - if v_0.Op != OpARM64CMPWconst || auxIntToInt32(v_0.AuxInt) != 0 { + if v_0.Op != OpARM64CMPconst || auxIntToInt64(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MADDW { + if z.Op != OpARM64MSUB { break } y := z.Args[2] @@ -14886,8 +14752,8 @@ func rewriteValueARM64_OpARM64NotEqual(v *Value) bool { break } v.reset(OpARM64NotEqual) - v0 := b.NewValue0(v.Pos, OpARM64CMNW, types.TypeFlags) - v1 := b.NewValue0(v.Pos, OpARM64MULW, x.Type) + v0 := b.NewValue0(v.Pos, OpARM64CMP, types.TypeFlags) + v1 := b.NewValue0(v.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) v.AddArg(v0) @@ -24785,16 +24651,16 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64EQ, v0) return true } - // match: (EQ (CMPconst [0] z:(MSUB a x y)) yes no) + // match: (EQ (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 - // result: (EQ (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { + // result: (EQ (CMNW a (MULW x y)) yes no) + for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { + if auxIntToInt32(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MSUB { + if z.Op != OpARM64MADDW { break } y := z.Args[2] @@ -24803,23 +24669,23 @@ func rewriteBlockARM64(b *Block) bool { if !(z.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) + v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) + v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) b.resetWithControl(BlockARM64EQ, v0) return true } - // match: (EQ (CMPWconst [0] z:(MADDW a x y)) yes no) + // match: (EQ (CMPconst [0] z:(MSUB a x y)) yes no) // cond: z.Uses==1 - // result: (EQ (CMNW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { + // result: (EQ (CMP a (MUL x y)) yes no) + for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { + if auxIntToInt64(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MADDW { + if z.Op != OpARM64MSUB { break } y := z.Args[2] @@ -24828,8 +24694,8 @@ func rewriteBlockARM64(b *Block) bool { if !(z.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) + v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) + v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) b.resetWithControl(BlockARM64EQ, v0) @@ -25187,31 +25053,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GEnoov, v0) return true } - // match: (GE (CMPconst [0] z:(MSUB a x y)) yes no) - // cond: z.Uses==1 - // result: (GEnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { - v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GEnoov, v0) - return true - } // match: (GE (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 // result: (GEnoov (CMNW a (MULW x y)) yes no) @@ -25237,31 +25078,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GEnoov, v0) return true } - // match: (GE (CMPWconst [0] z:(MSUBW a x y)) yes no) - // cond: z.Uses==1 - // result: (GEnoov (CMPW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GEnoov, v0) - return true - } // match: (GE (CMPWconst [0] x) yes no) // result: (TBZ [31] x yes no) for b.Controls[0].Op == OpARM64CMPWconst { @@ -25345,14 +25161,6 @@ func rewriteBlockARM64(b *Block) bool { b.swapSuccessors() return true } - // match: (GEnoov (InvertFlags cmp) yes no) - // result: (LEnoov cmp yes no) - for b.Controls[0].Op == OpARM64InvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARM64LEnoov, cmp) - return true - } case BlockARM64GT: // match: (GT (CMPconst [0] z:(AND x y)) yes no) // cond: z.Uses == 1 @@ -25583,31 +25391,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GTnoov, v0) return true } - // match: (GT (CMPconst [0] z:(MSUB a x y)) yes no) - // cond: z.Uses==1 - // result: (GTnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { - v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GTnoov, v0) - return true - } // match: (GT (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 // result: (GTnoov (CMNW a (MULW x y)) yes no) @@ -25633,31 +25416,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GTnoov, v0) return true } - // match: (GT (CMPWconst [0] z:(MSUBW a x y)) yes no) - // cond: z.Uses==1 - // result: (GTnoov (CMPW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GTnoov, v0) - return true - } // match: (GT (FlagConstant [fc]) yes no) // cond: fc.gt() // result: (First yes no) @@ -25717,14 +25475,6 @@ func rewriteBlockARM64(b *Block) bool { b.swapSuccessors() return true } - // match: (GTnoov (InvertFlags cmp) yes no) - // result: (LTnoov cmp yes no) - for b.Controls[0].Op == OpARM64InvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARM64LTnoov, cmp) - return true - } case BlockIf: // match: (If (Equal cc) yes no) // result: (EQ cc yes no) @@ -26089,31 +25839,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LEnoov, v0) return true } - // match: (LE (CMPconst [0] z:(MSUB a x y)) yes no) - // cond: z.Uses==1 - // result: (LEnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { - v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LEnoov, v0) - return true - } // match: (LE (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 // result: (LEnoov (CMNW a (MULW x y)) yes no) @@ -26139,31 +25864,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LEnoov, v0) return true } - // match: (LE (CMPWconst [0] z:(MSUBW a x y)) yes no) - // cond: z.Uses==1 - // result: (LEnoov (CMPW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LEnoov, v0) - return true - } // match: (LE (FlagConstant [fc]) yes no) // cond: fc.le() // result: (First yes no) @@ -26223,14 +25923,6 @@ func rewriteBlockARM64(b *Block) bool { b.swapSuccessors() return true } - // match: (LEnoov (InvertFlags cmp) yes no) - // result: (GEnoov cmp yes no) - for b.Controls[0].Op == OpARM64InvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARM64GEnoov, cmp) - return true - } case BlockARM64LT: // match: (LT (CMPconst [0] z:(AND x y)) yes no) // cond: z.Uses == 1 @@ -26461,31 +26153,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LTnoov, v0) return true } - // match: (LT (CMPconst [0] z:(MSUB a x y)) yes no) - // cond: z.Uses==1 - // result: (LTnoov (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { - v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUB { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LTnoov, v0) - return true - } // match: (LT (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 // result: (LTnoov (CMNW a (MULW x y)) yes no) @@ -26511,31 +26178,6 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LTnoov, v0) return true } - // match: (LT (CMPWconst [0] z:(MSUBW a x y)) yes no) - // cond: z.Uses==1 - // result: (LTnoov (CMPW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { - v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { - break - } - z := v_0.Args[0] - if z.Op != OpARM64MSUBW { - break - } - y := z.Args[2] - a := z.Args[0] - x := z.Args[1] - if !(z.Uses == 1) { - break - } - v0 := b.NewValue0(v_0.Pos, OpARM64CMPW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) - v1.AddArg2(x, y) - v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LTnoov, v0) - return true - } // match: (LT (CMPWconst [0] x) yes no) // result: (TBNZ [31] x yes no) for b.Controls[0].Op == OpARM64CMPWconst { @@ -26619,14 +26261,6 @@ func rewriteBlockARM64(b *Block) bool { b.swapSuccessors() return true } - // match: (LTnoov (InvertFlags cmp) yes no) - // result: (GTnoov cmp yes no) - for b.Controls[0].Op == OpARM64InvertFlags { - v_0 := b.Controls[0] - cmp := v_0.Args[0] - b.resetWithControl(BlockARM64GTnoov, cmp) - return true - } case BlockARM64NE: // match: (NE (CMPconst [0] z:(AND x y)) yes no) // cond: z.Uses == 1 @@ -26919,16 +26553,16 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64NE, v0) return true } - // match: (NE (CMPconst [0] z:(MSUB a x y)) yes no) + // match: (NE (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 - // result: (NE (CMP a (MUL x y)) yes no) - for b.Controls[0].Op == OpARM64CMPconst { + // result: (NE (CMNW a (MULW x y)) yes no) + for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] - if auxIntToInt64(v_0.AuxInt) != 0 { + if auxIntToInt32(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MSUB { + if z.Op != OpARM64MADDW { break } y := z.Args[2] @@ -26937,23 +26571,23 @@ func rewriteBlockARM64(b *Block) bool { if !(z.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) + v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) + v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) b.resetWithControl(BlockARM64NE, v0) return true } - // match: (NE (CMPWconst [0] z:(MADDW a x y)) yes no) + // match: (NE (CMPconst [0] z:(MSUB a x y)) yes no) // cond: z.Uses==1 - // result: (NE (CMNW a (MULW x y)) yes no) - for b.Controls[0].Op == OpARM64CMPWconst { + // result: (NE (CMP a (MUL x y)) yes no) + for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] - if auxIntToInt32(v_0.AuxInt) != 0 { + if auxIntToInt64(v_0.AuxInt) != 0 { break } z := v_0.Args[0] - if z.Op != OpARM64MADDW { + if z.Op != OpARM64MSUB { break } y := z.Args[2] @@ -26962,8 +26596,8 @@ func rewriteBlockARM64(b *Block) bool { if !(z.Uses == 1) { break } - v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) - v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) + v0 := b.NewValue0(v_0.Pos, OpARM64CMP, types.TypeFlags) + v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) b.resetWithControl(BlockARM64NE, v0) diff --git a/src/cmd/compile/internal/ssa/stackalloc.go b/src/cmd/compile/internal/ssa/stackalloc.go index 06097e95da0bc1..8b3d613d5a76b4 100644 --- a/src/cmd/compile/internal/ssa/stackalloc.go +++ b/src/cmd/compile/internal/ssa/stackalloc.go @@ -157,7 +157,7 @@ func (s *stackAllocState) stackalloc() { for _, name := range f.Names { // Note: not "range f.NamedValues" above, because // that would be nondeterministic. - for _, v := range f.NamedValues[*name] { + for _, v := range f.NamedValues[name] { if v.Op == OpArgIntReg || v.Op == OpArgFloatReg { aux := v.Aux.(*AuxNameOffset) // Never let an arg be bound to a differently named thing. @@ -177,9 +177,9 @@ func (s *stackAllocState) stackalloc() { if names[v.ID] == empty { if f.pass.debug > stackDebug { - fmt.Printf("stackalloc value %s to name %s\n", v, *name) + fmt.Printf("stackalloc value %s to name %s\n", v, name) } - names[v.ID] = *name + names[v.ID] = name } } } diff --git a/src/cmd/compile/internal/ssa/writebarrier.go b/src/cmd/compile/internal/ssa/writebarrier.go index ec5a0fed29d791..a735c41d36903f 100644 --- a/src/cmd/compile/internal/ssa/writebarrier.go +++ b/src/cmd/compile/internal/ssa/writebarrier.go @@ -326,31 +326,25 @@ func writebarrier(f *Func) { tmp *Value // address of temporary we've copied the volatile value into } var volatiles []volatileCopy - - if !(f.ABIDefault == f.ABI1 && len(f.Config.intParamRegs) >= 3) { - // We don't need to do this if the calls we're going to do take - // all their arguments in registers. - // 3 is the magic number because it covers wbZero, wbMove, cgoCheckMemmove. - copyLoop: - for _, w := range stores { - if w.Op == OpMoveWB { - val := w.Args[1] - if isVolatile(val) { - for _, c := range volatiles { - if val == c.src { - continue copyLoop // already copied - } + copyLoop: + for _, w := range stores { + if w.Op == OpMoveWB { + val := w.Args[1] + if isVolatile(val) { + for _, c := range volatiles { + if val == c.src { + continue copyLoop // already copied } - - t := val.Type.Elem() - tmp := f.NewLocal(w.Pos, t) - mem = b.NewValue1A(w.Pos, OpVarDef, types.TypeMem, tmp, mem) - tmpaddr := b.NewValue2A(w.Pos, OpLocalAddr, t.PtrTo(), tmp, sp, mem) - siz := t.Size() - mem = b.NewValue3I(w.Pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem) - mem.Aux = t - volatiles = append(volatiles, volatileCopy{val, tmpaddr}) } + + t := val.Type.Elem() + tmp := f.NewLocal(w.Pos, t) + mem = b.NewValue1A(w.Pos, OpVarDef, types.TypeMem, tmp, mem) + tmpaddr := b.NewValue2A(w.Pos, OpLocalAddr, t.PtrTo(), tmp, sp, mem) + siz := t.Size() + mem = b.NewValue3I(w.Pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem) + mem.Aux = t + volatiles = append(volatiles, volatileCopy{val, tmpaddr}) } } } diff --git a/src/cmd/compile/internal/ssagen/arch.go b/src/cmd/compile/internal/ssagen/arch.go index ef5d8f59d7168a..fea2630bea26c8 100644 --- a/src/cmd/compile/internal/ssagen/arch.go +++ b/src/cmd/compile/internal/ssagen/arch.go @@ -53,4 +53,11 @@ type ArchInfo struct { // SpillArgReg emits instructions that spill reg to n+off. SpillArgReg func(pp *objw.Progs, p *obj.Prog, f *ssa.Func, t *types.Type, reg int16, n *ir.Name, off int64) *obj.Prog + + // SSAGenFinish, if non-nil, is called after all of a function's Progs + // have been generated and defframe has run: branch and jump-table + // targets are resolved and the frame size is final. It allows the + // backend to run a last Prog-level pass. Used on arm64 to fuse adjacent + // spill/reload MOVDs into STP/LDP. + SSAGenFinish func(pp *objw.Progs) } diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 7d523e56928d79..0deb520471dde6 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -6688,8 +6688,7 @@ func (s *state) addNamedValue(n *ir.Name, v *ssa.Value) { loc := ssa.LocalSlot{N: n, Type: n.Type(), Off: 0} values, ok := s.f.NamedValues[loc] if !ok { - s.f.Names = append(s.f.Names, &loc) - s.f.CanonicalLocalSlots[loc] = &loc + s.f.Names = append(s.f.Names, loc) } s.f.NamedValues[loc] = append(values, v) } @@ -7396,6 +7395,18 @@ func genssa(f *ssa.Func, pp *objw.Progs) { fi.JumpTables = append(fi.JumpTables, obj.JumpTable{Sym: jt.Aux.(*obj.LSym), Targets: targets}) } + // Finalize the frame, then let the backend run a final pass over the + // generated Progs (e.g. arm64 fuses adjacent spill/reload MOVDs into + // STP/LDP). Branch and jump-table targets are resolved at this point. + // Doing this before the debug dumps below means -S and GOSSAFUNC's genssa + // output reflect the instructions that are actually assembled. defframe + // must run first: it finalizes the frame size, which the backend pass + // depends on. + defframe(&s, e, f) + if Arch.SSAGenFinish != nil { + Arch.SSAGenFinish(s.pp) + } + if e.log { // spew to stdout filename := "" for p := s.pp.Text; p != nil; p = p.Link { @@ -7515,8 +7526,6 @@ func genssa(f *ssa.Func, pp *objw.Progs) { } } - defframe(&s, e, f) - f.HTMLWriter.Close() f.HTMLWriter = nil } diff --git a/src/cmd/compile/internal/test/bench_test.go b/src/cmd/compile/internal/test/bench_test.go index 1d74d39a49fe78..22db15f9e296d3 100644 --- a/src/cmd/compile/internal/test/bench_test.go +++ b/src/cmd/compile/internal/test/bench_test.go @@ -204,6 +204,37 @@ func containsLeft(strs []string, str string) bool { return false } +//go:noinline +func benchSpillReloadSink() {} + +// benchSpillReloadDistinctAutos loads two values from independent pointers, +// spills both across a call, and returns them as a pair. The spills and +// reloads do not exist when the SSA pair pass runs (regalloc inserts them +// later), so only the late pair pass in cmd/compile/internal/arm64 can fuse +// the reloads into a single LDP. +// +//go:noinline +func benchSpillReloadDistinctAutos(p, q *int) (int, int) { + a := *p + b := *q + benchSpillReloadSink() + return a, b +} + +// BenchmarkSpillReloadPair exercises the spill/reload pattern the late +// arm64 pair pass fuses but the SSA pair pass cannot. The numbers are not +// meaningful in absolute terms; the benchmark exists so builds with and +// without the pass can be compared. +func BenchmarkSpillReloadPair(b *testing.B) { + x, y := 1, 2 + var s int + for b.Loop() { + a, c := benchSpillReloadDistinctAutos(&x, &y) + s += a + c + } + globl = int64(s) +} + // BenchmarkStringEqParamOrder tests that the operand order of string // equality comparisons does not affect performance. See issue #74471. func BenchmarkStringEqParamOrder(b *testing.B) { diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go index 27943f2902c85c..b8b8dc2dafe051 100644 --- a/src/cmd/compile/internal/typecheck/subr.go +++ b/src/cmd/compile/internal/typecheck/subr.go @@ -96,9 +96,10 @@ func AddImplicitDots(n *ir.SelectorExpr) *ir.SelectorExpr { // CalcMethods calculates all the methods (including embedding) of a non-interface // type t. func CalcMethods(t *types.Type) { - if t == nil || len(t.AllMethods()) != 0 { + if t == nil || t.MethodsComputed() { return } + defer t.SetMethodsComputed(true) // mark top-level method symbols // so that expand1 doesn't consider them. diff --git a/src/cmd/compile/internal/types/size.go b/src/cmd/compile/internal/types/size.go index 4d64c7f8d28cc9..c52ce6e8c4170d 100644 --- a/src/cmd/compile/internal/types/size.go +++ b/src/cmd/compile/internal/types/size.go @@ -469,10 +469,10 @@ func simdify(st *Type, isTag bool) { st.align = 8 st.alg = ANOALG // not comparable with == st.intRegs = 0 - st.isSIMD = true + st.flags |= typeIsSIMD if isTag { st.width = 0 - st.isSIMDTag = true + st.flags |= typeIsSIMDTag st.floatRegs = 0 } else { st.floatRegs = 1 @@ -584,7 +584,7 @@ func CalcStructSize(t *Type) { } } - if len(t.Fields()) >= 1 && t.Fields()[0].Type.isSIMDTag { + if len(t.Fields()) >= 1 && t.Fields()[0].Type.flags&typeIsSIMDTag != 0 { // this catches `type Foo simd.Whatever` -- Foo is also SIMD. simdify(t, false) } diff --git a/src/cmd/compile/internal/types/tflag.go b/src/cmd/compile/internal/types/tflag.go new file mode 100644 index 00000000000000..15b53ebe3625db --- /dev/null +++ b/src/cmd/compile/internal/types/tflag.go @@ -0,0 +1,82 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import ( + "cmd/compile/internal/base" + "internal/abi" + "strings" + "sync" +) + +// tflagComputed is set in the high bit of Type.tflag once the low +// bits hold a valid abi.TFlag. abi.TFlag itself uses only the low 6. +const tflagComputed uint8 = 1 << 7 + +var tflagMu sync.Mutex + +// TFlag returns the abi.TFlag value for t's runtime type. Callers +// must have run typecheck.CalcMethods on ReceiverBaseType(t). +func (t *Type) TFlag() abi.TFlag { + tflagMu.Lock() + defer tflagMu.Unlock() + if t.tflag&tflagComputed != 0 { + return abi.TFlag(t.tflag &^ tflagComputed) + } + tflag := computeTFlag(t) + t.tflag = uint8(tflag) | tflagComputed + return tflag +} + +func computeTFlag(t *Type) abi.TFlag { + var tflag abi.TFlag + if hasUncommon(t) { + tflag |= abi.TFlagUncommon + } + if t.Sym() != nil && t.Sym().Name != "" { + tflag |= abi.TFlagNamed + } + if t.alg == AMEM { + tflag |= abi.TFlagRegularMemory + } + if PtrDataSize(t)/int64(PtrSize) > abi.MaxPtrmaskBytes*8 { + tflag |= abi.TFlagGCMaskOnDemand + } + if !strings.HasPrefix(t.NameString(), "*") { + tflag |= abi.TFlagExtraStar + } + if IsDirectIface(t) { + tflag |= abi.TFlagDirectIface + } + return tflag +} + +// hasUncommon reports whether t needs an abi.UncommonType. +// See TFlag for the precondition. +func hasUncommon(t *Type) bool { + if t.Sym() != nil { + return true + } + if t.HasShape() { + return false + } + mt := ReceiverBaseType(t) + if mt == nil { + return false + } + if !mt.MethodsComputed() { + base.Fatalf("hasUncommon: methods not computed on %v", mt) + } + for _, f := range mt.AllMethods() { + if f.Nointerface() { + continue + } + if !IsMethodApplicable(t, f) { + continue + } + return true + } + return false +} diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go index 3fa0b90e12938e..6b13b3b0f6799b 100644 --- a/src/cmd/compile/internal/types/type.go +++ b/src/cmd/compile/internal/types/type.go @@ -200,9 +200,10 @@ type Type struct { intRegs, floatRegs uint8 // registers needed for ABIInternal - flags bitset8 - alg AlgKind // valid if Align > 0 - isSIMDTag, isSIMD bool // tag is the marker type, isSIMD means has marker type + flags bitset16 + alg AlgKind // valid if Align > 0 + + tflag uint8 // size of prefix of object that contains all pointers. valid if Align > 0. // Note that for pointers, this is always PtrSize even if the element type @@ -233,6 +234,9 @@ const ( // typeIsFullyInstantiated reports whether a type is fully instantiated generic type; i.e. // an instantiated generic type where all type arguments are non-generic or fully instantiated generic types. typeIsFullyInstantiated + typeIsSIMDTag // type is the SIMD marker type + typeIsSIMD // type contains the SIMD marker type + typeMethodsComputed ) func (t *Type) NotInHeap() bool { return t.flags&typeNotInHeap != 0 } @@ -242,12 +246,14 @@ func (t *Type) Recur() bool { return t.flags&typeRecur != 0 } func (t *Type) IsShape() bool { return t.flags&typeIsShape != 0 } func (t *Type) HasShape() bool { return t.flags&typeHasShape != 0 } func (t *Type) IsFullyInstantiated() bool { return t.flags&typeIsFullyInstantiated != 0 } +func (t *Type) MethodsComputed() bool { return t.flags&typeMethodsComputed != 0 } func (t *Type) SetNotInHeap(b bool) { t.flags.set(typeNotInHeap, b) } func (t *Type) SetNoalg(b bool) { t.flags.set(typeNoalg, b) } func (t *Type) SetDeferwidth(b bool) { t.flags.set(typeDeferwidth, b) } func (t *Type) SetRecur(b bool) { t.flags.set(typeRecur, b) } func (t *Type) SetIsFullyInstantiated(b bool) { t.flags.set(typeIsFullyInstantiated, b) } +func (t *Type) SetMethodsComputed(b bool) { t.flags.set(typeMethodsComputed, b) } // Should always do SetHasShape(true) when doing SetIsShape(true). func (t *Type) SetIsShape(b bool) { t.flags.set(typeIsShape, b) } @@ -597,7 +603,7 @@ func newSSA(name string) *Type { func newSIMD(name string) *Type { t := newSSA(name) - t.isSIMD = true + t.flags |= typeIsSIMD return t } @@ -1683,8 +1689,8 @@ func (t *Type) SetUnderlying(underlying *Type) { if underlying.HasShape() { t.SetHasShape(true) } - if underlying.isSIMD { - simdify(t, underlying.isSIMDTag) + if underlying.flags&typeIsSIMD != 0 { + simdify(t, underlying.flags&typeIsSIMDTag != 0) } // spec: "The declared type does not inherit any methods bound @@ -1988,5 +1994,5 @@ var SimType [NTYPE]Kind var ShapePkg = NewPkg("go.shape", "go.shape") func (t *Type) IsSIMD() bool { - return t.isSIMD + return t.flags&typeIsSIMD != 0 } diff --git a/src/cmd/compile/internal/types/utils.go b/src/cmd/compile/internal/types/utils.go index f9f629ca3ea6cf..58c9641c42f4ca 100644 --- a/src/cmd/compile/internal/types/utils.go +++ b/src/cmd/compile/internal/types/utils.go @@ -15,3 +15,13 @@ func (f *bitset8) set(mask uint8, b bool) { *(*uint8)(f) &^= mask } } + +type bitset16 uint16 + +func (f *bitset16) set(mask uint16, b bool) { + if b { + *(*uint16)(f) |= mask + } else { + *(*uint16)(f) &^= mask + } +} diff --git a/src/cmd/compile/internal/walk/walk.go b/src/cmd/compile/internal/walk/walk.go index 259649019a414b..68e8545cd8d690 100644 --- a/src/cmd/compile/internal/walk/walk.go +++ b/src/cmd/compile/internal/walk/walk.go @@ -337,11 +337,17 @@ func mayCall(n ir.Node) bool { return true case ir.OINDEX, ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR, ir.OSLICESTR, - ir.ODEREF, ir.ODOTPTR, ir.ODOTTYPE, ir.ODYNAMICDOTTYPE, ir.ODIV, ir.OMOD, + ir.ODEREF, ir.ODOTPTR, ir.ODOTTYPE, ir.ODYNAMICDOTTYPE, ir.OMOD, ir.OSLICE2ARR, ir.OSLICE2ARRPTR: // These ops might panic, make sure they are done // before we start marshaling args for a call. See issue 16760. return true + case ir.ODIV: + n := n.(*ir.BinaryExpr) + if types.IsFloat[n.X.Type().Kind()] { + return ssagen.Arch.SoftFloat + } + return true case ir.OANDAND, ir.OOROR: n := n.(*ir.LogicalExpr) diff --git a/src/cmd/compile/testdata/script/issue28698.txt b/src/cmd/compile/testdata/script/issue28698.txt new file mode 100644 index 00000000000000..87b39bd0c1e71b --- /dev/null +++ b/src/cmd/compile/testdata/script/issue28698.txt @@ -0,0 +1,15 @@ +# Float division does not require a temporary when preparing call arguments. + +go tool compile -W p.go +stdout 'DIV float64' +! stdout 'autotmp_.*float64' + +-- p.go -- +package p + +//go:noinline +func sink(i int, f float64) {} + +func div(i int, x, y float64) { + sink(i, x/y) +} diff --git a/src/cmd/go/internal/doc/pkgsite.go b/src/cmd/go/internal/doc/pkgsite.go index 8bfae93029bdc5..bb006607824e8d 100644 --- a/src/cmd/go/internal/doc/pkgsite.go +++ b/src/cmd/go/internal/doc/pkgsite.go @@ -93,6 +93,12 @@ func buildPkgsite(ctx context.Context) string { a := b.LinkAction(loader, work.ModeBuild, work.ModeBuild, p) a.CacheExecutable = true b.Do(ctx, a) + + // Both paths return an executable in GOCACHE: CachedExecutable is set on + // fresh builds, while BuiltTarget is set on cache hits. + if cached := a.CachedExecutable(); cached != "" { + return cached + } return a.BuiltTarget() } @@ -139,6 +145,9 @@ func doPkgsite(ctx context.Context, urlPath, fragment string) error { pkgsite := buildPkgsite(ctx) if os.Getenv("TEST_GODOC_BUILD_ONLY") != "" { + if _, err := os.Stat(pkgsite); err != nil { + return fmt.Errorf("built pkgsite binary does not exist: %w", err) + } return nil } cmd := exec.Command(pkgsite, "-gorepo", cfg.GOROOT, "-http", addr, "-open", path) diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index 2473c243e31159..2084bc68a91217 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -1733,9 +1733,11 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action) if bytes.HasPrefix(out, tooManyFuzzTestsToFuzz[1:]) || bytes.Contains(out, tooManyFuzzTestsToFuzz) { norun = "[-fuzz matches more than one fuzz test, won't fuzz]" } - if len(out) > 0 && !bytes.HasSuffix(out, []byte("\n")) { - // Ensure that the output ends with a newline before the "ok" - // line we're about to print (https://golang.org/issue/49317). + // Ensure the output ends with a newline before the "ok" line + // (https://golang.org/issue/49317). Check buf, which holds the + // output still to be printed; out may include output that was + // discarded above (https://go.dev/issue/79786). + if buf.Len() > 0 && !bytes.HasSuffix(buf.Bytes(), []byte("\n")) { cmd.Stdout.Write([]byte("\n")) } fmt.Fprintf(cmd.Stdout, "ok \t%s\t%s%s%s\n", a.Package.ImportPath, t, coveragePercentage(out), norun) diff --git a/src/cmd/go/internal/tool/tool.go b/src/cmd/go/internal/tool/tool.go index 97c27c8caa44d6..afc50d9b2d9177 100644 --- a/src/cmd/go/internal/tool/tool.go +++ b/src/cmd/go/internal/tool/tool.go @@ -22,6 +22,7 @@ import ( "slices" "sort" "strings" + "time" "cmd/go/internal/base" "cmd/go/internal/cfg" @@ -392,15 +393,30 @@ func runBuiltTool(toolName string, env, cmdline []string) error { return nil } - toolCmd := &exec.Cmd{ - Path: cmdline[0], - Args: cmdline, - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, - Env: env, + // The tool was just linked and cached into $GOCACHE (CacheExecutable), and + // is executed from there. A concurrent go process may still hold a writable + // descriptor to the same cached file, so the exec can fail with ETXTBSY + // ("text file busy"). Retry a few times with backoff, matching base.RunStdin + // and (*runTestActor).Act in cmd/go/internal/test. See #22220, #22315, #78204. + var toolCmd *exec.Cmd + var err error + for try := range 3 { + toolCmd = &exec.Cmd{ + Path: cmdline[0], + Args: cmdline, + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + Env: env, + } + err = toolCmd.Start() + if err == nil || !base.IsETXTBSY(err) { + break + } + // Another go process likely still has the cached file open for + // writing; it will close it shortly. Sleep and retry. + time.Sleep(100 * time.Millisecond << uint(try)) } - err := toolCmd.Start() if err == nil { c := make(chan os.Signal, 100) signal.Notify(c, signalsToForward...) diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 883177058b0164..72743e9c54a9be 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -2614,12 +2614,7 @@ func (b *Builder) compilerCmd(compiler []string, incdir, workdir string) []strin // gcc-4.5 and beyond require explicit "-pthread" flag // for multithreading with pthread library. if cfg.BuildContext.CgoEnabled { - switch cfg.Goos { - case "windows": - a = append(a, "-mthreads") - default: - a = append(a, "-pthread") - } + a = append(a, "-pthread") } if cfg.Goos == "aix" { diff --git a/src/cmd/go/testdata/script/doc_http_clean_cache.txt b/src/cmd/go/testdata/script/doc_http_clean_cache.txt new file mode 100644 index 00000000000000..0c44326a3f16eb --- /dev/null +++ b/src/cmd/go/testdata/script/doc_http_clean_cache.txt @@ -0,0 +1,11 @@ +[short] skip 'builds pkgsite on clean GOCACHE' + +# Test that go doc -http succeeds on a clean GOCACHE (golang/go#80287). +env TEST_GODOC_BUILD_ONLY=1 +env GOCACHE=$WORK/clean-gocache + +# First run with clean GOCACHE +go doc -http fmt + +# Second run with existing GOCACHE +go doc -http fmt diff --git a/src/cmd/go/testdata/script/test_fail_newline.txt b/src/cmd/go/testdata/script/test_fail_newline.txt index 43cee565a19c1e..8de503b5d6911a 100644 --- a/src/cmd/go/testdata/script/test_fail_newline.txt +++ b/src/cmd/go/testdata/script/test_fail_newline.txt @@ -25,6 +25,14 @@ go test -v . stdout '^skipping\n' stdout '^ok\s+example/skip' +# In package list mode without -v, a passing test's output is discarded, so the +# 'ok' line must not be preceded by a spurious blank line, even when the +# discarded output ended without a newline (https://go.dev/issue/79786). +go test -count=1 . +! stderr . +! stdout '^\n' +stdout '^ok\s+example/skip' + # If the output is streamed and the test passes, we can't tell whether it ended # in a partial line, and don't want to emit any extra output in the # overwhelmingly common case that it did not. diff --git a/src/cmd/internal/obj/riscv/doc.go b/src/cmd/internal/obj/riscv/doc.go index d8b2c9ad7193c3..e3dbccf14bbd06 100644 --- a/src/cmd/internal/obj/riscv/doc.go +++ b/src/cmd/internal/obj/riscv/doc.go @@ -71,9 +71,9 @@ that ensure they are hardware supported. The file asm_riscv64.h defines macros for each RISC-V extension that is enabled by setting the GORISCV64 environment variable to a value other than [rva20u64]. For example, if GORISCV64=rva22u64 the macros hasZba, hasZbb and hasZbs will be -defined. If GORISCV64=rva23u64 hasV will be defined in addition to hasZba, -hasZbb and hasZbs. These macros can be used to determine whether it's safe -to use an instruction in hand-written assembly. +defined. If GORISCV64=rva23u64, hasV, hasZfa and hasZicond will be defined in +addition to hasZba, hasZbb and hasZbs. These macros can be used to determine +whether it's safe to use an instruction in hand-written assembly. It is not always necessary to include asm_riscv64.h and use #ifdefs in your code to safely take advantage of instructions present in the [rva22u64] diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index 8125acfa2967ff..118c936e813d87 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -823,21 +823,30 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if autoffset != 0 { to := p.To // Keep To attached to RET for retjmp below p.To = obj.Addr{} - if localoffset != 0 { - p.As = AADJSP - p.From.Type = obj.TYPE_CONST - p.From.Offset = int64(-localoffset) - p.Spadj = -localoffset - p = obj.Appendp(p, newprog) - } - if bpsize > 0 { - // Restore caller's BP - p.As = APOPQ - p.To.Type = obj.TYPE_REG - p.To.Reg = REG_BP - p.Spadj = -int32(bpsize) + needSpRestore, needBpRestore := localoffset != 0, bpsize > 0 + // We can't use LEAVE with assembly because the go asm promise + // it will insert save and restores for BP. Thus many pieces + // of code use BP as a scratch register. + if !ctxt.IsAsm && needSpRestore && needBpRestore { + p.As = ALEAVEQ + p.Spadj = -localoffset - int32(bpsize) p = obj.Appendp(p, newprog) + } else { + if needSpRestore { + p.As = AADJSP + p.From.Type = obj.TYPE_CONST + p.From.Offset = int64(-localoffset) + p.Spadj = -localoffset + p = obj.Appendp(p, newprog) + } + if needBpRestore { + p.As = APOPQ + p.To.Type = obj.TYPE_REG + p.To.Reg = REG_BP + p.Spadj = -int32(bpsize) + p = obj.Appendp(p, newprog) + } } p.As = obj.ARET diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 9cbc12919f1d00..b7ff79c0958bab 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1874,7 +1874,6 @@ func (ctxt *Link) hostlink() { // We want to have C files after Go files to remove // trampolines csects made by ld. argv = append(argv, "-nostartfiles") - argv = append(argv, "/lib/crt0_64.o") extld := ctxt.extld() name, args := extld[0], extld[1:] @@ -1887,6 +1886,7 @@ func (ctxt *Link) hostlink() { } return strings.Trim(string(out), "\n") } + argv = append(argv, getPathFile("crt0_64.o")) // Since GCC version 11, the 64-bit version of GCC starting files // are now suffixed by "_64". Even under "-maix64" multilib directory // "crtcxa.o" is 32-bit. diff --git a/src/crypto/cipher/cipher.go b/src/crypto/cipher/cipher.go index 4d631991ee1104..4bba78127d11f9 100644 --- a/src/crypto/cipher/cipher.go +++ b/src/crypto/cipher/cipher.go @@ -79,7 +79,7 @@ type AEAD interface { // // To reuse plaintext's storage for the encrypted output, use plaintext[:0] // as dst. Otherwise, the remaining capacity of dst must not overlap plaintext. - // dst and additionalData may not overlap. + // The remaining capacity of dst must not overlap additionalData. Seal(dst, nonce, plaintext, additionalData []byte) []byte // Open decrypts and authenticates ciphertext, authenticates the @@ -90,7 +90,7 @@ type AEAD interface { // // To reuse ciphertext's storage for the decrypted output, use ciphertext[:0] // as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext. - // dst and additionalData may not overlap. + // The remaining capacity of dst and additionalData may not overlap. // // Even if the function fails, the contents of dst, up to its capacity, // may be overwritten. diff --git a/src/debug/elf/file.go b/src/debug/elf/file.go index a9c13212284ebe..f979c97c2f0d65 100644 --- a/src/debug/elf/file.go +++ b/src/debug/elf/file.go @@ -763,12 +763,11 @@ func getString(section []byte, start int) (string, bool) { return "", false } - for end := start; end < len(section); end++ { - if section[end] == 0 { - return string(section[start:end]), true - } + end := bytes.IndexByte(section[start:], 0) + if end < 0 { + return "", false } - return "", false + return string(section[start : start+end]), true } // Section returns a section with the given name, or nil if no such diff --git a/src/encoding/base32/base32.go b/src/encoding/base32/base32.go index 7a3221aea2497c..acc729802aab6d 100644 --- a/src/encoding/base32/base32.go +++ b/src/encoding/base32/base32.go @@ -7,6 +7,7 @@ package base32 import ( "io" + "math" "slices" "strconv" ) @@ -279,10 +280,18 @@ func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser { // EncodedLen returns the length in bytes of the base32 encoding // of an input buffer of length n. +// It panics if the encoded length overflows int, +// which can happen only if n > [math.MaxInt]/8*5. func (enc *Encoding) EncodedLen(n int) int { if enc.padChar == NoPadding { + if n > math.MaxInt/8*5+4 { + panic("encoded length overflows int") + } return n/5*8 + (n%5*8+4)/5 } + if n > math.MaxInt/8*5 { + panic("encoded length overflows int") + } return (n + 4) / 5 * 8 } diff --git a/src/encoding/base32/base32_test.go b/src/encoding/base32/base32_test.go index 6f8d564def3c86..c115984f4e2b3e 100644 --- a/src/encoding/base32/base32_test.go +++ b/src/encoding/base32/base32_test.go @@ -765,6 +765,7 @@ func TestEncodedLen(t *testing.T) { tests = append(tests, test{rawStdEncoding, (math.MaxInt-4)/8 + 1, 1844674407370955162}) tests = append(tests, test{rawStdEncoding, math.MaxInt/8*5 + 4, math.MaxInt}) } + tests = append(tests, test{StdEncoding, math.MaxInt / 8 * 5, math.MaxInt - 7}) for _, tt := range tests { if got := tt.enc.EncodedLen(tt.n); int64(got) != tt.want { t.Errorf("EncodedLen(%d): got %d, want %d", tt.n, got, tt.want) @@ -772,6 +773,25 @@ func TestEncodedLen(t *testing.T) { } } +func TestEncodedLenOverflow(t *testing.T) { + for _, tt := range []struct { + enc *Encoding + n int + }{ + {StdEncoding, math.MaxInt/8*5 + 1}, + {StdEncoding.WithPadding(NoPadding), math.MaxInt/8*5 + 5}, + } { + func() { + defer func() { + if recover() == nil { + t.Errorf("EncodedLen(%d) did not panic", tt.n) + } + }() + tt.enc.EncodedLen(tt.n) + }() + } +} + func TestDecodedLen(t *testing.T) { var rawStdEncoding = StdEncoding.WithPadding(NoPadding) type test struct { diff --git a/src/encoding/base64/base64.go b/src/encoding/base64/base64.go index 32014f45bbc146..952cf0c6f859a3 100644 --- a/src/encoding/base64/base64.go +++ b/src/encoding/base64/base64.go @@ -8,6 +8,7 @@ package base64 import ( "internal/byteorder" "io" + "math" "slices" "strconv" ) @@ -282,10 +283,18 @@ func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser { // EncodedLen returns the length in bytes of the base64 encoding // of an input buffer of length n. +// It panics if the encoded length overflows int, +// which can happen only if n > [math.MaxInt]/4*3. func (enc *Encoding) EncodedLen(n int) int { if enc.padChar == NoPadding { + if n > math.MaxInt/4*3+2 { + panic("encoded length overflows int") + } return n/3*4 + (n%3*8+5)/6 // minimum # chars at 6 bits per char } + if n > math.MaxInt/4*3 { + panic("encoded length overflows int") + } return (n + 2) / 3 * 4 // minimum # 4-char quanta, 3 bytes each } diff --git a/src/encoding/base64/base64_test.go b/src/encoding/base64/base64_test.go index 7f5ebd8085de56..bc67413cc7ca85 100644 --- a/src/encoding/base64/base64_test.go +++ b/src/encoding/base64/base64_test.go @@ -305,6 +305,7 @@ func TestEncodedLen(t *testing.T) { tests = append(tests, test{RawStdEncoding, (math.MaxInt-5)/8 + 1, 1537228672809129302}) tests = append(tests, test{RawStdEncoding, math.MaxInt/4*3 + 2, math.MaxInt}) } + tests = append(tests, test{StdEncoding, math.MaxInt / 4 * 3, math.MaxInt - 3}) for _, tt := range tests { if got := tt.enc.EncodedLen(tt.n); int64(got) != tt.want { t.Errorf("EncodedLen(%d): got %d, want %d", tt.n, got, tt.want) @@ -312,6 +313,25 @@ func TestEncodedLen(t *testing.T) { } } +func TestEncodedLenOverflow(t *testing.T) { + for _, tt := range []struct { + enc *Encoding + n int + }{ + {StdEncoding, math.MaxInt/4*3 + 1}, + {RawStdEncoding, math.MaxInt/4*3 + 3}, + } { + func() { + defer func() { + if recover() == nil { + t.Errorf("EncodedLen(%d) did not panic", tt.n) + } + }() + tt.enc.EncodedLen(tt.n) + }() + } +} + func TestDecodedLen(t *testing.T) { type test struct { enc *Encoding diff --git a/src/encoding/xml/marshal.go b/src/encoding/xml/marshal.go index 13fbeeeedc75ce..be1e84f47af9eb 100644 --- a/src/encoding/xml/marshal.go +++ b/src/encoding/xml/marshal.go @@ -224,11 +224,15 @@ func (enc *Encoder) EncodeToken(t Token) error { case CharData: escapeText(p, t, false) case Comment: - if bytes.Contains(t, endComment) { - return fmt.Errorf("xml: EncodeToken of Comment containing --> marker") + if bytes.Contains(t, ddBytes) { + return fmt.Errorf("xml: EncodeToken of Comment containing -- marker") } p.WriteString("" is invalid grammar. Make it "- -->" + p.WriteByte(' ') + } p.WriteString("-->") return p.cachedWriteError() case ProcInst: diff --git a/src/encoding/xml/marshal_test.go b/src/encoding/xml/marshal_test.go index 6c7e711aac0566..fd37412d2ca5c0 100644 --- a/src/encoding/xml/marshal_test.go +++ b/src/encoding/xml/marshal_test.go @@ -2006,7 +2006,19 @@ var encodeTokenTests = []struct { toks: []Token{ Comment("foo-->"), }, - err: "xml: EncodeToken of Comment containing --> marker", + err: "xml: EncodeToken of Comment containing -- marker", +}, { + desc: "comment with double hyphen", + toks: []Token{ + Comment("foo--bar"), + }, + err: "xml: EncodeToken of Comment containing -- marker", +}, { + desc: "comment ending in hyphen", + toks: []Token{ + Comment("foo-"), + }, + want: ``, }, { desc: "proc instruction", toks: []Token{ diff --git a/src/go/build/constraint/vers.go b/src/go/build/constraint/vers.go index 13544484d69f3f..34886a4a55412e 100644 --- a/src/go/build/constraint/vers.go +++ b/src/go/build/constraint/vers.go @@ -66,7 +66,7 @@ func minVersion(z Expr, sign int) int { if z.Tag == "go1" { return 0 } - _, v, ok := strings.Cut(z.Tag, "go1.") + v, ok := strings.CutPrefix(z.Tag, "go1.") if !ok { return -1 } diff --git a/src/go/build/constraint/vers_test.go b/src/go/build/constraint/vers_test.go index 044de7f8b104fd..be0ca1246ff049 100644 --- a/src/go/build/constraint/vers_test.go +++ b/src/go/build/constraint/vers_test.go @@ -23,6 +23,8 @@ var tests = []struct { {"//go:build !go1.60", -1}, {"//go:build linux && go1.50 || darwin && go1.60", 50}, {"//go:build linux && go1.50 || !(!darwin || !go1.60)", 50}, + {"//go:build notgo1.21", -1}, + {"//go:build ago1.20", -1}, } func TestGoVersion(t *testing.T) { diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index 4d5784a8b4c35d..eb675628b37da4 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -939,28 +939,7 @@ func (d *parseDoc) parseText(out []Text, s string, autoLink bool) []Text { continue } } - switch { - case strings.HasPrefix(t, "``"): - if len(t) >= 3 && t[2] == '`' { - // Do not convert `` inside ```, in case people are mistakenly writing Markdown. - i += 3 - for i < len(t) && t[i] == '`' { - i++ - } - break - } - writeUntil(i) - w.WriteRune('“') - i += 2 - wrote = i - case strings.HasPrefix(t, "''"): - writeUntil(i) - w.WriteRune('”') - i += 2 - wrote = i - default: - i++ - } + i++ } flush(len(s)) return out diff --git a/src/go/doc/comment/testdata/quote.txt b/src/go/doc/comment/testdata/quote.txt index b64adae0b36680..cfe945a9fa354b 100644 --- a/src/go/doc/comment/testdata/quote.txt +++ b/src/go/doc/comment/testdata/quote.txt @@ -1,15 +1,15 @@ -- input -- -Doubled single quotes like `` and '' turn into Unicode double quotes, -but single quotes ` and ' do not. +Doubled single quotes like `` and '' don't turn into Unicode double quotes, +and neither do single quotes ` and '. Misplaced markdown fences ``` do not either. -- gofmt -- -Doubled single quotes like “ and ” turn into Unicode double quotes, -but single quotes ` and ' do not. +Doubled single quotes like `` and '' don't turn into Unicode double quotes, +and neither do single quotes ` and '. Misplaced markdown fences ``` do not either. -- text -- -Doubled single quotes like “ and ” turn into Unicode double quotes, but single -quotes ` and ' do not. Misplaced markdown fences ``` do not either. +Doubled single quotes like `` and '' don't turn into Unicode double quotes, and +neither do single quotes ` and '. Misplaced markdown fences ``` do not either. -- html -- -

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

Doubled single quotes like `` and '' don't turn into Unicode double quotes, +and neither do single quotes ` and '. Misplaced markdown fences ``` do not either. diff --git a/src/go/doc/synopsis_test.go b/src/go/doc/synopsis_test.go index 158c734bf02bbf..949859c87c160f 100644 --- a/src/go/doc/synopsis_test.go +++ b/src/go/doc/synopsis_test.go @@ -35,7 +35,7 @@ var tests = []struct { {"All Rights reserved. Package foo does bar.", 20, ""}, {"All rights reserved. Package foo does bar.", 20, ""}, {"Authors: foo@bar.com. Package foo does bar.", 21, ""}, - {"typically invoked as ``go tool asm'',", 37, "typically invoked as “go tool asm”,"}, + {"typically invoked as ``go tool asm'',", 37, "typically invoked as ``go tool asm'',"}, } func TestSynopsis(t *testing.T) { diff --git a/src/go/version/version_test.go b/src/go/version/version_test.go index ad83a258614f6f..9e14914a9f027b 100644 --- a/src/go/version/version_test.go +++ b/src/go/version/version_test.go @@ -15,6 +15,7 @@ var compareTests = []testCase2[string, string, int]{ {"", "", 0}, {"x", "x", 0}, {"", "x", 0}, + {"notgo1.21", "go1.21", -1}, {"1", "1.1", 0}, {"go1", "go1.1", -1}, {"go1.5", "go1.6", -1}, @@ -47,6 +48,7 @@ func TestLang(t *testing.T) { test1(t, langTests, "Lang", Lang) } var langTests = []testCase1[string, string]{ {"bad", ""}, + {"notgo1.21", ""}, {"go1.2rc3", "go1.2"}, {"go1.2.3", "go1.2"}, {"go1.2", "go1.2"}, @@ -60,6 +62,7 @@ func TestIsValid(t *testing.T) { test1(t, isValidTests, "IsValid", IsValid) } var isValidTests = []testCase1[string, bool]{ {"", false}, {"1.2.3", false}, + {"notgo1.21", false}, {"go1.2rc3", true}, {"go1.2.3", true}, {"go1.999testmod", true}, diff --git a/src/html/template/js.go b/src/html/template/js.go index e2db30f966ed7d..9ed458db6d6ec8 100644 --- a/src/html/template/js.go +++ b/src/html/template/js.go @@ -170,8 +170,6 @@ func jsValEscaper(args ...any) string { } a = fmt.Sprint(args...) } - // TODO: detect cycles before calling Marshal which loops infinitely on - // cyclic data. This may be an unacceptable DoS risk. b, err := json.Marshal(a) if err != nil { // While the standard JSON marshaler does not include user controlled diff --git a/src/html/template/js_test.go b/src/html/template/js_test.go index 015d97e6b50119..270e4ea36593f1 100644 --- a/src/html/template/js_test.go +++ b/src/html/template/js_test.go @@ -180,6 +180,31 @@ func TestJSValEscaper(t *testing.T) { } } +func TestJSValEscaperCycles(t *testing.T) { + mapCycle := map[string]any{} + mapCycle["self"] = mapCycle + sliceCycle := []any{nil} + sliceCycle[0] = sliceCycle + + for _, test := range []struct { + name string + value any + }{ + {"map", mapCycle}, + {"slice", sliceCycle}, + } { + t.Run(test.name, func(t *testing.T) { + got := jsValEscaper(test.value) + if !strings.Contains(strings.ToLower(got), "cycle") { + t.Errorf("got %q, want cycle error", got) + } + if want := "*/null "; !strings.HasSuffix(got, want) { + t.Errorf("got %q, want %q", got, want) + } + }) + } +} + func TestJSStrEscaper(t *testing.T) { tests := []struct { x any diff --git a/src/html/template/template.go b/src/html/template/template.go index 097f3dfa2d5e77..c4244fb11ef00f 100644 --- a/src/html/template/template.go +++ b/src/html/template/template.go @@ -480,9 +480,17 @@ func parseGlob(t *Template, pattern string) (*Template, error) { return parseFiles(t, readFileOS, filenames...) } -// IsTrue reports whether the value is 'true', in the sense of not the zero of its type, -// and whether the value has a meaningful truth value. This is the definition of -// truth used by if and other such actions. +// IsTrue reports whether the value is true, in the sense of being nonzero, +// nonempty, or non-nil, and whether the value has a meaningful truth value. +// This is the definition of truth used in "if" actions and elsewhere in +// templates: +// +// - A boolean value is true if it is true. +// - A numeric value is true if it is nonzero. +// - An array, map, slice, or string value is true if its length is +// greater than zero. +// - Any other value is true if it is non-nil; struct values are +// never nil, and therefore always true. func IsTrue(val any) (truth, ok bool) { return template.IsTrue(val) } diff --git a/src/net/http/csrf.go b/src/net/http/csrf.go index 0b71b403899a2a..448052bdf5a554 100644 --- a/src/net/http/csrf.go +++ b/src/net/http/csrf.go @@ -65,8 +65,8 @@ func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error { if u.Host == "" { return fmt.Errorf("invalid origin %q: host is required", origin) } - if u.Path != "" || u.RawQuery != "" || u.Fragment != "" { - return fmt.Errorf("invalid origin %q: path, query, and fragment are not allowed", origin) + if u.User != nil || u.Path != "" || u.RawQuery != "" || u.Fragment != "" { + return fmt.Errorf("invalid origin %q: userinfo, path, query, and fragment are not allowed", origin) } c.trustedMu.Lock() defer c.trustedMu.Unlock() diff --git a/src/net/http/csrf_test.go b/src/net/http/csrf_test.go index a29e3ae16dff6b..0d1f6201524ea7 100644 --- a/src/net/http/csrf_test.go +++ b/src/net/http/csrf_test.go @@ -235,6 +235,8 @@ func TestCrossOriginProtectionAddTrustedOriginErrors(t *testing.T) { {"http origin", "http://example.com", false}, {"missing scheme", "example.com", true}, {"missing host", "https://", true}, + {"with empty userinfo", "https://@example.com", true}, + {"with nonempty userinfo", "https://foo:bar@example.com", true}, {"trailing slash", "https://example.com/", true}, {"with path", "https://example.com/path", true}, {"with query", "https://example.com?query=value", true}, diff --git a/src/net/url/url.go b/src/net/url/url.go index 77c2dd2b5ae93f..82bd9bd991b66c 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -411,6 +411,16 @@ func Parse(rawURL string) (*URL, error) { return url, nil } +// MustParse calls [Parse](rawURL) and panics on error. +// It is intended for use with hard-coded strings representing valid urls. +func MustParse(rawURL string) *URL { + url, err := Parse(rawURL) + if err != nil { + panic(err) + } + return url +} + // ParseRequestURI parses a raw url into a [URL] structure. It assumes that // url was received in an HTTP request, so the url is interpreted // only as an absolute URI or an absolute path. diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index dd093e6942357d..e1b84749774e90 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -715,6 +715,60 @@ func TestParse(t *testing.T) { if !reflect.DeepEqual(u, tt.out) { t.Errorf("Parse(%q):\n\tgot %v\n\twant %v\n", tt.in, ufmt(u), ufmt(tt.out)) } + u = MustParse(tt.in) + if !reflect.DeepEqual(u, tt.out) { + t.Errorf("MustParse(%q):\n\tgot %v\n\twant %v\n", tt.in, ufmt(u), ufmt(tt.out)) + } + } +} + +func TestInvalidParse(t *testing.T) { + cases := []struct { + name string + in string + }{ + { + name: "null-character", + in: "http://example.com/\x00", + }, + { + name: "control-character", + in: "http://example.com/\t", + }, + { + name: "invalid-escape", + in: "http://example.com/%zz", + }, + { + name: "invalid-port", + in: "http://example.com:port", + }, + { + name: "malformed-ipv6", + in: "http://[::1/", + }, + { + name: "ambiguous", + in: ":", + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + defer func() { + err := recover() + if err == nil { + t.Fatalf("MustParse(%q): should panic", tt.in) + } + if e, ok := err.(*Error); !ok || e.Op != "parse" { + t.Fatalf("MustParse(%q): unexpected panic: %#v", tt.in, err) + } + }() + if _, err := Parse(tt.in); err == nil { + t.Errorf("Parse(%q): should error", tt.in) + } + _ = MustParse(tt.in) + }) } } diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index c15b5882ede554..11e3a427fde479 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -4796,6 +4796,60 @@ func TestConvertPanic(t *testing.T) { }) } +type issue80332ZeroLen struct { + Type +} + +func (z issue80332ZeroLen) Len() int { + return 0 +} + +func TestIssue80332(t *testing.T) { + type helper struct { + x [1]int + y uintptr + } + var e helper + value := ValueOf(e.x[:]) + fakeType := issue80332ZeroLen{TypeOf([2]int{})} + + shouldPanic("reflect: cannot convert slice with length 1 to array with length 2", func() { + _ = value.Convert(fakeType) + }) +} + +type issue80332FakeChan struct { + Type +} + +func (c issue80332FakeChan) Kind() Kind { + return Chan +} + +func (c issue80332FakeChan) ChanDir() ChanDir { + return BothDir +} + +type issue80332FakeMap struct { + Type +} + +func (m issue80332FakeMap) Kind() Kind { + return Map +} + +func TestIssue80332Others(t *testing.T) { + fakeChan := issue80332FakeChan{TypeOf(0)} + shouldPanic("reflect.MakeChan of non-chan type", func() { + _ = MakeChan(fakeChan, 0) + }) + + fakeMap := issue80332FakeMap{TypeOf(0)} + shouldPanic("reflect.MakeMapWithSize of non-map type", func() { + _ = MakeMapWithSize(fakeMap, 0) + }) +} + func TestConvertSlice2Array(t *testing.T) { s := make([]int, 4) p := [4]int{} diff --git a/src/reflect/example_test.go b/src/reflect/example_test.go index 1df524758ab66f..a37ce462ea0550 100644 --- a/src/reflect/example_test.go +++ b/src/reflect/example_test.go @@ -260,3 +260,29 @@ func ExampleValue_Methods() { // UnreadRune // WriteTo } + +func ExampleTypeAssert() { + reader := bytes.NewReader([]byte("Hello, Gophers!")) + v := reflect.ValueOf(reader) + + if br, ok := reflect.TypeAssert[*bytes.Reader](v); ok { + fmt.Printf("Remaining bytes: %d\n", br.Len()) + } + + if _, ok := reflect.TypeAssert[*os.File](v); !ok { + fmt.Println("Cannot assert to *os.File") + } + + if r, ok := reflect.TypeAssert[io.Reader](v); ok { + data, err := io.ReadAll(r) + if err != nil { + panic(err) + } + fmt.Printf("Read through io.Reader: %s\n", data) + } + + // Output: + // Remaining bytes: 15 + // Cannot assert to *os.File + // Read through io.Reader: Hello, Gophers! +} diff --git a/src/reflect/makefunc.go b/src/reflect/makefunc.go index d35c92a14c7317..b550ce0d8eef28 100644 --- a/src/reflect/makefunc.go +++ b/src/reflect/makefunc.go @@ -45,11 +45,12 @@ type makeFuncImpl struct { // The Examples section of the documentation includes an illustration // of how to use MakeFunc to build a swap function for different types. func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { + t := typ.common() + typ = toType(t) // for #80332, ensure t's exported methods are not shadowed if typ.Kind() != Func { panic("reflect: call of MakeFunc with non-Func type") } - t := typ.common() ftyp := (*funcType)(unsafe.Pointer(t)) code := abi.FuncPCABI0(makeFuncStub) diff --git a/src/reflect/map.go b/src/reflect/map.go index 6c81d5f7e25227..bd3378aabb5a32 100644 --- a/src/reflect/map.go +++ b/src/reflect/map.go @@ -30,6 +30,8 @@ func (t *rtype) Key() Type { func MapOf(key, elem Type) Type { ktyp := key.common() etyp := elem.common() + key = toType(ktyp) + elem = toType(etyp) if ktyp.Equal == nil { panic("reflect.MapOf: invalid key type " + stringFor(ktyp)) diff --git a/src/reflect/type.go b/src/reflect/type.go index cb040037b771b1..67eaefa0f9f95d 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -1821,6 +1821,7 @@ var funcLookupCache struct { // If t's size is equal to or exceeds this limit, ChanOf panics. func ChanOf(dir ChanDir, t Type) Type { typ := t.common() + t = toType(typ) // for #80332, ensure t's exported methods are not shadowed // Look in cache. ckey := cacheKey{Chan, typ, nil, uintptr(dir)} @@ -1912,7 +1913,7 @@ func initFuncTypes(n int) Type { // panics if the in[len(in)-1] does not represent a slice and variadic is // true. func FuncOf(in, out []Type, variadic bool) Type { - if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { + if variadic && (len(in) == 0 || toType(in[len(in)-1].common()).Kind() != Slice) { panic("reflect.FuncOf: last arg of variadic func must be slice") } @@ -2128,6 +2129,7 @@ func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) { // For example, if t represents int, SliceOf(t) represents []int. func SliceOf(t Type) Type { typ := t.common() + t = toType(typ) // for #80332, ensure t's exported methods are not shadowed // Look in cache. ckey := cacheKey{Slice, typ, nil, 0} @@ -2656,6 +2658,7 @@ func ArrayOf(length int, elem Type) Type { } typ := elem.common() + elem = toType(typ) // for #80332, ensure elem's exported methods are not shadowed // Look in cache. ckey := cacheKey{Array, typ, nil, uintptr(length)} diff --git a/src/reflect/value.go b/src/reflect/value.go index 7afab5376d19f7..8a81fe6ba1e6e8 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -2166,7 +2166,12 @@ func (v Value) Set(x Value) { x.mustBeExported() // do not let unexported x leak var target unsafe.Pointer if v.kind() == Interface { - target = v.ptr + // x.assignTo below uses target as a scratch space, which + // then will be assigned back to v in the code below. + // So it is a self-assignment, therefore does not cause + // escape, but the compiler cannot see it. Mark it noescape + // to help the compiler. + target = abi.NoEscape(v.ptr) } x = x.assignTo("reflect.Set", v.typ(), target) if x.flag&flagIndir != 0 { @@ -3083,6 +3088,7 @@ func unsafe_NewArray(*abi.Type, int) unsafe.Pointer // MakeSlice creates a new zero-initialized slice value // for the specified slice type, length, and capacity. func MakeSlice(typ Type, len, cap int) Value { + typ = toType(typ.common()) if typ.Kind() != Slice { panic("reflect.MakeSlice of non-slice type") } @@ -3112,6 +3118,7 @@ func SliceAt(typ Type, p unsafe.Pointer, n int) Value { // MakeChan creates a new channel with the specified type and buffer size. func MakeChan(typ Type, buffer int) Value { + typ = toType(typ.common()) if typ.Kind() != Chan { panic("reflect.MakeChan of non-chan type") } @@ -3134,6 +3141,7 @@ func MakeMap(typ Type) Value { // MakeMapWithSize creates a new map with the specified type // and initial space for approximately n elements. func MakeMapWithSize(typ Type, n int) Value { + typ = toType(typ.common()) if typ.Kind() != Map { panic("reflect.MakeMapWithSize of non-map type") } @@ -3260,6 +3268,7 @@ func (v Value) Convert(t Type) Value { if v.flag&flagMethod != 0 { v = makeMethodValue("Convert", v) } + t = toType(t.common()) op := convertOp(t.common(), v.typ()) if op == nil { panic("reflect.Value.Convert: value of type " + stringFor(v.typ()) + " cannot be converted to type " + t.String()) @@ -3271,6 +3280,7 @@ func (v Value) Convert(t Type) Value { // If v.CanConvert(t) returns true then v.Convert(t) will not panic. func (v Value) CanConvert(t Type) bool { vt := v.Type() + t = toType(t.common()) if !vt.ConvertibleTo(t) { return false } diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 9edb1f57bb853d..3511c834f7532e 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -1930,6 +1930,7 @@ func mstart1() { gp.sched.g = guintptr(unsafe.Pointer(gp)) gp.sched.pc = sys.GetCallerPC() gp.sched.sp = sys.GetCallerSP() + gp.sched.bp = getcallerfp() asminit() minit() diff --git a/src/strings/example_test.go b/src/strings/example_test.go index 72adbae5f26a31..1865ca9d50c57c 100644 --- a/src/strings/example_test.go +++ b/src/strings/example_test.go @@ -115,6 +115,18 @@ func ExampleCut() { // Cut("Gopher", "Badger") = "Gopher", "", false } +func ExampleCutLast() { + show := func(s, sep string) { + before, after, found := strings.CutLast(s, sep) + fmt.Printf("CutLast(%q, %q) = %q, %q, %v\n", s, sep, before, after, found) + } + show("root/user/docs", "/") + show("Gopher", "/") + // Output: + // CutLast("root/user/docs", "/") = "root/user", "docs", true + // CutLast("Gopher", "/") = "Gopher", "", false +} + func ExampleCutPrefix() { show := func(s, prefix string) { after, found := strings.CutPrefix(s, prefix) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 7a67ec6824f5e5..2b7146b94f63c4 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -314,9 +314,17 @@ func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse. } } -// IsTrue reports whether the value is 'true', in the sense of not the zero of its type, -// and whether the value has a meaningful truth value. This is the definition of -// truth used by if and other such actions. +// IsTrue reports whether the value is true, in the sense of being nonzero, +// nonempty, or non-nil, and whether the value has a meaningful truth value. +// This is the definition of truth used in "if" actions and elsewhere in +// templates: +// +// - A boolean value is true if it is true. +// - A numeric value is true if it is nonzero. +// - An array, map, slice, or string value is true if its length is +// greater than zero. +// - Any other value is true if it is non-nil; struct values are +// never nil, and therefore always true. func IsTrue(val any) (truth, ok bool) { return isTrue(reflect.ValueOf(val)) } diff --git a/test/codegen/comparisons.go b/test/codegen/comparisons.go index 4394fb5a1f8ab4..0c25946094ce5a 100644 --- a/test/codegen/comparisons.go +++ b/test/codegen/comparisons.go @@ -357,7 +357,7 @@ func CmpToZero_ex1(a int64, e int32) int { } // arm64:`SUB` `TBNZ` - // arm:`CMP|CMN` -`(ADD|SUB)` `(BMI|BPL)` + // arm:`SUB` -`(BMI|BPL)` if e-11 >= 0 { return 8 } @@ -425,22 +425,22 @@ func CmpToZero_ex3(a, b, c, d int64, e, f, g, h int32) int { // var - var*var func CmpToZero_ex4(a, b, c, d int64, e, f, g, h int32) int { - // arm64:`CMP` -`MSUB` `MUL` `BEQ` `(BMI|BPL)` + // arm64:`MSUB` if a-b*c > 0 { return 1 } - // arm64:`CMP` -`MSUB` `MUL` `(BMI|BPL)` + // arm64:`MSUB` if b-c*d >= 0 { return 2 } - // arm64:`CMPW` -`MSUBW` `MULW` `(BMI|BPL)` + // arm64:`MSUBW` if e-f*g < 0 { return 5 } - // arm64:`CMPW` -`MSUBW` `MULW` `(BMI|BPL)` + // arm64:`MSUBW` if f-g*h >= 0 { return 6 } @@ -453,7 +453,7 @@ func CmpToZero_ex5(e, f int32, u uint32) int { return 1 } - // arm:`CMP` -`SUB` `(BMI|BPL)` + // arm:`SUB` -`(BMI|BPL)` if f-int32(u>>2) >= 0 { return 2 } @@ -748,7 +748,7 @@ func cmpToCmnLessThan(a, b, c, d int) int { if a*b+c < 0 { c3 = 1 } - // arm64:`CMP` `CSET MI` -`CMN` + // arm64:`MSUB` `CSET LT` -`CMN` if a-b*c < 0 { c4 = 1 } @@ -817,7 +817,7 @@ func cmpToCmnGreaterThanEqual(a, b, c, d int) int { if a*b+c >= 0 { c3 = 1 } - // arm64:`CMP` `CSET PL` -`CMN` + // arm64:`MSUB` `CSET GE` -`CMN` if a-b*c >= 0 { c4 = 1 } @@ -865,19 +865,6 @@ func cmp7() { cmp6[string]("") // force instantiation } -type Point struct { - X, Y int -} - -// invertLessThanNoov checks (LessThanNoov (InvertFlags x)) is lowered as -// CMP, CSET, CSEL instruction sequence. InvertFlags are only generated under -// certain conditions, see canonLessThan, so if the code below does not -// generate an InvertFlags OP, this check may fail. -func invertLessThanNoov(p1, p2, p3 Point) bool { - // arm64:`CMP` `CSET` `CSEL` - return (p1.X-p3.X)*(p2.Y-p3.Y)-(p2.X-p3.X)*(p1.Y-p3.Y) < 0 -} - func cmpstring1(x, y string) int { // amd64:".*cmpstring" if x < y { diff --git a/test/codegen/mathbits.go b/test/codegen/mathbits.go index 60db94d6a93f2a..72b425d6429f2c 100644 --- a/test/codegen/mathbits.go +++ b/test/codegen/mathbits.go @@ -763,6 +763,19 @@ func Add64MPanicOnOverflowGT(a, b [2]uint64) [2]uint64 { return r } +func issue80399add(a, b [2]uint64, s uint64) uint64 { + _, c := bits.Add64(a[0], b[0], 0) + _, c2 := bits.Add64(a[1], b[1], c) + // amd64:-"SET" -"MOVBLZX" "ADCQ" + return s + c2 +} +func issue80399sub(a, b [2]uint64, s uint64) uint64 { + _, c := bits.Add64(a[0], b[0], 0) + _, c2 := bits.Add64(a[1], b[1], c) + // amd64:-"SET" -"MOVBLZX" "SBBQ" + return s - c2 +} + // Verify independent carry chain operations are scheduled efficiently // and do not cause unnecessary save/restore of the CA bit. // diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go index f71c806ade9dcc..d3f2e338097193 100644 --- a/test/codegen/memcombine.go +++ b/test/codegen/memcombine.go @@ -17,6 +17,7 @@ import ( func load_le64(b []byte) uint64 { // amd64:`MOVQ \(.*\),` -`MOV[BWL] [^$]` -`OR` + // riscv64:-`MOV \(X[0-9]+\), X[0-9]+` // s390x:`MOVDBR \(.*\),` // arm64:`MOVD \(R[0-9]+\),` -`MOV[BHW]` // loong64:`MOVV \(R[0-9]+\),` @@ -184,6 +185,225 @@ func load_le_byte8_uint64_inv(s []byte) uint64 { return uint64(s[7])<<56 | uint64(s[6])<<48 | uint64(s[5])<<40 | uint64(s[4])<<32 | uint64(s[3])<<24 | uint64(s[2])<<16 | uint64(s[1])<<8 | uint64(s[0]) } +type memCombineAligned struct { + x uint64 + b [16]byte +} + +func load_le_byte8_uint64_aligned(s *memCombineAligned) uint64 { + // riscv64:`MOV 8\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_aligned_offset1(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[1:]) +} + +func load_le_byte8_uint64_aligned_offset2(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[2:]) +} + +func load_le_byte8_uint64_aligned_offset3(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[3:]) +} + +func load_le_byte8_uint64_aligned_offset4(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[4:]) +} + +func load_le_byte8_uint64_aligned_offset5(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[5:]) +} + +func load_le_byte8_uint64_aligned_offset6(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[6:]) +} + +func load_le_byte8_uint64_aligned_offset7(s *memCombineAligned) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[7:]) +} + +func load_le_byte8_uint64_aligned_offset8(s *memCombineAligned) uint64 { + // riscv64:`MOV 16\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint64(s.b[8:]) +} + +func load_le_byte4_uint32_aligned(s *memCombineAligned) uint32 { + // riscv64:`MOVWU 8\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint32(s.b[:]) +} + +func load_le_byte4_uint32_aligned_offset2(s *memCombineAligned) uint32 { + // riscv64:`MOVBU` -`MOVWU` + return binary.LittleEndian.Uint32(s.b[2:]) +} + +func load_le_byte4_uint32_aligned_offset4(s *memCombineAligned) uint32 { + // riscv64:`MOVWU 12\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint32(s.b[4:]) +} + +func load_le_byte2_uint16_aligned(s *memCombineAligned) uint16 { + // riscv64:`MOVHU 8\(X[0-9]+\), X[0-9]+` -`MOVBU` -`OR` + return binary.LittleEndian.Uint16(s.b[:]) +} + +func load_le_byte2_uint16_aligned_offset1(s *memCombineAligned) uint16 { + // riscv64:`MOVBU` -`MOVHU` + return binary.LittleEndian.Uint16(s.b[1:]) +} + +type memCombineUnaligned08 struct { + x byte + _ [0]byte + b [8]byte +} + +type memCombineUnaligned18 struct { + x byte + _ [1]byte + b [8]byte +} + +type memCombineUnaligned28 struct { + x byte + _ [2]byte + b [8]byte +} + +type memCombineUnaligned38 struct { + x byte + _ [3]byte + b [8]byte +} + +type memCombineUnaligned48 struct { + x byte + _ [4]byte + b [8]byte +} + +type memCombineUnaligned58 struct { + x byte + _ [5]byte + b [8]byte +} + +type memCombineUnaligned68 struct { + x byte + _ [6]byte + b [8]byte +} + +type memCombineUnaligned78 struct { + x byte + _ [7]byte + b [8]byte +} + +type memCombineUnaligned88 struct { + x byte + _ [8]byte + b [8]byte +} + +type memCombineUnaligned16 struct { + x byte + b [16]byte +} + +func load_le_byte8_uint64_unaligned_offset0(s *memCombineUnaligned08) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset1(s *memCombineUnaligned18) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset2(s *memCombineUnaligned28) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset3(s *memCombineUnaligned38) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset4(s *memCombineUnaligned48) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset5(s *memCombineUnaligned58) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset6(s *memCombineUnaligned68) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset7(s *memCombineUnaligned78) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_offset8(s *memCombineUnaligned88) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset1(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[1:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset2(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[2:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset3(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[3:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset4(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[4:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset5(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[5:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset6(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[6:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset7(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[7:]) +} + +func load_le_byte8_uint64_unaligned_slice_offset8(s *memCombineUnaligned16) uint64 { + // riscv64:`MOVBU` -`MOV ` + return binary.LittleEndian.Uint64(s.b[8:]) +} + func load_be_byte2_uint16(s []byte) uint16 { // arm64:`MOVHU \(R[0-9]+\)` `REV16W` -`ORR` -`MOVB` // amd64:`MOVWLZX \([A-Z]+\)` `ROLW` -`MOVB` -`OR` @@ -512,6 +732,126 @@ func store_le64_load(b []byte, x *[8]byte) { binary.LittleEndian.PutUint64(b, binary.LittleEndian.Uint64(x[:])) } +func store_le_byte8_uint64_aligned(s *memCombineAligned, x uint64) { + // riscv64:`MOV X[0-9]+, 8\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint64(s.b[:], x) +} + +func store_le_byte8_uint64_aligned_offset1(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[1:], x) +} + +func store_le_byte8_uint64_aligned_offset2(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[2:], x) +} + +func store_le_byte8_uint64_aligned_offset3(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[3:], x) +} + +func store_le_byte8_uint64_aligned_offset4(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[4:], x) +} + +func store_le_byte8_uint64_aligned_offset5(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[5:], x) +} + +func store_le_byte8_uint64_aligned_offset6(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[6:], x) +} + +func store_le_byte8_uint64_aligned_offset7(s *memCombineAligned, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[7:], x) +} + +func store_le_byte8_uint64_aligned_offset8(s *memCombineAligned, x uint64) { + // riscv64:`MOV X[0-9]+, 16\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint64(s.b[8:], x) +} + +func store_le_byte4_uint32_aligned(s *memCombineAligned, x uint32) { + // riscv64:`MOVW X[0-9]+, 8\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint32(s.b[:], x) +} + +func store_le_byte4_uint32_aligned_offset2(s *memCombineAligned, x uint32) { + // riscv64:`MOVB` -`MOVW` + binary.LittleEndian.PutUint32(s.b[2:], x) +} + +func store_le_byte4_uint32_aligned_offset4(s *memCombineAligned, x uint32) { + // riscv64:`MOVW X[0-9]+, 12\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint32(s.b[4:], x) +} + +func store_le_byte2_uint16_aligned(s *memCombineAligned, x uint16) { + // riscv64:`MOVH X[0-9]+, 8\(X[0-9]+\)` -`MOVB` + binary.LittleEndian.PutUint16(s.b[:], x) +} + +func store_le_byte2_uint16_aligned_offset1(s *memCombineAligned, x uint16) { + // riscv64:`MOVB` -`MOVH` + binary.LittleEndian.PutUint16(s.b[1:], x) +} + +func store_le_byte8_uint64_unaligned(s *memCombineUnaligned08, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[:], x) +} + +func store_le_byte8_uint64_unaligned_offset7(s *memCombineUnaligned78, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset1(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[1:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset2(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[2:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset3(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[3:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset4(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[4:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset5(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[5:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset6(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[6:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset7(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[7:], x) +} + +func store_le_byte8_uint64_unaligned_slice_offset8(s *memCombineUnaligned16, x uint64) { + // riscv64:`MOVB` -`MOV ` + binary.LittleEndian.PutUint64(s.b[8:], x) +} + func store_le32(b []byte, x uint32) { // amd64:`MOVL ` // arm64:`MOVW` -`MOV[BH]` @@ -1150,3 +1490,28 @@ func dwstoreOrder(p *struct { p.e = true p.b = b } + +// --------------------------------------------------- // +// arm64 spill/reload pair coalescing // +// --------------------------------------------------- // + +// Spills and reloads do not exist when the SSA pair pass runs: regalloc +// inserts them later, so they never get a chance to be fused by that pass. +// A late Prog-level pass in cmd/compile/internal/arm64 catches adjacent +// spill/reload pairs that target the same base register at consecutive +// 8-byte offsets. These tests pin down that behavior. + +//go:noinline +func dwpairClobber() {} + +// Two distinct values that need to survive a call: regalloc spills both, +// and the late pass coalesces the adjacent reloads into a single LDP. The +// pattern requires a spill-slot (SP) base so that a frame-pointer epilogue +// LDP, which uses (RSP), cannot satisfy it. +func dwpairSpillReloadDistinct(p, q *int) (int, int) { + a := *p + b := *q + dwpairClobber() + // arm64:`LDP\s.+\(SP\), \(R[0-9]+, R[0-9]+\)` + return a, b +} diff --git a/test/escape_reflect.go b/test/escape_reflect.go index e50323702e9162..69cc5d6aca2fd4 100644 --- a/test/escape_reflect.go +++ b/test/escape_reflect.go @@ -370,17 +370,17 @@ func convert2(x []byte) string { // ERROR "leaking param: x$" return v.Convert(stringTyp).String() } -// Unfortunate: v doesn't need to leak, x (the interface storage) doesn't need to escape. -func set1(v reflect.Value, x int) { // ERROR "leaking param: v$" +// Unfortunate: x (the interface storage) doesn't need to escape. +func set1(v reflect.Value, x int) { // ERROR "v does not escape" vx := reflect.ValueOf(x) // ERROR "x escapes to heap" v.Set(vx) } -// Unfortunate: a can be stack allocated, x (the interface storage) doesn't need to escape. +// Unfortunate: x (the interface storage) doesn't need to escape. func set2(x int) int64 { - var a int // ERROR "moved to heap: a" - v := reflect.ValueOf(&a).Elem() - vx := reflect.ValueOf(x) // ERROR "x escapes to heap" + var a int + v := reflect.ValueOf(&a).Elem() // a should not escape, no error printed + vx := reflect.ValueOf(x) // ERROR "x escapes to heap" v.Set(vx) return v.Int() } @@ -396,15 +396,19 @@ func set4(x int) int { return int(v.Int()) } -func set5(v reflect.Value, x string) { // ERROR "v does not escape" "leaking param: x$" +func set5(v any, x reflect.Value) { // ERROR "v does not escape" "leaking param: x$" + reflect.ValueOf(v).Elem().Set(x) +} + +func setstring(v reflect.Value, x string) { // ERROR "v does not escape" "leaking param: x$" v.SetString(x) } -func set6(v reflect.Value, x []byte) { // ERROR "v does not escape" "leaking param: x$" +func setbytes(v reflect.Value, x []byte) { // ERROR "v does not escape" "leaking param: x$" v.SetBytes(x) } -func set7(v reflect.Value, x unsafe.Pointer) { // ERROR "v does not escape" "leaking param: x$" +func setpointer(v reflect.Value, x unsafe.Pointer) { // ERROR "v does not escape" "leaking param: x$" v.SetPointer(x) } diff --git a/test/fixedbugs/issue62469.go b/test/fixedbugs/issue62469.go index d850ccb289b38d..50a98306b32c2f 100644 --- a/test/fixedbugs/issue62469.go +++ b/test/fixedbugs/issue62469.go @@ -1,15 +1,80 @@ -// compile +// run // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package p +// Comparing a wrapped product difference against zero must respect the +// sign of the wrapped value, even when it wraps to MinInt. +package main + +import "fmt" + +// int64 (not int) so the cross product is 64-bit on every GOARCH. +type point struct{ x, y int64 } + +//go:noinline func sign(p1, p2, p3 point) bool { return (p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y) < 0 } -type point struct { - x, y int +type point32 struct{ x, y int32 } + +//go:noinline +func sign32(p1, p2, p3 point32) bool { + if (p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y) < 0 { + return true + } + return false +} + +const minInt64 = -1 << 63 + +//go:noinline +func msub64(x, y int64) bool { + if minInt64-x*y < 0 { + return true + } + return false +} + +const minInt32 = -1 << 31 + +//go:noinline +func msub32(x, y int32) bool { + if minInt32-x*y < 0 { + return true + } + return false +} + +const minUint32 = uint32(1 << 31) + +//go:noinline +func umod32(y uint32) bool { + if int32(minUint32%y) < 0 { + return true + } + return false +} + +func main() { + var bad bool + check := func(name string, got, want bool) { + if got != want { + fmt.Printf("%s = %v, want %v\n", name, got, want) + bad = true + } + } + + check("sign", sign(point{0, 2}, point{1 << 62, 0}, point{0, 0}), true) // wraps to MinInt64 + check("sign32", sign32(point32{0, 2}, point32{1 << 30, 0}, point32{0, 0}), true) // wraps to MinInt32 + check("msub64", msub64(0, 123), true) // minInt64 - 0 + check("msub32", msub32(0, 123), true) // minInt32 - 0 + check("umod32", umod32(0xffffffff), true) // minUint32 % big == minUint32 + + if bad { + panic("InvertFlags noov MinInt miscompilation") + } } diff --git a/test/fixedbugs/spillreload_arm64_pair.go b/test/fixedbugs/spillreload_arm64_pair.go new file mode 100644 index 00000000000000..8572c85587d7b4 --- /dev/null +++ b/test/fixedbugs/spillreload_arm64_pair.go @@ -0,0 +1,106 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Regression coverage for the late spill/reload pair coalescer on arm64 +// (cmd/compile/internal/arm64.pairSpills). When the coalescer fused two +// adjacent AMOVD reloads into a single LDP, paths that branched directly to +// the second reload would silently skip the fused load and observe a stale +// or uninitialized register. The fix records branch and jump-table targets +// before fusing and refuses to fuse when the second instruction is one. +// These functions exercise patterns that produce strictly-adjacent reloads +// and conditional branches over calls, including the shape from +// runtime.schedule. +// +// The check is end-to-end: each function returns a value that depends on +// every spilled variable, so a missing reload produces a wrong result and +// the program panics. + +package main + +import "fmt" + +//go:noinline +func sink(int) {} + +//go:noinline +func sink2(int, int) {} + +func callTwoVars(p, q *int) (int, int) { + a := *p + b := *q + sink(0) + return a, b +} + +func condCallTwoVars(c bool, p, q *int) (int, int) { + a := *p + b := *q + if c { + sink(0) + } + return a, b +} + +// condCallThreeVars mimics the shape that produced the original +// miscompile: a conditional call surrounded by values that need to +// survive it, with the join landing on the second of two adjacent +// reloads. +func condCallThreeVars(c bool, p, q, r *int) (int, int, int) { + a := *p + b := *q + d := *r + if c { + sink2(a, b) + } + return a, b, d +} + +func loopReload(p []int) int { + s := 0 + for _, v := range p { + sink(v) + s += v + } + return s +} + +func nestedConds(c1, c2 bool, p, q, r *int) (int, int, int) { + a := *p + b := *q + d := *r + if c1 { + sink(a) + if c2 { + sink(b) + } + } + return a, b, d +} + +func main() { + x, y, z := 7, 11, 13 + if a, b := callTwoVars(&x, &y); a != 7 || b != 11 { + panic(fmt.Sprintf("callTwoVars = %d, %d", a, b)) + } + for _, c := range []bool{false, true} { + if a, b := condCallTwoVars(c, &x, &y); a != 7 || b != 11 { + panic(fmt.Sprintf("condCallTwoVars(%v) = %d, %d", c, a, b)) + } + if a, b, d := condCallThreeVars(c, &x, &y, &z); a != 7 || b != 11 || d != 13 { + panic(fmt.Sprintf("condCallThreeVars(%v) = %d, %d, %d", c, a, b, d)) + } + } + for _, c1 := range []bool{false, true} { + for _, c2 := range []bool{false, true} { + if a, b, d := nestedConds(c1, c2, &x, &y, &z); a != 7 || b != 11 || d != 13 { + panic(fmt.Sprintf("nestedConds(%v,%v) = %d, %d, %d", c1, c2, a, b, d)) + } + } + } + if s := loopReload([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); s != 55 { + panic(fmt.Sprintf("loopReload = %d", s)) + } +}