diff --git a/doc/go1.15.html b/doc/go1.15.html index 79b18a3720..ba68c65463 100644 --- a/doc/go1.15.html +++ b/doc/go1.15.html @@ -26,7 +26,7 @@ Do not send CLs removing the interior tags from such phrases.

Changes to the language

-TODO + There are no changes to the language.

Ports

diff --git a/src/cmd/compile/fmtmap_test.go b/src/cmd/compile/fmtmap_test.go index 5a24296c5e..6f69abf4fb 100644 --- a/src/cmd/compile/fmtmap_test.go +++ b/src/cmd/compile/fmtmap_test.go @@ -151,9 +151,11 @@ var knownFormats = map[string]string{ "int %x": "", "int16 %d": "", "int16 %x": "", + "int32 %#x": "", "int32 %d": "", "int32 %v": "", "int32 %x": "", + "int64 %#x": "", "int64 %+d": "", "int64 %-10d": "", "int64 %.5d": "", diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index b58696d650..47cb422ab1 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -1252,11 +1252,11 @@ var blockJump = [...]struct { ssa.BlockAMD64NAN: {x86.AJPS, x86.AJPC}, } -var eqfJumps = [2][2]gc.FloatingEQNEJump{ +var eqfJumps = [2][2]gc.IndexJump{ {{Jump: x86.AJNE, Index: 1}, {Jump: x86.AJPS, Index: 1}}, // next == b.Succs[0] {{Jump: x86.AJNE, Index: 1}, {Jump: x86.AJPC, Index: 0}}, // next == b.Succs[1] } -var nefJumps = [2][2]gc.FloatingEQNEJump{ +var nefJumps = [2][2]gc.IndexJump{ {{Jump: x86.AJNE, Index: 0}, {Jump: x86.AJPC, Index: 1}}, // next == b.Succs[0] {{Jump: x86.AJNE, Index: 0}, {Jump: x86.AJPS, Index: 0}}, // next == b.Succs[1] } @@ -1296,10 +1296,10 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.To.Sym = b.Aux.(*obj.LSym) case ssa.BlockAMD64EQF: - s.FPJump(b, next, &eqfJumps) + s.CombJump(b, next, &eqfJumps) case ssa.BlockAMD64NEF: - s.FPJump(b, next, &nefJumps) + s.CombJump(b, next, &nefJumps) case ssa.BlockAMD64EQ, ssa.BlockAMD64NE, ssa.BlockAMD64LT, ssa.BlockAMD64GE, diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go index 06c520d76a..709253b48d 100644 --- a/src/cmd/compile/internal/arm64/ssa.go +++ b/src/cmd/compile/internal/arm64/ssa.go @@ -998,6 +998,20 @@ var blockJump = map[ssa.BlockKind]struct { ssa.BlockARM64FGE: {arm64.ABGE, arm64.ABLT}, ssa.BlockARM64FLE: {arm64.ABLS, arm64.ABHI}, ssa.BlockARM64FGT: {arm64.ABGT, arm64.ABLE}, + ssa.BlockARM64LTnoov:{arm64.ABMI, arm64.ABPL}, + ssa.BlockARM64GEnoov:{arm64.ABPL, arm64.ABMI}, +} + +// To model a 'LEnoov' ('<=' without overflow checking) branching +var leJumps = [2][2]gc.IndexJump{ + {{Jump: arm64.ABEQ, Index: 0}, {Jump: arm64.ABPL, Index: 1}}, // next == b.Succs[0] + {{Jump: arm64.ABMI, Index: 0}, {Jump: arm64.ABEQ, Index: 0}}, // next == b.Succs[1] +} + +// To model a 'GTnoov' ('>' without overflow checking) branching +var gtJumps = [2][2]gc.IndexJump{ + {{Jump: arm64.ABMI, Index: 1}, {Jump: arm64.ABEQ, Index: 1}}, // next == b.Succs[0] + {{Jump: arm64.ABEQ, Index: 1}, {Jump: arm64.ABPL, Index: 0}}, // next == b.Succs[1] } func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { @@ -1045,7 +1059,8 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { ssa.BlockARM64Z, ssa.BlockARM64NZ, ssa.BlockARM64ZW, ssa.BlockARM64NZW, ssa.BlockARM64FLT, ssa.BlockARM64FGE, - ssa.BlockARM64FLE, ssa.BlockARM64FGT: + ssa.BlockARM64FLE, ssa.BlockARM64FGT, + ssa.BlockARM64LTnoov, ssa.BlockARM64GEnoov: jmp := blockJump[b.Kind] var p *obj.Prog switch next { @@ -1087,6 +1102,10 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.From.Type = obj.TYPE_CONST p.Reg = b.Controls[0].Reg() + case ssa.BlockARM64LEnoov: + s.CombJump(b, next, &leJumps) + case ssa.BlockARM64GTnoov: + s.CombJump(b, next, >Jumps) default: b.Fatalf("branch not implemented: %s", b.LongString()) } diff --git a/src/cmd/compile/internal/gc/plive.go b/src/cmd/compile/internal/gc/plive.go index a4c051bda6..e2de6286a0 100644 --- a/src/cmd/compile/internal/gc/plive.go +++ b/src/cmd/compile/internal/gc/plive.go @@ -1249,7 +1249,7 @@ func (lv *Liveness) compact(b *ssa.Block) { if go115ReduceLiveness { hasStackMap := lv.hasStackMap(v) isUnsafePoint := lv.allUnsafe || lv.unsafePoints.Get(int32(v.ID)) - idx := LivenessIndex{StackMapDontCare, 0, isUnsafePoint} + idx := LivenessIndex{StackMapDontCare, StackMapDontCare, isUnsafePoint} if hasStackMap { idx.stackMapIndex = lv.stackMapSet.add(lv.livevars[pos].vars) pos++ diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 70f6dd6e18..c0902cdea6 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -6313,34 +6313,39 @@ func defframe(s *SSAGenState, e *ssafn) { thearch.ZeroRange(pp, p, frame+lo, hi-lo, &state) } -type FloatingEQNEJump struct { +// For generating consecutive jump instructions to model a specific branching +type IndexJump struct { Jump obj.As Index int } -func (s *SSAGenState) oneFPJump(b *ssa.Block, jumps *FloatingEQNEJump) { - p := s.Prog(jumps.Jump) - p.To.Type = obj.TYPE_BRANCH +func (s *SSAGenState) oneJump(b *ssa.Block, jump *IndexJump) { + p := s.Br(jump.Jump, b.Succs[jump.Index].Block()) p.Pos = b.Pos - to := jumps.Index - s.Branches = append(s.Branches, Branch{p, b.Succs[to].Block()}) } -func (s *SSAGenState) FPJump(b, next *ssa.Block, jumps *[2][2]FloatingEQNEJump) { +// CombJump generates combinational instructions (2 at present) for a block jump, +// thereby the behaviour of non-standard condition codes could be simulated +func (s *SSAGenState) CombJump(b, next *ssa.Block, jumps *[2][2]IndexJump) { switch next { case b.Succs[0].Block(): - s.oneFPJump(b, &jumps[0][0]) - s.oneFPJump(b, &jumps[0][1]) + s.oneJump(b, &jumps[0][0]) + s.oneJump(b, &jumps[0][1]) case b.Succs[1].Block(): - s.oneFPJump(b, &jumps[1][0]) - s.oneFPJump(b, &jumps[1][1]) + s.oneJump(b, &jumps[1][0]) + s.oneJump(b, &jumps[1][1]) default: - s.oneFPJump(b, &jumps[1][0]) - s.oneFPJump(b, &jumps[1][1]) - q := s.Prog(obj.AJMP) + var q *obj.Prog + if b.Likely != ssa.BranchUnlikely { + s.oneJump(b, &jumps[1][0]) + s.oneJump(b, &jumps[1][1]) + q = s.Br(obj.AJMP, b.Succs[1].Block()) + } else { + s.oneJump(b, &jumps[0][0]) + s.oneJump(b, &jumps[0][1]) + q = s.Br(obj.AJMP, b.Succs[0].Block()) + } q.Pos = b.Pos - q.To.Type = obj.TYPE_BRANCH - s.Branches = append(s.Branches, Branch{q, b.Succs[1].Block()}) } } diff --git a/src/cmd/compile/internal/ssa/gen/ARM64.rules b/src/cmd/compile/internal/ssa/gen/ARM64.rules index 03202414d6..47f221495a 100644 --- a/src/cmd/compile/internal/ssa/gen/ARM64.rules +++ b/src/cmd/compile/internal/ssa/gen/ARM64.rules @@ -598,31 +598,31 @@ (EQ (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (EQ (CMNconst [c] y) yes no) (NE (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (NE (CMNconst [c] y) yes no) -(LT (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (LT (CMNconst [c] y) yes no) -(LE (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (LE (CMNconst [c] y) yes no) -(GT (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (GT (CMNconst [c] y) yes no) -(GE (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (GE (CMNconst [c] y) yes no) +(LT (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (LTnoov (CMNconst [c] y) yes no) +(LE (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (LEnoov (CMNconst [c] y) yes no) +(GT (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (GTnoov (CMNconst [c] y) yes no) +(GE (CMPconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (GEnoov (CMNconst [c] y) yes no) (EQ (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (EQ (CMNWconst [int32(c)] y) yes no) (NE (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (NE (CMNWconst [int32(c)] y) yes no) -(LT (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (LT (CMNWconst [int32(c)] y) yes no) -(LE (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (LE (CMNWconst [int32(c)] y) yes no) -(GT (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (GT (CMNWconst [int32(c)] y) yes no) -(GE (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (GE (CMNWconst [int32(c)] y) yes no) +(LT (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (LTnoov (CMNWconst [int32(c)] y) yes no) +(LE (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (LEnoov (CMNWconst [int32(c)] y) yes no) +(GT (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (GTnoov (CMNWconst [int32(c)] y) yes no) +(GE (CMPWconst [0] x:(ADDconst [c] y)) yes no) && x.Uses == 1 => (GEnoov (CMNWconst [int32(c)] y) yes no) (EQ (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (EQ (CMN x y) yes no) (NE (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (NE (CMN x y) yes no) -(LT (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (LT (CMN x y) yes no) -(LE (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (LE (CMN x y) yes no) -(GT (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (GT (CMN x y) yes no) -(GE (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (GE (CMN x y) yes no) +(LT (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (LTnoov (CMN x y) yes no) +(LE (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (LEnoov (CMN x y) yes no) +(GT (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (GTnoov (CMN x y) yes no) +(GE (CMPconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (GEnoov (CMN x y) yes no) (EQ (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (EQ (CMNW x y) yes no) (NE (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (NE (CMNW x y) yes no) -(LT (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (LT (CMNW x y) yes no) -(LE (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (LE (CMNW x y) yes no) -(GT (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (GT (CMNW x y) yes no) -(GE (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (GE (CMNW x y) yes no) +(LT (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (LTnoov (CMNW x y) yes no) +(LE (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (LEnoov (CMNW x y) yes no) +(GT (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (GTnoov (CMNW x y) yes no) +(GE (CMPWconst [0] z:(ADD x y)) yes no) && z.Uses == 1 => (GEnoov (CMNW x y) yes no) (EQ (CMP x z:(NEG y)) yes no) && z.Uses == 1 => (EQ (CMN x y) yes no) (NE (CMP x z:(NEG y)) yes no) && z.Uses == 1 => (NE (CMN x y) yes no) @@ -645,31 +645,31 @@ (EQ (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (EQ (CMN a (MUL x y)) yes no) (NE (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (NE (CMN a (MUL x y)) yes no) -(LT (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (LT (CMN a (MUL x y)) yes no) -(LE (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (LE (CMN a (MUL x y)) yes no) -(GT (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (GT (CMN a (MUL x y)) yes no) -(GE (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (GE (CMN a (MUL x y)) yes no) +(LT (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (LTnoov (CMN a (MUL x y)) yes no) +(LE (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (LEnoov (CMN a (MUL x y)) yes no) +(GT (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (GTnoov (CMN a (MUL x y)) yes no) +(GE (CMPconst [0] z:(MADD a x y)) yes no) && z.Uses==1 => (GEnoov (CMN a (MUL x y)) yes no) (EQ (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (EQ (CMP a (MUL x y)) yes no) (NE (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (NE (CMP a (MUL x y)) yes no) -(LE (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (LE (CMP a (MUL x y)) yes no) -(LT (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (LT (CMP a (MUL x y)) yes no) -(GE (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (GE (CMP a (MUL x y)) yes no) -(GT (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (GT (CMP a (MUL x y)) yes no) +(LE (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (LEnoov (CMP a (MUL x y)) yes no) +(LT (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (LTnoov (CMP a (MUL x y)) yes no) +(GE (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (GEnoov (CMP a (MUL x y)) yes no) +(GT (CMPconst [0] z:(MSUB a x y)) yes no) && z.Uses==1 => (GTnoov (CMP a (MUL x y)) yes no) (EQ (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (EQ (CMNW a (MULW x y)) yes no) (NE (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (NE (CMNW a (MULW x y)) yes no) -(LE (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (LE (CMNW a (MULW x y)) yes no) -(LT (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (LT (CMNW a (MULW x y)) yes no) -(GE (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (GE (CMNW a (MULW x y)) yes no) -(GT (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (GT (CMNW a (MULW x y)) yes no) +(LE (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (LEnoov (CMNW a (MULW x y)) yes no) +(LT (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (LTnoov (CMNW a (MULW x y)) yes no) +(GE (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (GEnoov (CMNW a (MULW x y)) yes no) +(GT (CMPWconst [0] z:(MADDW a x y)) yes no) && z.Uses==1 => (GTnoov (CMNW a (MULW x y)) yes no) (EQ (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (EQ (CMPW a (MULW x y)) yes no) (NE (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (NE (CMPW a (MULW x y)) yes no) -(LE (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (LE (CMPW a (MULW x y)) yes no) -(LT (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (LT (CMPW a (MULW x y)) yes no) -(GE (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (GE (CMPW a (MULW x y)) yes no) -(GT (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (GT (CMPW a (MULW x y)) yes no) +(LE (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (LEnoov (CMPW a (MULW x y)) yes no) +(LT (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (LTnoov (CMPW a (MULW x y)) yes no) +(GE (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (GEnoov (CMPW a (MULW x y)) yes no) +(GT (CMPWconst [0] z:(MSUBW a x y)) yes no) && z.Uses==1 => (GTnoov (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) @@ -1503,6 +1503,10 @@ (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 CSEL(0) (CSEL {cc} x y (InvertFlags cmp)) -> (CSEL {arm64Invert(cc.(Op))} x y cmp) diff --git a/src/cmd/compile/internal/ssa/gen/ARM64Ops.go b/src/cmd/compile/internal/ssa/gen/ARM64Ops.go index 73e18bc56a..63faab2909 100644 --- a/src/cmd/compile/internal/ssa/gen/ARM64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/ARM64Ops.go @@ -701,6 +701,10 @@ func init() { {name: "FLE", controls: 1}, {name: "FGT", controls: 1}, {name: "FGE", controls: 1}, + {name: "LTnoov", controls: 1}, // 'LT' but without honoring overflow + {name: "LEnoov", controls: 1}, // 'LE' but without honoring overflow + {name: "GTnoov", controls: 1}, // 'GT' but without honoring overflow + {name: "GEnoov", controls: 1}, // 'GE' but without honoring overflow } archs = append(archs, arch{ diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index d619f36cf5..4a83a46be2 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -82,6 +82,10 @@ const ( BlockARM64FLE BlockARM64FGT BlockARM64FGE + BlockARM64LTnoov + BlockARM64LEnoov + BlockARM64GTnoov + BlockARM64GEnoov BlockMIPSEQ BlockMIPSNE @@ -192,26 +196,30 @@ var blockString = [...]string{ BlockARMUGT: "UGT", BlockARMUGE: "UGE", - BlockARM64EQ: "EQ", - BlockARM64NE: "NE", - BlockARM64LT: "LT", - BlockARM64LE: "LE", - BlockARM64GT: "GT", - BlockARM64GE: "GE", - BlockARM64ULT: "ULT", - BlockARM64ULE: "ULE", - BlockARM64UGT: "UGT", - BlockARM64UGE: "UGE", - BlockARM64Z: "Z", - BlockARM64NZ: "NZ", - BlockARM64ZW: "ZW", - BlockARM64NZW: "NZW", - BlockARM64TBZ: "TBZ", - BlockARM64TBNZ: "TBNZ", - BlockARM64FLT: "FLT", - BlockARM64FLE: "FLE", - BlockARM64FGT: "FGT", - BlockARM64FGE: "FGE", + BlockARM64EQ: "EQ", + BlockARM64NE: "NE", + BlockARM64LT: "LT", + BlockARM64LE: "LE", + BlockARM64GT: "GT", + BlockARM64GE: "GE", + BlockARM64ULT: "ULT", + BlockARM64ULE: "ULE", + BlockARM64UGT: "UGT", + BlockARM64UGE: "UGE", + BlockARM64Z: "Z", + BlockARM64NZ: "NZ", + BlockARM64ZW: "ZW", + BlockARM64NZW: "NZW", + BlockARM64TBZ: "TBZ", + BlockARM64TBNZ: "TBNZ", + BlockARM64FLT: "FLT", + BlockARM64FLE: "FLE", + BlockARM64FGT: "FGT", + BlockARM64FGE: "FGE", + BlockARM64LTnoov: "LTnoov", + BlockARM64LEnoov: "LEnoov", + BlockARM64GTnoov: "GTnoov", + BlockARM64GEnoov: "GEnoov", BlockMIPSEQ: "EQ", BlockMIPSNE: "NE", diff --git a/src/cmd/compile/internal/ssa/rewriteARM64.go b/src/cmd/compile/internal/ssa/rewriteARM64.go index d243ea9407..4b8ef43303 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM64.go +++ b/src/cmd/compile/internal/ssa/rewriteARM64.go @@ -26436,7 +26436,7 @@ func rewriteBlockARM64(b *Block) bool { } // match: (GE (CMPconst [0] x:(ADDconst [c] y)) yes no) // cond: x.Uses == 1 - // result: (GE (CMNconst [c] y) yes no) + // result: (GEnoov (CMNconst [c] y) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -26454,12 +26454,12 @@ func rewriteBlockARM64(b *Block) bool { v0 := b.NewValue0(v_0.Pos, OpARM64CMNconst, types.TypeFlags) v0.AuxInt = int64ToAuxInt(c) v0.AddArg(y) - b.resetWithControl(BlockARM64GE, v0) + b.resetWithControl(BlockARM64GEnoov, v0) return true } // match: (GE (CMPWconst [0] x:(ADDconst [c] y)) yes no) // cond: x.Uses == 1 - // result: (GE (CMNWconst [int32(c)] y) yes no) + // result: (GEnoov (CMNWconst [int32(c)] y) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -26477,12 +26477,12 @@ func rewriteBlockARM64(b *Block) bool { v0 := b.NewValue0(v_0.Pos, OpARM64CMNWconst, types.TypeFlags) v0.AuxInt = int32ToAuxInt(int32(c)) v0.AddArg(y) - b.resetWithControl(BlockARM64GE, v0) + b.resetWithControl(BlockARM64GEnoov, v0) return true } // match: (GE (CMPconst [0] z:(ADD x y)) yes no) // cond: z.Uses == 1 - // result: (GE (CMN x y) yes no) + // result: (GEnoov (CMN x y) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -26503,14 +26503,14 @@ func rewriteBlockARM64(b *Block) bool { } v0 := b.NewValue0(v_0.Pos, OpARM64CMN, types.TypeFlags) v0.AddArg2(x, y) - b.resetWithControl(BlockARM64GE, v0) + b.resetWithControl(BlockARM64GEnoov, v0) return true } break } // match: (GE (CMPWconst [0] z:(ADD x y)) yes no) // cond: z.Uses == 1 - // result: (GE (CMNW x y) yes no) + // result: (GEnoov (CMNW x y) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -26531,7 +26531,7 @@ func rewriteBlockARM64(b *Block) bool { } v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) v0.AddArg2(x, y) - b.resetWithControl(BlockARM64GE, v0) + b.resetWithControl(BlockARM64GEnoov, v0) return true } break @@ -26578,7 +26578,7 @@ func rewriteBlockARM64(b *Block) bool { } // match: (GE (CMPconst [0] z:(MADD a x y)) yes no) // cond: z.Uses==1 - // result: (GE (CMN a (MUL x y)) yes no) + // result: (GEnoov (CMN a (MUL x y)) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -26598,12 +26598,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GE, v0) + b.resetWithControl(BlockARM64GEnoov, v0) return true } // match: (GE (CMPconst [0] z:(MSUB a x y)) yes no) // cond: z.Uses==1 - // result: (GE (CMP a (MUL x y)) yes no) + // 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 { @@ -26623,12 +26623,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GE, v0) + b.resetWithControl(BlockARM64GEnoov, v0) return true } // match: (GE (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 - // result: (GE (CMNW a (MULW x y)) yes no) + // result: (GEnoov (CMNW a (MULW x y)) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -26648,12 +26648,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GE, v0) + b.resetWithControl(BlockARM64GEnoov, v0) return true } // match: (GE (CMPWconst [0] z:(MSUBW a x y)) yes no) // cond: z.Uses==1 - // result: (GE (CMPW a (MULW x y)) yes no) + // 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 { @@ -26673,7 +26673,7 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GE, v0) + b.resetWithControl(BlockARM64GEnoov, v0) return true } // match: (GE (CMPWconst [0] x) yes no) @@ -26740,6 +26740,15 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LE, cmp) return true } + case BlockARM64GEnoov: + // 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 (CMPWconst [0] x:(ANDconst [c] y)) yes no) // cond: x.Uses == 1 @@ -26845,7 +26854,7 @@ func rewriteBlockARM64(b *Block) bool { } // match: (GT (CMPconst [0] x:(ADDconst [c] y)) yes no) // cond: x.Uses == 1 - // result: (GT (CMNconst [c] y) yes no) + // result: (GTnoov (CMNconst [c] y) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -26863,12 +26872,12 @@ func rewriteBlockARM64(b *Block) bool { v0 := b.NewValue0(v_0.Pos, OpARM64CMNconst, types.TypeFlags) v0.AuxInt = int64ToAuxInt(c) v0.AddArg(y) - b.resetWithControl(BlockARM64GT, v0) + b.resetWithControl(BlockARM64GTnoov, v0) return true } // match: (GT (CMPWconst [0] x:(ADDconst [c] y)) yes no) // cond: x.Uses == 1 - // result: (GT (CMNWconst [int32(c)] y) yes no) + // result: (GTnoov (CMNWconst [int32(c)] y) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -26886,12 +26895,12 @@ func rewriteBlockARM64(b *Block) bool { v0 := b.NewValue0(v_0.Pos, OpARM64CMNWconst, types.TypeFlags) v0.AuxInt = int32ToAuxInt(int32(c)) v0.AddArg(y) - b.resetWithControl(BlockARM64GT, v0) + b.resetWithControl(BlockARM64GTnoov, v0) return true } // match: (GT (CMPconst [0] z:(ADD x y)) yes no) // cond: z.Uses == 1 - // result: (GT (CMN x y) yes no) + // result: (GTnoov (CMN x y) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -26912,14 +26921,14 @@ func rewriteBlockARM64(b *Block) bool { } v0 := b.NewValue0(v_0.Pos, OpARM64CMN, types.TypeFlags) v0.AddArg2(x, y) - b.resetWithControl(BlockARM64GT, v0) + b.resetWithControl(BlockARM64GTnoov, v0) return true } break } // match: (GT (CMPWconst [0] z:(ADD x y)) yes no) // cond: z.Uses == 1 - // result: (GT (CMNW x y) yes no) + // result: (GTnoov (CMNW x y) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -26940,7 +26949,7 @@ func rewriteBlockARM64(b *Block) bool { } v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) v0.AddArg2(x, y) - b.resetWithControl(BlockARM64GT, v0) + b.resetWithControl(BlockARM64GTnoov, v0) return true } break @@ -26987,7 +26996,7 @@ func rewriteBlockARM64(b *Block) bool { } // match: (GT (CMPconst [0] z:(MADD a x y)) yes no) // cond: z.Uses==1 - // result: (GT (CMN a (MUL x y)) yes no) + // result: (GTnoov (CMN a (MUL x y)) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -27007,12 +27016,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GT, v0) + b.resetWithControl(BlockARM64GTnoov, v0) return true } // match: (GT (CMPconst [0] z:(MSUB a x y)) yes no) // cond: z.Uses==1 - // result: (GT (CMP a (MUL x y)) yes no) + // 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 { @@ -27032,12 +27041,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GT, v0) + b.resetWithControl(BlockARM64GTnoov, v0) return true } // match: (GT (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 - // result: (GT (CMNW a (MULW x y)) yes no) + // result: (GTnoov (CMNW a (MULW x y)) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -27057,12 +27066,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GT, v0) + b.resetWithControl(BlockARM64GTnoov, v0) return true } // match: (GT (CMPWconst [0] z:(MSUBW a x y)) yes no) // cond: z.Uses==1 - // result: (GT (CMPW a (MULW x y)) yes no) + // 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 { @@ -27082,7 +27091,7 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64GT, v0) + b.resetWithControl(BlockARM64GTnoov, v0) return true } // match: (GT (FlagEQ) yes no) @@ -27126,6 +27135,15 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64LT, cmp) return true } + case BlockARM64GTnoov: + // 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) @@ -27351,7 +27369,7 @@ func rewriteBlockARM64(b *Block) bool { } // match: (LE (CMPconst [0] x:(ADDconst [c] y)) yes no) // cond: x.Uses == 1 - // result: (LE (CMNconst [c] y) yes no) + // result: (LEnoov (CMNconst [c] y) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -27369,12 +27387,12 @@ func rewriteBlockARM64(b *Block) bool { v0 := b.NewValue0(v_0.Pos, OpARM64CMNconst, types.TypeFlags) v0.AuxInt = int64ToAuxInt(c) v0.AddArg(y) - b.resetWithControl(BlockARM64LE, v0) + b.resetWithControl(BlockARM64LEnoov, v0) return true } // match: (LE (CMPWconst [0] x:(ADDconst [c] y)) yes no) // cond: x.Uses == 1 - // result: (LE (CMNWconst [int32(c)] y) yes no) + // result: (LEnoov (CMNWconst [int32(c)] y) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -27392,12 +27410,12 @@ func rewriteBlockARM64(b *Block) bool { v0 := b.NewValue0(v_0.Pos, OpARM64CMNWconst, types.TypeFlags) v0.AuxInt = int32ToAuxInt(int32(c)) v0.AddArg(y) - b.resetWithControl(BlockARM64LE, v0) + b.resetWithControl(BlockARM64LEnoov, v0) return true } // match: (LE (CMPconst [0] z:(ADD x y)) yes no) // cond: z.Uses == 1 - // result: (LE (CMN x y) yes no) + // result: (LEnoov (CMN x y) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -27418,14 +27436,14 @@ func rewriteBlockARM64(b *Block) bool { } v0 := b.NewValue0(v_0.Pos, OpARM64CMN, types.TypeFlags) v0.AddArg2(x, y) - b.resetWithControl(BlockARM64LE, v0) + b.resetWithControl(BlockARM64LEnoov, v0) return true } break } // match: (LE (CMPWconst [0] z:(ADD x y)) yes no) // cond: z.Uses == 1 - // result: (LE (CMNW x y) yes no) + // result: (LEnoov (CMNW x y) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -27446,7 +27464,7 @@ func rewriteBlockARM64(b *Block) bool { } v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) v0.AddArg2(x, y) - b.resetWithControl(BlockARM64LE, v0) + b.resetWithControl(BlockARM64LEnoov, v0) return true } break @@ -27493,7 +27511,7 @@ func rewriteBlockARM64(b *Block) bool { } // match: (LE (CMPconst [0] z:(MADD a x y)) yes no) // cond: z.Uses==1 - // result: (LE (CMN a (MUL x y)) yes no) + // result: (LEnoov (CMN a (MUL x y)) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -27513,12 +27531,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LE, v0) + b.resetWithControl(BlockARM64LEnoov, v0) return true } // match: (LE (CMPconst [0] z:(MSUB a x y)) yes no) // cond: z.Uses==1 - // result: (LE (CMP a (MUL x y)) yes no) + // 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 { @@ -27538,12 +27556,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LE, v0) + b.resetWithControl(BlockARM64LEnoov, v0) return true } // match: (LE (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 - // result: (LE (CMNW a (MULW x y)) yes no) + // result: (LEnoov (CMNW a (MULW x y)) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -27563,12 +27581,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LE, v0) + b.resetWithControl(BlockARM64LEnoov, v0) return true } // match: (LE (CMPWconst [0] z:(MSUBW a x y)) yes no) // cond: z.Uses==1 - // result: (LE (CMPW a (MULW x y)) yes no) + // 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 { @@ -27588,7 +27606,7 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LE, v0) + b.resetWithControl(BlockARM64LEnoov, v0) return true } // match: (LE (FlagEQ) yes no) @@ -27631,6 +27649,15 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GE, cmp) return true } + case BlockARM64LEnoov: + // 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 (CMPWconst [0] x:(ANDconst [c] y)) yes no) // cond: x.Uses == 1 @@ -27736,7 +27763,7 @@ func rewriteBlockARM64(b *Block) bool { } // match: (LT (CMPconst [0] x:(ADDconst [c] y)) yes no) // cond: x.Uses == 1 - // result: (LT (CMNconst [c] y) yes no) + // result: (LTnoov (CMNconst [c] y) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -27754,12 +27781,12 @@ func rewriteBlockARM64(b *Block) bool { v0 := b.NewValue0(v_0.Pos, OpARM64CMNconst, types.TypeFlags) v0.AuxInt = int64ToAuxInt(c) v0.AddArg(y) - b.resetWithControl(BlockARM64LT, v0) + b.resetWithControl(BlockARM64LTnoov, v0) return true } // match: (LT (CMPWconst [0] x:(ADDconst [c] y)) yes no) // cond: x.Uses == 1 - // result: (LT (CMNWconst [int32(c)] y) yes no) + // result: (LTnoov (CMNWconst [int32(c)] y) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -27777,12 +27804,12 @@ func rewriteBlockARM64(b *Block) bool { v0 := b.NewValue0(v_0.Pos, OpARM64CMNWconst, types.TypeFlags) v0.AuxInt = int32ToAuxInt(int32(c)) v0.AddArg(y) - b.resetWithControl(BlockARM64LT, v0) + b.resetWithControl(BlockARM64LTnoov, v0) return true } // match: (LT (CMPconst [0] z:(ADD x y)) yes no) // cond: z.Uses == 1 - // result: (LT (CMN x y) yes no) + // result: (LTnoov (CMN x y) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -27803,14 +27830,14 @@ func rewriteBlockARM64(b *Block) bool { } v0 := b.NewValue0(v_0.Pos, OpARM64CMN, types.TypeFlags) v0.AddArg2(x, y) - b.resetWithControl(BlockARM64LT, v0) + b.resetWithControl(BlockARM64LTnoov, v0) return true } break } // match: (LT (CMPWconst [0] z:(ADD x y)) yes no) // cond: z.Uses == 1 - // result: (LT (CMNW x y) yes no) + // result: (LTnoov (CMNW x y) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -27831,7 +27858,7 @@ func rewriteBlockARM64(b *Block) bool { } v0 := b.NewValue0(v_0.Pos, OpARM64CMNW, types.TypeFlags) v0.AddArg2(x, y) - b.resetWithControl(BlockARM64LT, v0) + b.resetWithControl(BlockARM64LTnoov, v0) return true } break @@ -27878,7 +27905,7 @@ func rewriteBlockARM64(b *Block) bool { } // match: (LT (CMPconst [0] z:(MADD a x y)) yes no) // cond: z.Uses==1 - // result: (LT (CMN a (MUL x y)) yes no) + // result: (LTnoov (CMN a (MUL x y)) yes no) for b.Controls[0].Op == OpARM64CMPconst { v_0 := b.Controls[0] if auxIntToInt64(v_0.AuxInt) != 0 { @@ -27898,12 +27925,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LT, v0) + b.resetWithControl(BlockARM64LTnoov, v0) return true } // match: (LT (CMPconst [0] z:(MSUB a x y)) yes no) // cond: z.Uses==1 - // result: (LT (CMP a (MUL x y)) yes no) + // 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 { @@ -27923,12 +27950,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MUL, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LT, v0) + b.resetWithControl(BlockARM64LTnoov, v0) return true } // match: (LT (CMPWconst [0] z:(MADDW a x y)) yes no) // cond: z.Uses==1 - // result: (LT (CMNW a (MULW x y)) yes no) + // result: (LTnoov (CMNW a (MULW x y)) yes no) for b.Controls[0].Op == OpARM64CMPWconst { v_0 := b.Controls[0] if auxIntToInt32(v_0.AuxInt) != 0 { @@ -27948,12 +27975,12 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LT, v0) + b.resetWithControl(BlockARM64LTnoov, v0) return true } // match: (LT (CMPWconst [0] z:(MSUBW a x y)) yes no) // cond: z.Uses==1 - // result: (LT (CMPW a (MULW x y)) yes no) + // 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 { @@ -27973,7 +28000,7 @@ func rewriteBlockARM64(b *Block) bool { v1 := b.NewValue0(v_0.Pos, OpARM64MULW, x.Type) v1.AddArg2(x, y) v0.AddArg2(a, v1) - b.resetWithControl(BlockARM64LT, v0) + b.resetWithControl(BlockARM64LTnoov, v0) return true } // match: (LT (CMPWconst [0] x) yes no) @@ -28041,6 +28068,15 @@ func rewriteBlockARM64(b *Block) bool { b.resetWithControl(BlockARM64GT, cmp) return true } + case BlockARM64LTnoov: + // 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 (CMPWconst [0] x:(ANDconst [c] y)) yes no) // cond: x.Uses == 1 diff --git a/src/cmd/compile/internal/ssa/rewriteCond_test.go b/src/cmd/compile/internal/ssa/rewriteCond_test.go new file mode 100644 index 0000000000..b8feff77e5 --- /dev/null +++ b/src/cmd/compile/internal/ssa/rewriteCond_test.go @@ -0,0 +1,536 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssa + +import ( + "math" + "math/rand" + "testing" + "runtime" +) + +var ( + x64 int64 = math.MaxInt64 - 2 + x64b int64 = math.MaxInt64 - 2 + x64c int64 = math.MaxInt64 - 2 + y64 int64 = math.MinInt64 + 1 + x32 int32 = math.MaxInt32 - 2 + x32b int32 = math.MaxInt32 - 2 + y32 int32 = math.MinInt32 + 1 + one64 int64 = 1 + one32 int32 = 1 + v64 int64 = 11 // ensure it's not 2**n +/- 1 + v64_n int64 = -11 + v32 int32 = 11 + v32_n int32 = -11 +) + +var crTests = []struct { + name string + tf func(t *testing.T) +}{ + {"AddConst64", testAddConst64}, + {"AddConst32", testAddConst32}, + {"AddVar64", testAddVar64}, + {"AddVar32", testAddVar32}, + {"MAddVar64", testMAddVar64}, + {"MAddVar32", testMAddVar32}, + {"MSubVar64", testMSubVar64}, + {"MSubVar32", testMSubVar32}, +} + +var crBenches = []struct { + name string + bf func(b *testing.B) +}{ + {"SoloJump", benchSoloJump}, + {"CombJump", benchCombJump}, +} + +// Test int32/int64's add/sub/madd/msub operations with boundary values to +// ensure the optimization to 'comparing to zero' expressions of if-statements +// yield expected results. +// 32 rewriting rules are covered. At least two scenarios for "Canonicalize +// the order of arguments to comparisons", which helps with CSE, are covered. +// The tedious if-else structures are necessary to ensure all concerned rules +// and machine code sequences are covered. +// It's for arm64 initially, please see https://github.com/golang/go/issues/38740 +func TestCondRewrite(t *testing.T) { + if runtime.GOARCH == "arm" { + t.Skip("fix on arm expected!") + } + for _, test := range crTests { + t.Run(test.name, test.tf) + } +} + +// Profile the aforementioned optimization from two angles: +// SoloJump: generated branching code has one 'jump', for '<' and '>=' +// CombJump: generated branching code has two consecutive 'jump', for '<=' and '>' +// We expect that 'CombJump' is generally on par with the non-optimized code, and +// 'SoloJump' demonstrates some improvement. +// It's for arm64 initially, please see https://github.com/golang/go/issues/38740 +func BenchmarkCondRewrite(b *testing.B) { + for _, bench := range crBenches { + b.Run(bench.name, bench.bf) + } +} + +// var +/- const +func testAddConst64(t *testing.T) { + if x64+11 < 0 { + } else { + t.Errorf("'%#x + 11 < 0' failed", x64) + } + + if x64+13 <= 0 { + } else { + t.Errorf("'%#x + 13 <= 0' failed", x64) + } + + if y64-11 > 0 { + } else { + t.Errorf("'%#x - 11 > 0' failed", y64) + } + + if y64-13 >= 0 { + } else { + t.Errorf("'%#x - 13 >= 0' failed", y64) + } + + if x64+19 > 0 { + t.Errorf("'%#x + 19 > 0' failed", x64) + } + + if x64+23 >= 0 { + t.Errorf("'%#x + 23 >= 0' failed", x64) + } + + if y64-19 < 0 { + t.Errorf("'%#x - 19 < 0' failed", y64) + } + + if y64-23 <= 0 { + t.Errorf("'%#x - 23 <= 0' failed", y64) + } +} + +// 32-bit var +/- const +func testAddConst32(t *testing.T) { + if x32+11 < 0 { + } else { + t.Errorf("'%#x + 11 < 0' failed", x32) + } + + if x32+13 <= 0 { + } else { + t.Errorf("'%#x + 13 <= 0' failed", x32) + } + + if y32-11 > 0 { + } else { + t.Errorf("'%#x - 11 > 0' failed", y32) + } + + if y32-13 >= 0 { + } else { + t.Errorf("'%#x - 13 >= 0' failed", y32) + } + + if x32+19 > 0 { + t.Errorf("'%#x + 19 > 0' failed", x32) + } + + if x32+23 >= 0 { + t.Errorf("'%#x + 23 >= 0' failed", x32) + } + + if y32-19 < 0 { + t.Errorf("'%#x - 19 < 0' failed", y32) + } + + if y32-23 <= 0 { + t.Errorf("'%#x - 23 <= 0' failed", y32) + } +} + +// var + var +func testAddVar64(t *testing.T) { + if x64+v64 < 0 { + } else { + t.Errorf("'%#x + %#x < 0' failed", x64, v64) + } + + if x64+v64 <= 0 { + } else { + t.Errorf("'%#x + %#x <= 0' failed", x64, v64) + } + + if y64+v64_n > 0 { + } else { + t.Errorf("'%#x + %#x > 0' failed", y64, v64_n) + } + + if y64+v64_n >= 0 { + } else { + t.Errorf("'%#x + %#x >= 0' failed", y64, v64_n) + } + + if x64+v64 > 0 { + t.Errorf("'%#x + %#x > 0' failed", x64, v64) + } + + if x64+v64 >= 0 { + t.Errorf("'%#x + %#x >= 0' failed", x64, v64) + } + + if y64+v64_n < 0 { + t.Errorf("'%#x + %#x < 0' failed", y64, v64_n) + } + + if y64+v64_n <= 0 { + t.Errorf("'%#x + %#x <= 0' failed", y64, v64_n) + } +} + +// 32-bit var+var +func testAddVar32(t *testing.T) { + if x32+v32 < 0 { + } else { + t.Errorf("'%#x + %#x < 0' failed", x32, v32) + } + + if x32+v32 <= 0 { + } else { + t.Errorf("'%#x + %#x <= 0' failed", x32, v32) + } + + if y32+v32_n > 0 { + } else { + t.Errorf("'%#x + %#x > 0' failed", y32, v32_n) + } + + if y32+v32_n >= 0 { + } else { + t.Errorf("'%#x + %#x >= 0' failed", y32, v32_n) + } + + if x32+v32 > 0 { + t.Errorf("'%#x + %#x > 0' failed", x32, v32) + } + + if x32+v32 >= 0 { + t.Errorf("'%#x + %#x >= 0' failed", x32, v32) + } + + if y32+v32_n < 0 { + t.Errorf("'%#x + %#x < 0' failed", y32, v32_n) + } + + if y32+v32_n <= 0 { + t.Errorf("'%#x + %#x <= 0' failed", y32, v32_n) + } +} + +// multiply-add +func testMAddVar64(t *testing.T) { + if x64+v64*one64 < 0 { + } else { + t.Errorf("'%#x + %#x*1 < 0' failed", x64, v64) + } + + if x64+v64*one64 <= 0 { + } else { + t.Errorf("'%#x + %#x*1 <= 0' failed", x64, v64) + } + + if y64+v64_n*one64 > 0 { + } else { + t.Errorf("'%#x + %#x*1 > 0' failed", y64, v64_n) + } + + if y64+v64_n*one64 >= 0 { + } else { + t.Errorf("'%#x + %#x*1 >= 0' failed", y64, v64_n) + } + + if x64+v64*one64 > 0 { + t.Errorf("'%#x + %#x*1 > 0' failed", x64, v64) + } + + if x64+v64*one64 >= 0 { + t.Errorf("'%#x + %#x*1 >= 0' failed", x64, v64) + } + + if y64+v64_n*one64 < 0 { + t.Errorf("'%#x + %#x*1 < 0' failed", y64, v64_n) + } + + if y64+v64_n*one64 <= 0 { + t.Errorf("'%#x + %#x*1 <= 0' failed", y64, v64_n) + } +} + +// 32-bit multiply-add +func testMAddVar32(t *testing.T) { + if x32+v32*one32 < 0 { + } else { + t.Errorf("'%#x + %#x*1 < 0' failed", x32, v32) + } + + if x32+v32*one32 <= 0 { + } else { + t.Errorf("'%#x + %#x*1 <= 0' failed", x32, v32) + } + + if y32+v32_n*one32 > 0 { + } else { + t.Errorf("'%#x + %#x*1 > 0' failed", y32, v32_n) + } + + if y32+v32_n*one32 >= 0 { + } else { + t.Errorf("'%#x + %#x*1 >= 0' failed", y32, v32_n) + } + + if x32+v32*one32 > 0 { + t.Errorf("'%#x + %#x*1 > 0' failed", x32, v32) + } + + if x32+v32*one32 >= 0 { + t.Errorf("'%#x + %#x*1 >= 0' failed", x32, v32) + } + + if y32+v32_n*one32 < 0 { + t.Errorf("'%#x + %#x*1 < 0' failed", y32, v32_n) + } + + if y32+v32_n*one32 <= 0 { + t.Errorf("'%#x + %#x*1 <= 0' failed", y32, v32_n) + } +} + +// multiply-sub +func testMSubVar64(t *testing.T) { + if x64-v64_n*one64 < 0 { + } else { + t.Errorf("'%#x - %#x*1 < 0' failed", x64, v64_n) + } + + if x64-v64_n*one64 <= 0 { + } else { + t.Errorf("'%#x - %#x*1 <= 0' failed", x64, v64_n) + } + + if y64-v64*one64 > 0 { + } else { + t.Errorf("'%#x - %#x*1 > 0' failed", y64, v64) + } + + if y64-v64*one64 >= 0 { + } else { + t.Errorf("'%#x - %#x*1 >= 0' failed", y64, v64) + } + + if x64-v64_n*one64 > 0 { + t.Errorf("'%#x - %#x*1 > 0' failed", x64, v64_n) + } + + if x64-v64_n*one64 >= 0 { + t.Errorf("'%#x - %#x*1 >= 0' failed", x64, v64_n) + } + + if y64-v64*one64 < 0 { + t.Errorf("'%#x - %#x*1 < 0' failed", y64, v64) + } + + if y64-v64*one64 <= 0 { + t.Errorf("'%#x - %#x*1 <= 0' failed", y64, v64) + } + + if x64-x64b*one64 < 0 { + t.Errorf("'%#x - %#x*1 < 0' failed", x64, x64b) + } + + if x64-x64b*one64 >= 0 { + } else { + t.Errorf("'%#x - %#x*1 >= 0' failed", x64, x64b) + } +} + +// 32-bit multiply-sub +func testMSubVar32(t *testing.T) { + if x32-v32_n*one32 < 0 { + } else { + t.Errorf("'%#x - %#x*1 < 0' failed", x32, v32_n) + } + + if x32-v32_n*one32 <= 0 { + } else { + t.Errorf("'%#x - %#x*1 <= 0' failed", x32, v32_n) + } + + if y32-v32*one32 > 0 { + } else { + t.Errorf("'%#x - %#x*1 > 0' failed", y32, v32) + } + + if y32-v32*one32 >= 0 { + } else { + t.Errorf("'%#x - %#x*1 >= 0' failed", y32, v32) + } + + if x32-v32_n*one32 > 0 { + t.Errorf("'%#x - %#x*1 > 0' failed", x32, v32_n) + } + + if x32-v32_n*one32 >= 0 { + t.Errorf("'%#x - %#x*1 >= 0' failed", x32, v32_n) + } + + if y32-v32*one32 < 0 { + t.Errorf("'%#x - %#x*1 < 0' failed", y32, v32) + } + + if y32-v32*one32 <= 0 { + t.Errorf("'%#x - %#x*1 <= 0' failed", y32, v32) + } + + if x32-x32b*one32 < 0 { + t.Errorf("'%#x - %#x*1 < 0' failed", x32, x32b) + } + + if x32-x32b*one32 >= 0 { + } else { + t.Errorf("'%#x - %#x*1 >= 0' failed", x32, x32b) + } +} + +var rnd = rand.New(rand.NewSource(0)) +var sink int64 + +func benchSoloJump(b *testing.B) { + r1 := x64 + r2 := x64b + r3 := x64c + r4 := y64 + d := rnd.Int63n(10) + + // 6 out 10 conditions evaluate to true + for i := 0; i < b.N; i++ { + if r1+r2 < 0 { + d *= 2 + d /= 2 + } + + if r1+r3 >= 0 { + d *= 2 + d /= 2 + } + + if r1+r2*one64 < 0 { + d *= 2 + d /= 2 + } + + if r2+r3*one64 >= 0 { + d *= 2 + d /= 2 + } + + if r1-r2*v64 >= 0 { + d *= 2 + d /= 2 + } + + if r3-r4*v64 < 0 { + d *= 2 + d /= 2 + } + + if r1+11 < 0 { + d *= 2 + d /= 2 + } + + if r1+13 >= 0 { + d *= 2 + d /= 2 + } + + if r4-17 < 0 { + d *= 2 + d /= 2 + } + + if r4-19 >= 0 { + d *= 2 + d /= 2 + } + } + sink = d +} + +func benchCombJump(b *testing.B) { + r1 := x64 + r2 := x64b + r3 := x64c + r4 := y64 + d := rnd.Int63n(10) + + // 6 out 10 conditions evaluate to true + for i := 0; i < b.N; i++ { + if r1+r2 <= 0 { + d *= 2 + d /= 2 + } + + if r1+r3 > 0 { + d *= 2 + d /= 2 + } + + if r1+r2*one64 <= 0 { + d *= 2 + d /= 2 + } + + if r2+r3*one64 > 0 { + d *= 2 + d /= 2 + } + + if r1-r2*v64 > 0 { + d *= 2 + d /= 2 + } + + if r3-r4*v64 <= 0 { + d *= 2 + d /= 2 + } + + if r1+11 <= 0 { + d *= 2 + d /= 2 + } + + if r1+13 > 0 { + d *= 2 + d /= 2 + } + + if r4-17 <= 0 { + d *= 2 + d /= 2 + } + + if r4-19 > 0 { + d *= 2 + d /= 2 + } + } + sink = d +} diff --git a/src/cmd/compile/internal/x86/ssa.go b/src/cmd/compile/internal/x86/ssa.go index 0c7e5bdb97..2de978c28a 100644 --- a/src/cmd/compile/internal/x86/ssa.go +++ b/src/cmd/compile/internal/x86/ssa.go @@ -885,11 +885,11 @@ var blockJump = [...]struct { ssa.Block386NAN: {x86.AJPS, x86.AJPC}, } -var eqfJumps = [2][2]gc.FloatingEQNEJump{ +var eqfJumps = [2][2]gc.IndexJump{ {{Jump: x86.AJNE, Index: 1}, {Jump: x86.AJPS, Index: 1}}, // next == b.Succs[0] {{Jump: x86.AJNE, Index: 1}, {Jump: x86.AJPC, Index: 0}}, // next == b.Succs[1] } -var nefJumps = [2][2]gc.FloatingEQNEJump{ +var nefJumps = [2][2]gc.IndexJump{ {{Jump: x86.AJNE, Index: 0}, {Jump: x86.AJPC, Index: 1}}, // next == b.Succs[0] {{Jump: x86.AJNE, Index: 0}, {Jump: x86.AJPS, Index: 0}}, // next == b.Succs[1] } @@ -929,10 +929,10 @@ func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { p.To.Sym = b.Aux.(*obj.LSym) case ssa.Block386EQF: - s.FPJump(b, next, &eqfJumps) + s.CombJump(b, next, &eqfJumps) case ssa.Block386NEF: - s.FPJump(b, next, &nefJumps) + s.CombJump(b, next, &nefJumps) case ssa.Block386EQ, ssa.Block386NE, ssa.Block386LT, ssa.Block386GE, diff --git a/src/cmd/go/internal/clean/clean.go b/src/cmd/go/internal/clean/clean.go index d5028de970..99704cb2b1 100644 --- a/src/cmd/go/internal/clean/clean.go +++ b/src/cmd/go/internal/clean/clean.go @@ -137,20 +137,27 @@ func runClean(cmd *base.Command, args []string) { if cfg.BuildN || cfg.BuildX { b.Showcmd("", "rm -r %s", strings.Join(subdirs, " ")) } - for _, d := range subdirs { - // Only print the first error - there may be many. - // This also mimics what os.RemoveAll(dir) would do. - if err := os.RemoveAll(d); err != nil && !printedErrors { - printedErrors = true - base.Errorf("go clean -cache: %v", err) + if !cfg.BuildN { + for _, d := range subdirs { + // Only print the first error - there may be many. + // This also mimics what os.RemoveAll(dir) would do. + if err := os.RemoveAll(d); err != nil && !printedErrors { + printedErrors = true + base.Errorf("go clean -cache: %v", err) + } } } } logFile := filepath.Join(dir, "log.txt") - if err := os.RemoveAll(logFile); err != nil && !printedErrors { - printedErrors = true - base.Errorf("go clean -cache: %v", err) + if cfg.BuildN || cfg.BuildX { + b.Showcmd("", "rm -f %s", logFile) + } + if !cfg.BuildN { + if err := os.RemoveAll(logFile); err != nil && !printedErrors { + printedErrors = true + base.Errorf("go clean -cache: %v", err) + } } } } diff --git a/src/cmd/go/internal/modfetch/proxy.go b/src/cmd/go/internal/modfetch/proxy.go index 3971598733..1c35d0b99b 100644 --- a/src/cmd/go/internal/modfetch/proxy.go +++ b/src/cmd/go/internal/modfetch/proxy.go @@ -171,6 +171,14 @@ func proxyList() ([]proxySpec, error) { fallBackOnError: fallBackOnError, }) } + + if len(proxyOnce.list) == 0 || + len(proxyOnce.list) == 1 && proxyOnce.list[0].url == "noproxy" { + // There were no proxies, other than the implicit "noproxy" added when + // GONOPROXY is set. This can happen if GOPROXY is a non-empty string + // like "," or " ". + proxyOnce.err = fmt.Errorf("GOPROXY list is not the empty string, but contains no entries") + } }) return proxyOnce.list, proxyOnce.err @@ -191,7 +199,7 @@ func TryProxies(f func(proxy string) error) error { return err } if len(proxies) == 0 { - return f("off") + panic("GOPROXY list is empty") } // We try to report the most helpful error to the user. "direct" and "noproxy" diff --git a/src/cmd/go/testdata/script/clean_cache_n.txt b/src/cmd/go/testdata/script/clean_cache_n.txt new file mode 100644 index 0000000000..4497b36bc3 --- /dev/null +++ b/src/cmd/go/testdata/script/clean_cache_n.txt @@ -0,0 +1,25 @@ +# We're testing cache behavior, so start with a clean GOCACHE. +env GOCACHE=$WORK/cache + +# Build something so that the cache gets populates +go build main.go + +# Check that cache contains directories before running +exists $GOCACHE/00 + +# Run go clean -cache -n and ensure that directories weren't deleted +go clean -cache -n +exists $GOCACHE/00 + +# Re-run go clean cache without the -n flag go ensure that directories were properly removed +go clean -cache +! exists $GOCACHE/00 + +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println("hello!") +} diff --git a/src/cmd/go/testdata/script/mod_gonoproxy.txt b/src/cmd/go/testdata/script/mod_gonoproxy.txt index d7848c7d26..a9e0ca4010 100644 --- a/src/cmd/go/testdata/script/mod_gonoproxy.txt +++ b/src/cmd/go/testdata/script/mod_gonoproxy.txt @@ -18,6 +18,11 @@ env GOPRIVATE='*/quote,*/*mple*,golang.org/x' env GONOPROXY=none # that is, proxy all despite GOPRIVATE go get rsc.io/quote +# When GOPROXY is not empty but contains no entries, an error should be reported. +env GOPROXY=',' +! go get golang.org/x/text +stderr '^go get golang.org/x/text: GOPROXY list is not the empty string, but contains no entries$' + # When GOPROXY=off, fetching modules not matched by GONOPROXY fails. env GONOPROXY=*/fortune env GOPROXY=off diff --git a/src/cmd/go/testdata/script/test_benchmark_chatty_fail.txt b/src/cmd/go/testdata/script/test_benchmark_chatty_fail.txt new file mode 100644 index 0000000000..6031eadd0a --- /dev/null +++ b/src/cmd/go/testdata/script/test_benchmark_chatty_fail.txt @@ -0,0 +1,32 @@ +# Run chatty tests. Assert on CONT lines. +! go test chatty_test.go -v -bench . chatty_bench + +# Sanity check that output occurs. +stdout -count=2 'this is sub-0' +stdout -count=2 'this is sub-1' +stdout -count=2 'this is sub-2' +stdout -count=1 'error from sub-0' +stdout -count=1 'error from sub-1' +stdout -count=1 'error from sub-2' + +# Benchmarks should not print CONT. +! stdout CONT + +-- chatty_test.go -- +package chatty_bench + +import ( + "testing" + "fmt" +) + +func BenchmarkChatty(b *testing.B) { + for i := 0; i < 3; i++ { + b.Run(fmt.Sprintf("sub-%d", i), func(b *testing.B) { + for j := 0; j < 2; j++ { + b.Logf("this is sub-%d", i) + } + b.Errorf("error from sub-%d", i) + }) + } +} \ No newline at end of file diff --git a/src/cmd/go/testdata/script/test_benchmark_chatty_success.txt b/src/cmd/go/testdata/script/test_benchmark_chatty_success.txt new file mode 100644 index 0000000000..a1c0d6545a --- /dev/null +++ b/src/cmd/go/testdata/script/test_benchmark_chatty_success.txt @@ -0,0 +1,29 @@ +# Run chatty tests. Assert on CONT lines. +go test chatty_test.go -v -bench . chatty_bench + +# Sanity check that output happens. We don't provide -count because the amount +# of output is variable. +stdout 'this is sub-0' +stdout 'this is sub-1' +stdout 'this is sub-2' + +# Benchmarks should not print CONT. +! stdout CONT + +-- chatty_test.go -- +package chatty_bench + +import ( + "testing" + "fmt" +) + +func BenchmarkChatty(b *testing.B) { + for i := 0; i < 3; i++ { + b.Run(fmt.Sprintf("sub-%d", i), func(b *testing.B) { + for j := 0; j < 2; j++ { + b.Logf("this is sub-%d", i) + } + }) + } +} \ No newline at end of file diff --git a/src/cmd/go/testdata/script/test_chatty_fail.txt b/src/cmd/go/testdata/script/test_chatty_fail.txt new file mode 100644 index 0000000000..a5ef559c77 --- /dev/null +++ b/src/cmd/go/testdata/script/test_chatty_fail.txt @@ -0,0 +1,32 @@ +# Run chatty tests. Assert on CONT lines. +! go test chatty_test.go -v + +# Sanity check that output occurs. +stdout -count=2 'this is sub-0' +stdout -count=2 'this is sub-1' +stdout -count=2 'this is sub-2' +stdout -count=1 'error from sub-0' +stdout -count=1 'error from sub-1' +stdout -count=1 'error from sub-2' + +# Non-parallel tests should not print CONT. +! stdout CONT + +-- chatty_test.go -- +package chatty_test + +import ( + "testing" + "fmt" +) + +func TestChatty(t *testing.T) { + for i := 0; i < 3; i++ { + t.Run(fmt.Sprintf("sub-%d", i), func(t *testing.T) { + for j := 0; j < 2; j++ { + t.Logf("this is sub-%d", i) + } + t.Errorf("error from sub-%d", i) + }) + } +} \ No newline at end of file diff --git a/src/cmd/go/testdata/script/test_chatty_parallel_fail.txt b/src/cmd/go/testdata/script/test_chatty_parallel_fail.txt new file mode 100644 index 0000000000..3f7360b659 --- /dev/null +++ b/src/cmd/go/testdata/script/test_chatty_parallel_fail.txt @@ -0,0 +1,58 @@ +# Run parallel chatty tests. Assert on CONT lines. This test makes sure that +# multiple parallel outputs have the appropriate CONT lines between them. +! go test -parallel 3 chatty_parallel_test.go -v + +stdout -count=1 '^=== CONT TestChattyParallel/sub-0\n chatty_parallel_test.go:38: error from sub-0$' +stdout -count=1 '^=== CONT TestChattyParallel/sub-1\n chatty_parallel_test.go:38: error from sub-1$' +stdout -count=1 '^=== CONT TestChattyParallel/sub-2\n chatty_parallel_test.go:38: error from sub-2$' + +# Run parallel chatty tests with -json. Assert on CONT lines as above - make +# sure there are CONT lines before each output line. +! go test -json -parallel 3 chatty_parallel_test.go -v +stdout -count=1 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-0","Output":"=== CONT TestChattyParallel/sub-0\\n"}\n{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-0","Output":" chatty_parallel_test.go:38: error from sub-0\\n"}' +stdout -count=1 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-1","Output":"=== CONT TestChattyParallel/sub-1\\n"}\n{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-1","Output":" chatty_parallel_test.go:38: error from sub-1\\n"}' +stdout -count=1 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-2","Output":"=== CONT TestChattyParallel/sub-2\\n"}\n{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-2","Output":" chatty_parallel_test.go:38: error from sub-2\\n"}' + +-- chatty_parallel_test.go -- +package chatty_paralell_test + +import ( + "testing" + "fmt" + "flag" +) + +// This test ensures the the order of CONT lines in parallel chatty tests. +func TestChattyParallel(t *testing.T) { + t.Parallel() + + // The number of concurrent tests running. This is closely tied to the + // -parallel test flag, so we grab it from the flag rather than setting it + // to some constant. + parallel := flag.Lookup("test.parallel").Value.(flag.Getter).Get().(int) + + // ready is a synchronization mechanism that causes subtests to execute + // round robin. + ready := make([]chan bool, parallel) + for i := range ready { + ready[i] = make(chan bool, 1) + } + ready[0] <- true + + for i := range ready { + i := i + t.Run(fmt.Sprintf("sub-%d", i), func(t *testing.T) { + t.Parallel() + + // Some basic log output to precede the failures. + <-ready[i] + t.Logf("this is sub-%d", i) + ready[(i+1)%len(ready)] <- true + + // The actual failure messages we care about. + <-ready[i] + t.Errorf("error from sub-%d", i) + ready[(i+1)%len(ready)] <- true + }) + } +} diff --git a/src/cmd/go/testdata/script/test_chatty_parallel_success.txt b/src/cmd/go/testdata/script/test_chatty_parallel_success.txt new file mode 100644 index 0000000000..4a86d74f19 --- /dev/null +++ b/src/cmd/go/testdata/script/test_chatty_parallel_success.txt @@ -0,0 +1,52 @@ +# Run parallel chatty tests. Assert on CONT lines. This test makes sure that +# multiple parallel outputs have the appropriate CONT lines between them. +go test -parallel 3 chatty_parallel_test.go -v +stdout -count=2 '^=== CONT TestChattyParallel/sub-0\n chatty_parallel_test.go:32: this is sub-0$' +stdout -count=2 '^=== CONT TestChattyParallel/sub-1\n chatty_parallel_test.go:32: this is sub-1$' +stdout -count=2 '^=== CONT TestChattyParallel/sub-2\n chatty_parallel_test.go:32: this is sub-2$' + +# Run parallel chatty tests with -json. Assert on CONT lines as above - make +# sure there are CONT lines before each output line. +go test -json -parallel 3 chatty_parallel_test.go -v +stdout -count=2 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-0","Output":"=== CONT TestChattyParallel/sub-0\\n"}\n{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-0","Output":" chatty_parallel_test.go:32: this is sub-0\\n"}' +stdout -count=2 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-1","Output":"=== CONT TestChattyParallel/sub-1\\n"}\n{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-1","Output":" chatty_parallel_test.go:32: this is sub-1\\n"}' +stdout -count=2 '{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-2","Output":"=== CONT TestChattyParallel/sub-2\\n"}\n{"Time":"[0-9TZ:.+-]{20,40}","Action":"output","Package":"command-line-arguments","Test":"TestChattyParallel/sub-2","Output":" chatty_parallel_test.go:32: this is sub-2\\n"}' + +-- chatty_parallel_test.go -- +package chatty_paralell_test + +import ( + "testing" + "fmt" + "flag" +) + +// This test ensures the the order of CONT lines in parallel chatty tests. +func TestChattyParallel(t *testing.T) { + t.Parallel() + + // The number of concurrent tests running. This is closely tied to the + // -parallel test flag, so we grab it from the flag rather than setting it + // to some constant. + parallel := flag.Lookup("test.parallel").Value.(flag.Getter).Get().(int) + + // ready is a synchronization mechanism that causes subtests to execute + // round robin. + ready := make([]chan bool, parallel) + for i := range ready { + ready[i] = make(chan bool, 1) + } + ready[0] <- true + + for i := range ready { + i := i + t.Run(fmt.Sprintf("sub-%d", i), func(t *testing.T) { + t.Parallel() + for j := 0; j < 2; j++ { + <-ready[i] + t.Logf("this is sub-%d", i) + ready[(i+1)%len(ready)] <- true + } + }) + } +} diff --git a/src/cmd/go/testdata/script/test_chatty_success.txt b/src/cmd/go/testdata/script/test_chatty_success.txt new file mode 100644 index 0000000000..8bfa569f80 --- /dev/null +++ b/src/cmd/go/testdata/script/test_chatty_success.txt @@ -0,0 +1,27 @@ +# Run chatty tests. Assert on CONT lines. +go test chatty_test.go -v + +# Non-parallel tests should not print CONT. +! stdout CONT + +# The assertion is condensed into one line so that it precisely matches output, +# rather than skipping lines and allow rogue CONT lines. +stdout '=== RUN TestChatty\n=== RUN TestChatty/sub-0\n chatty_test.go:12: this is sub-0\n chatty_test.go:12: this is sub-0\n=== RUN TestChatty/sub-1\n chatty_test.go:12: this is sub-1\n chatty_test.go:12: this is sub-1\n=== RUN TestChatty/sub-2\n chatty_test.go:12: this is sub-2\n chatty_test.go:12: this is sub-2\n--- PASS: TestChatty' + +-- chatty_test.go -- +package chatty_test + +import ( + "testing" + "fmt" +) + +func TestChatty(t *testing.T) { + for i := 0; i < 3; i++ { + t.Run(fmt.Sprintf("sub-%d", i), func(t *testing.T) { + for j := 0; j < 2; j++ { + t.Logf("this is sub-%d", i) + } + }) + } +} \ No newline at end of file diff --git a/src/cmd/go/testdata/script/test_flags.txt b/src/cmd/go/testdata/script/test_flags.txt index 226b8d1315..27d718a3b2 100644 --- a/src/cmd/go/testdata/script/test_flags.txt +++ b/src/cmd/go/testdata/script/test_flags.txt @@ -30,23 +30,23 @@ exists ./cover.out # with the 'test.' prefix in the GOFLAGS entry... env GOFLAGS='-test.timeout=24h0m0s -count=1' go test -v -x ./x -stdout 'TestLogTimeout: .*: 24h0m0s$' +stdout '.*: 24h0m0s$' stderr '-test.count=1' # ...or without. env GOFLAGS='-timeout=24h0m0s -count=1' go test -v -x ./x -stdout 'TestLogTimeout: .*: 24h0m0s$' +stdout '.*: 24h0m0s$' stderr '-test.count=1' # Arguments from the command line should override GOFLAGS... go test -v -x -timeout=25h0m0s ./x -stdout 'TestLogTimeout: .*: 25h0m0s$' +stdout '.*: 25h0m0s$' stderr '-test.count=1' # ...even if they use a different flag name. go test -v -x -test.timeout=26h0m0s ./x -stdout 'TestLogTimeout: .*: 26h0m0s$' +stdout '.*: 26h0m0s$' stderr '-test\.timeout=26h0m0s' ! stderr 'timeout=24h0m0s' stderr '-test.count=1' diff --git a/src/cmd/go/testdata/script/test_regexps.txt b/src/cmd/go/testdata/script/test_regexps.txt index 39dedbf06f..a616195cab 100644 --- a/src/cmd/go/testdata/script/test_regexps.txt +++ b/src/cmd/go/testdata/script/test_regexps.txt @@ -4,28 +4,28 @@ go test -cpu=1 -run=X/Y -bench=X/Y -count=2 -v testregexp # TestX is run, twice stdout -count=2 '^=== RUN TestX$' -stdout -count=2 '^ TestX: x_test.go:6: LOG: X running$' +stdout -count=2 '^ x_test.go:6: LOG: X running$' # TestX/Y is run, twice stdout -count=2 '^=== RUN TestX/Y$' -stdout -count=2 '^ TestX/Y: x_test.go:8: LOG: Y running$' +stdout -count=2 '^ x_test.go:8: LOG: Y running$' # TestXX is run, twice stdout -count=2 '^=== RUN TestXX$' -stdout -count=2 '^ TestXX: z_test.go:10: LOG: XX running' +stdout -count=2 '^ z_test.go:10: LOG: XX running' # TestZ is not run ! stdout '^=== RUN TestZ$' # BenchmarkX is run with N=1 once, only to discover what sub-benchmarks it has, # and should not print a final summary line. -stdout -count=1 '^\s+BenchmarkX: x_test.go:13: LOG: X running N=1$' +stdout -count=1 '^ x_test.go:13: LOG: X running N=1$' ! stdout '^\s+BenchmarkX: x_test.go:13: LOG: X running N=\d\d+' ! stdout 'BenchmarkX\s+\d+' # Same for BenchmarkXX. -stdout -count=1 '^\s+BenchmarkXX: z_test.go:18: LOG: XX running N=1$' -! stdout '^\s+BenchmarkXX: z_test.go:18: LOG: XX running N=\d\d+' +stdout -count=1 '^ z_test.go:18: LOG: XX running N=1$' +! stdout '^ z_test.go:18: LOG: XX running N=\d\d+' ! stdout 'BenchmarkXX\s+\d+' # BenchmarkX/Y is run in full twice due to -count=2. @@ -33,7 +33,7 @@ stdout -count=1 '^\s+BenchmarkXX: z_test.go:18: LOG: XX running N=1$' # but may cap out at N=1e9. # We don't actually care what the final iteration count is, but it should be # a large number, and the last iteration count prints right before the results. -stdout -count=2 '^\s+BenchmarkX/Y: x_test.go:15: LOG: Y running N=[1-9]\d{4,}\nBenchmarkX/Y\s+\d+' +stdout -count=2 '^ x_test.go:15: LOG: Y running N=[1-9]\d{4,}\nBenchmarkX/Y\s+\d+' -- testregexp/x_test.go -- package x diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index b0c8f91e2a..429ea98347 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1423,6 +1423,7 @@ func (ctxt *Link) hostlink() { } } + var altLinker string if ctxt.IsELF && ctxt.DynlinkingGo() { // We force all symbol resolution to be done at program startup // because lazy PLT resolution can use large amounts of stack at @@ -1434,6 +1435,11 @@ func (ctxt *Link) hostlink() { // from the beginning of the section (like sym.STYPE). argv = append(argv, "-Wl,-znocopyreloc") + if objabi.GOOS == "android" { + // Use lld to avoid errors from default linker (issue #38838) + altLinker = "lld" + } + if ctxt.Arch.InFamily(sys.ARM, sys.ARM64) && objabi.GOOS == "linux" { // On ARM, the GNU linker will generate COPY relocations // even with -znocopyreloc set. @@ -1443,7 +1449,7 @@ func (ctxt *Link) hostlink() { // generating COPY relocations. // // In both cases, switch to gold. - argv = append(argv, "-fuse-ld=gold") + altLinker = "gold" // If gold is not installed, gcc will silently switch // back to ld.bfd. So we parse the version information @@ -1456,10 +1462,9 @@ func (ctxt *Link) hostlink() { } } } - if ctxt.Arch.Family == sys.ARM64 && objabi.GOOS == "freebsd" { // Switch to ld.bfd on freebsd/arm64. - argv = append(argv, "-fuse-ld=bfd") + altLinker = "bfd" // Provide a useful error if ld.bfd is missing. cmd := exec.Command(*flagExtld, "-fuse-ld=bfd", "-Wl,--version") @@ -1469,6 +1474,9 @@ func (ctxt *Link) hostlink() { } } } + if altLinker != "" { + argv = append(argv, "-fuse-ld="+altLinker) + } if ctxt.IsELF && len(buildinfo) > 0 { argv = append(argv, fmt.Sprintf("-Wl,--build-id=0x%x", buildinfo)) @@ -1505,7 +1513,7 @@ func (ctxt *Link) hostlink() { } const compressDWARF = "-Wl,--compress-debug-sections=zlib-gnu" - if ctxt.compressDWARF && linkerFlagSupported(argv[0], compressDWARF) { + if ctxt.compressDWARF && linkerFlagSupported(argv[0], altLinker, compressDWARF) { argv = append(argv, compressDWARF) } @@ -1595,7 +1603,7 @@ func (ctxt *Link) hostlink() { if ctxt.BuildMode == BuildModeExe && !ctxt.linkShared { // GCC uses -no-pie, clang uses -nopie. for _, nopie := range []string{"-no-pie", "-nopie"} { - if linkerFlagSupported(argv[0], nopie) { + if linkerFlagSupported(argv[0], altLinker, nopie) { argv = append(argv, nopie) break } @@ -1696,7 +1704,7 @@ func (ctxt *Link) hostlink() { var createTrivialCOnce sync.Once -func linkerFlagSupported(linker, flag string) bool { +func linkerFlagSupported(linker, altLinker, flag string) bool { createTrivialCOnce.Do(func() { src := filepath.Join(*flagTmpdir, "trivial.c") if err := ioutil.WriteFile(src, []byte("int main() { return 0; }"), 0666); err != nil { @@ -1756,6 +1764,9 @@ func linkerFlagSupported(linker, flag string) bool { } } + if altLinker != "" { + flags = append(flags, "-fuse-ld="+altLinker) + } flags = append(flags, flag, "trivial.c") cmd := exec.Command(linker, flags...) diff --git a/src/cmd/link/internal/ld/outbuf.go b/src/cmd/link/internal/ld/outbuf.go index 4ce211172c..09162ae90f 100644 --- a/src/cmd/link/internal/ld/outbuf.go +++ b/src/cmd/link/internal/ld/outbuf.go @@ -115,7 +115,6 @@ func (out *OutBuf) Close() error { if out.isMmapped() { out.copyHeap() out.munmap() - return nil } if out.f == nil { return nil diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index ecf44071cf..288c9c666f 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -2100,7 +2100,7 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub, priv return nil, errors.New("x509: no SerialNumber given") } - if template.BasicConstraintsValid && !template.IsCA && (template.MaxPathLen != 0 || template.MaxPathLenZero) { + if template.BasicConstraintsValid && !template.IsCA && template.MaxPathLen != -1 && (template.MaxPathLen != 0 || template.MaxPathLenZero) { return nil, errors.New("x509: only CAs are allowed to specify MaxPathLen") } diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index 7e001471dd..0141021504 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -1674,11 +1674,15 @@ func TestMaxPathLenNotCA(t *testing.T) { BasicConstraintsValid: true, IsCA: false, } - cert := serialiseAndParse(t, template) - if m := cert.MaxPathLen; m != -1 { + if m := serialiseAndParse(t, template).MaxPathLen; m != -1 { t.Errorf("MaxPathLen should be -1 when IsCa is false, got %d", m) } + template.MaxPathLen = -1 + if m := serialiseAndParse(t, template).MaxPathLen; m != -1 { + t.Errorf("MaxPathLen should be -1 when IsCa is false and MaxPathLen set to -1, got %d", m) + } + template.MaxPathLen = 5 if _, err := CreateCertificate(rand.Reader, template, template, &testPrivateKey.PublicKey, testPrivateKey); err == nil { t.Error("specifying a MaxPathLen when IsCA is false should fail") @@ -1691,8 +1695,7 @@ func TestMaxPathLenNotCA(t *testing.T) { } template.BasicConstraintsValid = false - cert2 := serialiseAndParse(t, template) - if m := cert2.MaxPathLen; m != 0 { + if m := serialiseAndParse(t, template).MaxPathLen; m != 0 { t.Errorf("Bad MaxPathLen should be ignored if BasicConstraintsValid is false, got %d", m) } } diff --git a/src/encoding/asn1/asn1.go b/src/encoding/asn1/asn1.go index 90ba5775af..d809dde278 100644 --- a/src/encoding/asn1/asn1.go +++ b/src/encoding/asn1/asn1.go @@ -1037,6 +1037,12 @@ func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) { // Because Unmarshal uses the reflect package, the structs // being written to must use upper case field names. // +// After parsing b, any bytes that were leftover and not used to fill +// val will be returned in rest. When parsing a SEQUENCE into a struct, +// any trailing elements of the SEQUENCE that do not have matching +// fields in val will not be included in rest, as these are considered +// valid elements of the SEQUENCE and not trailing data. +// // An ASN.1 INTEGER can be written to an int, int32, int64, // or *big.Int (from the math/big package). // If the encoded value does not fit in the Go type, diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 5acc6d8b26..5f34af44ea 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -677,6 +677,7 @@ func (d *decodeState) object(v reflect.Value) error { return nil } + var mapElem reflect.Value origErrorContext := d.errorContext for { @@ -700,66 +701,17 @@ func (d *decodeState) object(v reflect.Value) error { } // Figure out field corresponding to key. - var kv, subv reflect.Value + var subv reflect.Value destring := false // whether the value is wrapped in a string to be decoded first if v.Kind() == reflect.Map { - // First, figure out the key value from the input. - kt := t.Key() - switch { - case reflect.PtrTo(kt).Implements(textUnmarshalerType): - kv = reflect.New(kt) - if err := d.literalStore(item, kv, true); err != nil { - return err - } - kv = kv.Elem() - case kt.Kind() == reflect.String: - kv = reflect.ValueOf(key).Convert(kt) - default: - switch kt.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - s := string(key) - n, err := strconv.ParseInt(s, 10, 64) - if err != nil || reflect.Zero(kt).OverflowInt(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) - break - } - kv = reflect.ValueOf(n).Convert(kt) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - s := string(key) - n, err := strconv.ParseUint(s, 10, 64) - if err != nil || reflect.Zero(kt).OverflowUint(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) - break - } - kv = reflect.ValueOf(n).Convert(kt) - default: - panic("json: Unexpected key type") // should never occur - } - } - - // Then, decide what element value we'll decode into. - et := t.Elem() - if kv.IsValid() { - if existing := v.MapIndex(kv); !existing.IsValid() { - // Nothing to reuse. - } else if et.Kind() == reflect.Ptr { - // Pointer; decode directly into it if non-nil. - if !existing.IsNil() { - subv = existing - } - } else { - // Non-pointer. Make a copy and decode into the - // addressable copy. Don't just use a new/zero - // value, as that would lose existing data. - subv = reflect.New(et).Elem() - subv.Set(existing) - } - } - if !subv.IsValid() { - // We couldn't reuse an existing value. - subv = reflect.New(et).Elem() + elemType := t.Elem() + if !mapElem.IsValid() { + mapElem = reflect.New(elemType).Elem() + } else { + mapElem.Set(reflect.Zero(elemType)) } + subv = mapElem } else { var f *field if i, ok := fields.nameIndex[string(key)]; ok { @@ -838,8 +790,43 @@ func (d *decodeState) object(v reflect.Value) error { // Write value back to map; // if using struct, subv points into struct already. - if v.Kind() == reflect.Map && kv.IsValid() { - v.SetMapIndex(kv, subv) + if v.Kind() == reflect.Map { + kt := t.Key() + var kv reflect.Value + switch { + case reflect.PtrTo(kt).Implements(textUnmarshalerType): + kv = reflect.New(kt) + if err := d.literalStore(item, kv, true); err != nil { + return err + } + kv = kv.Elem() + case kt.Kind() == reflect.String: + kv = reflect.ValueOf(key).Convert(kt) + default: + switch kt.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + s := string(key) + n, err := strconv.ParseInt(s, 10, 64) + if err != nil || reflect.Zero(kt).OverflowInt(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) + break + } + kv = reflect.ValueOf(n).Convert(kt) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + s := string(key) + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || reflect.Zero(kt).OverflowUint(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) + break + } + kv = reflect.ValueOf(n).Convert(kt) + default: + panic("json: Unexpected key type") // should never occur + } + } + if kv.IsValid() { + v.SetMapIndex(kv, subv) + } } // Next token must be , or }. diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index a62488d447..5ac1022207 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -2569,57 +2569,3 @@ func TestUnmarshalMaxDepth(t *testing.T) { } } } - -func TestUnmarshalMapPointerElem(t *testing.T) { - type S struct{ Unchanged, Changed int } - input := []byte(`{"S":{"Changed":5}}`) - want := S{1, 5} - - // First, a map with struct pointer elements. The key-value pair exists, - // so reuse the existing value. - s := &S{1, 2} - ptrMap := map[string]*S{"S": s} - if err := Unmarshal(input, &ptrMap); err != nil { - t.Fatal(err) - } - if s != ptrMap["S"] { - t.Fatal("struct pointer element in map was completely replaced") - } - if got := *s; got != want { - t.Fatalf("want %#v, got %#v", want, got) - } - - // Second, a map with struct elements. The key-value pair exists, but - // the value isn't addresable, so make a copy and use that. - s = &S{1, 2} - strMap := map[string]S{"S": *s} - if err := Unmarshal(input, &strMap); err != nil { - t.Fatal(err) - } - if *s == strMap["S"] { - t.Fatal("struct element in map wasn't copied") - } - if got := strMap["S"]; got != want { - t.Fatalf("want %#v, got %#v", want, got) - } - - // Finally, check the cases where the key-value pair exists, but the - // value is zero. - want = S{0, 5} - - ptrMap = map[string]*S{"S": nil} - if err := Unmarshal(input, &ptrMap); err != nil { - t.Fatal(err) - } - if got := *ptrMap["S"]; got != want { - t.Fatalf("want %#v, got %#v", want, got) - } - - strMap = map[string]S{"S": {}} - if err := Unmarshal(input, &strMap); err != nil { - t.Fatal(err) - } - if got := strMap["S"]; got != want { - t.Fatalf("want %#v, got %#v", want, got) - } -} diff --git a/src/encoding/xml/marshal.go b/src/encoding/xml/marshal.go index 2440a51e20..d8a04a95a2 100644 --- a/src/encoding/xml/marshal.go +++ b/src/encoding/xml/marshal.go @@ -482,8 +482,11 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat xmlname := tinfo.xmlname if xmlname.name != "" { start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name - } else if v, ok := xmlname.value(val).Interface().(Name); ok && v.Local != "" { - start.Name = v + } else { + fv := xmlname.value(val, dontInitNilPointers) + if v, ok := fv.Interface().(Name); ok && v.Local != "" { + start.Name = v + } } } if start.Name.Local == "" && finfo != nil { @@ -503,7 +506,7 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat if finfo.flags&fAttr == 0 { continue } - fv := finfo.value(val) + fv := finfo.value(val, dontInitNilPointers) if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { continue @@ -806,7 +809,12 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { if finfo.flags&fAttr != 0 { continue } - vf := finfo.value(val) + vf := finfo.value(val, dontInitNilPointers) + if !vf.IsValid() { + // The field is behind an anonymous struct field that's + // nil. Skip it. + continue + } switch finfo.flags & fMode { case fCDATA, fCharData: diff --git a/src/encoding/xml/marshal_test.go b/src/encoding/xml/marshal_test.go index 6085ddbba2..d2e5137afd 100644 --- a/src/encoding/xml/marshal_test.go +++ b/src/encoding/xml/marshal_test.go @@ -309,6 +309,11 @@ type ChardataEmptyTest struct { Contents *string `xml:",chardata"` } +type PointerAnonFields struct { + *MyInt + *NamedType +} + type MyMarshalerTest struct { } @@ -889,6 +894,18 @@ var marshalTests = []struct { ``, }, + // Anonymous struct pointer field which is nil + { + Value: &EmbedB{}, + ExpectXML: ``, + }, + + // Other kinds of nil anonymous fields + { + Value: &PointerAnonFields{}, + ExpectXML: ``, + }, + // Test that name casing matters { Value: &NameCasing{Xy: "mixed", XY: "upper", XyA: "mixedA", XYA: "upperA"}, diff --git a/src/encoding/xml/read.go b/src/encoding/xml/read.go index 10a60eed1a..ef5df3f7f6 100644 --- a/src/encoding/xml/read.go +++ b/src/encoding/xml/read.go @@ -435,7 +435,7 @@ func (d *Decoder) unmarshal(val reflect.Value, start *StartElement) error { } return UnmarshalError(e) } - fv := finfo.value(sv) + fv := finfo.value(sv, initNilPointers) if _, ok := fv.Interface().(Name); ok { fv.Set(reflect.ValueOf(start.Name)) } @@ -449,7 +449,7 @@ func (d *Decoder) unmarshal(val reflect.Value, start *StartElement) error { finfo := &tinfo.fields[i] switch finfo.flags & fMode { case fAttr: - strv := finfo.value(sv) + strv := finfo.value(sv, initNilPointers) if a.Name.Local == finfo.name && (finfo.xmlns == "" || finfo.xmlns == a.Name.Space) { if err := d.unmarshalAttr(strv, a); err != nil { return err @@ -465,7 +465,7 @@ func (d *Decoder) unmarshal(val reflect.Value, start *StartElement) error { } if !handled && any >= 0 { finfo := &tinfo.fields[any] - strv := finfo.value(sv) + strv := finfo.value(sv, initNilPointers) if err := d.unmarshalAttr(strv, a); err != nil { return err } @@ -478,22 +478,22 @@ func (d *Decoder) unmarshal(val reflect.Value, start *StartElement) error { switch finfo.flags & fMode { case fCDATA, fCharData: if !saveData.IsValid() { - saveData = finfo.value(sv) + saveData = finfo.value(sv, initNilPointers) } case fComment: if !saveComment.IsValid() { - saveComment = finfo.value(sv) + saveComment = finfo.value(sv, initNilPointers) } case fAny, fAny | fElement: if !saveAny.IsValid() { - saveAny = finfo.value(sv) + saveAny = finfo.value(sv, initNilPointers) } case fInnerXML: if !saveXML.IsValid() { - saveXML = finfo.value(sv) + saveXML = finfo.value(sv, initNilPointers) if d.saved == nil { saveXMLIndex = 0 d.saved = new(bytes.Buffer) @@ -687,7 +687,7 @@ Loop: } if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local { // It's a perfect match, unmarshal the field. - return true, d.unmarshal(finfo.value(sv), start) + return true, d.unmarshal(finfo.value(sv, initNilPointers), start) } if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local { // It's a prefix for the field. Break and recurse diff --git a/src/encoding/xml/typeinfo.go b/src/encoding/xml/typeinfo.go index 639952c74a..f30fe58590 100644 --- a/src/encoding/xml/typeinfo.go +++ b/src/encoding/xml/typeinfo.go @@ -344,15 +344,25 @@ func (e *TagPathError) Error() string { return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2) } +const ( + initNilPointers = true + dontInitNilPointers = false +) + // value returns v's field value corresponding to finfo. -// It's equivalent to v.FieldByIndex(finfo.idx), but initializes -// and dereferences pointers as necessary. -func (finfo *fieldInfo) value(v reflect.Value) reflect.Value { +// It's equivalent to v.FieldByIndex(finfo.idx), but when passed +// initNilPointers, it initializes and dereferences pointers as necessary. +// When passed dontInitNilPointers and a nil pointer is reached, the function +// returns a zero reflect.Value. +func (finfo *fieldInfo) value(v reflect.Value, shouldInitNilPointers bool) reflect.Value { for i, x := range finfo.idx { if i > 0 { t := v.Type() if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { if v.IsNil() { + if !shouldInitNilPointers { + return reflect.Value{} + } v.Set(reflect.New(v.Type().Elem())) } v = v.Elem() diff --git a/src/go/build/build_test.go b/src/go/build/build_test.go index 7151ba1180..a7f2a3e902 100644 --- a/src/go/build/build_test.go +++ b/src/go/build/build_test.go @@ -5,6 +5,7 @@ package build import ( + "flag" "internal/testenv" "io" "io/ioutil" @@ -16,6 +17,14 @@ import ( "testing" ) +func TestMain(m *testing.M) { + flag.Parse() + if goTool, err := testenv.GoTool(); err == nil { + os.Setenv("PATH", filepath.Dir(goTool)+string(os.PathListSeparator)+os.Getenv("PATH")) + } + os.Exit(m.Run()) +} + func TestMatch(t *testing.T) { ctxt := Default what := "default" diff --git a/src/go/internal/srcimporter/srcimporter_test.go b/src/go/internal/srcimporter/srcimporter_test.go index c456b8e26a..102ac43f94 100644 --- a/src/go/internal/srcimporter/srcimporter_test.go +++ b/src/go/internal/srcimporter/srcimporter_test.go @@ -5,11 +5,13 @@ package srcimporter import ( + "flag" "go/build" "go/token" "go/types" "internal/testenv" "io/ioutil" + "os" "path" "path/filepath" "runtime" @@ -18,6 +20,14 @@ import ( "time" ) +func TestMain(m *testing.M) { + flag.Parse() + if goTool, err := testenv.GoTool(); err == nil { + os.Setenv("PATH", filepath.Dir(goTool)+string(os.PathListSeparator)+os.Getenv("PATH")) + } + os.Exit(m.Run()) +} + const maxTime = 2 * time.Second var importer = New(&build.Default, token.NewFileSet(), make(map[string]*types.Package)) diff --git a/src/math/exp_amd64.s b/src/math/exp_amd64.s index 525745d66c..b3e1c22d04 100644 --- a/src/math/exp_amd64.s +++ b/src/math/exp_amd64.s @@ -8,7 +8,7 @@ // methods of elementary functions suitable for SIMD computation", Proc. // of International Supercomputing Conference 2010 (ISC'10), pp. 25 -- 32 // (May 2010). The paper is available at -// https://www.springerlink.com/content/340228x165742104/ +// https://link.springer.com/article/10.1007/s00450-010-0108-2 // // The original code and the constants below are from the author's // implementation available at http://freshmeat.net/projects/sleef. diff --git a/src/net/http/request.go b/src/net/http/request.go index e386f13a37..e924e2a07f 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -425,6 +425,8 @@ func (r *Request) Cookie(name string) (*Cookie, error) { // AddCookie does not attach more than one Cookie header field. That // means all cookies, if any, are written into the same line, // separated by semicolon. +// AddCookie only sanitizes c's name and value, and does not sanitize +// a Cookie header already present in the request. func (r *Request) AddCookie(c *Cookie) { s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) if c := r.Header.Get("Cookie"); c != "" { diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go index 6d5ea05c32..9019afb61d 100644 --- a/src/net/http/transfer.go +++ b/src/net/http/transfer.go @@ -335,7 +335,7 @@ func (t *transferWriter) writeBody(w io.Writer) error { var ncopy int64 // Write body. We "unwrap" the body first if it was wrapped in a - // nopCloser. This is to ensure that we can take advantage of + // nopCloser or readTrackingBody. This is to ensure that we can take advantage of // OS-level optimizations in the event that the body is an // *os.File. if t.Body != nil { @@ -413,7 +413,10 @@ func (t *transferWriter) unwrapBody() io.Reader { if reflect.TypeOf(t.Body) == nopCloserType { return reflect.ValueOf(t.Body).Field(0).Interface().(io.Reader) } - + if r, ok := t.Body.(*readTrackingBody); ok { + r.didRead = true + return r.ReadCloser + } return t.Body } @@ -1075,6 +1078,9 @@ func isKnownInMemoryReader(r io.Reader) bool { if reflect.TypeOf(r) == nopCloserType { return isKnownInMemoryReader(reflect.ValueOf(r).Field(0).Interface().(io.Reader)) } + if r, ok := r.(*readTrackingBody); ok { + return isKnownInMemoryReader(r.ReadCloser) + } return false } diff --git a/src/net/http/transport.go b/src/net/http/transport.go index b1705d5439..da86b26106 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -511,10 +511,17 @@ func (t *Transport) roundTrip(req *Request) (*Response, error) { } } + req = setupRewindBody(req) + if altRT := t.alternateRoundTripper(req); altRT != nil { if resp, err := altRT.RoundTrip(req); err != ErrSkipAltProtocol { return resp, err } + var err error + req, err = rewindBody(req) + if err != nil { + return nil, err + } } if !isHTTP { req.closeBody() @@ -584,18 +591,59 @@ func (t *Transport) roundTrip(req *Request) (*Response, error) { testHookRoundTripRetried() // Rewind the body if we're able to. - if req.GetBody != nil { - newReq := *req - var err error - newReq.Body, err = req.GetBody() - if err != nil { - return nil, err - } - req = &newReq + req, err = rewindBody(req) + if err != nil { + return nil, err } } } +var errCannotRewind = errors.New("net/http: cannot rewind body after connection loss") + +type readTrackingBody struct { + io.ReadCloser + didRead bool +} + +func (r *readTrackingBody) Read(data []byte) (int, error) { + r.didRead = true + return r.ReadCloser.Read(data) +} + +// setupRewindBody returns a new request with a custom body wrapper +// that can report whether the body needs rewinding. +// This lets rewindBody avoid an error result when the request +// does not have GetBody but the body hasn't been read at all yet. +func setupRewindBody(req *Request) *Request { + if req.Body == nil || req.Body == NoBody { + return req + } + newReq := *req + newReq.Body = &readTrackingBody{ReadCloser: req.Body} + return &newReq +} + +// rewindBody returns a new request with the body rewound. +// It returns req unmodified if the body does not need rewinding. +// rewindBody takes care of closing req.Body when appropriate +// (in all cases except when rewindBody returns req unmodified). +func rewindBody(req *Request) (rewound *Request, err error) { + if req.Body == nil || req.Body == NoBody || !req.Body.(*readTrackingBody).didRead { + return req, nil // nothing to rewind + } + req.closeBody() + if req.GetBody == nil { + return nil, errCannotRewind + } + body, err := req.GetBody() + if err != nil { + return nil, err + } + newReq := *req + newReq.Body = &readTrackingBody{ReadCloser: body} + return &newReq, nil +} + // shouldRetryRequest reports whether we should retry sending a failed // HTTP request on a new connection. The non-nil input error is the // error from roundTrip. diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index f4014d95bb..5ccb3d14ab 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -6196,3 +6196,29 @@ func (timeoutProto) RoundTrip(req *Request) (*Response, error) { return nil, errors.New("request was not canceled") } } + +type roundTripFunc func(r *Request) (*Response, error) + +func (f roundTripFunc) RoundTrip(r *Request) (*Response, error) { return f(r) } + +// Issue 32441: body is not reset after ErrSkipAltProtocol +func TestIssue32441(t *testing.T) { + defer afterTest(t) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + if n, _ := io.Copy(ioutil.Discard, r.Body); n == 0 { + t.Error("body length is zero") + } + })) + defer ts.Close() + c := ts.Client() + c.Transport.(*Transport).RegisterProtocol("http", roundTripFunc(func(r *Request) (*Response, error) { + // Draining body to trigger failure condition on actual request to server. + if n, _ := io.Copy(ioutil.Discard, r.Body); n == 0 { + t.Error("body length is zero during round trip") + } + return nil, ErrSkipAltProtocol + })) + if _, err := c.Post(ts.URL, "application/octet-stream", bytes.NewBufferString("data")); err != nil { + t.Error(err) + } +} diff --git a/src/os/file.go b/src/os/file.go index 93ba4d78ad..a2b71cb61a 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -384,7 +384,7 @@ func TempDir() string { // within this one and use that. // // On Unix systems, it returns $XDG_CACHE_HOME as specified by -// https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html if +// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if // non-empty, else $HOME/.cache. // On Darwin, it returns $HOME/Library/Caches. // On Windows, it returns %LocalAppData%. diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 0d8c0fd20d..cc695fd94c 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -330,8 +330,18 @@ func Symlink(oldname, newname string) error { // need the exact location of the oldname when it's relative to determine if it's a directory destpath := oldname - if !isAbs(oldname) { - destpath = dirname(newname) + `\` + oldname + if v := volumeName(oldname); v == "" { + if len(oldname) > 0 && IsPathSeparator(oldname[0]) { + // oldname is relative to the volume containing newname. + if v = volumeName(newname); v != "" { + // Prepend the volume explicitly, because it may be different from the + // volume of the current working directory. + destpath = v + oldname + } + } else { + // oldname is relative to newname. + destpath = dirname(newname) + `\` + oldname + } } fi, err := Stat(destpath) diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index 8c14103143..f03ec750d0 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -965,9 +965,10 @@ func TestWindowsDevNullFile(t *testing.T) { // works on Windows when developer mode is active. // This is supported starting Windows 10 (1703, v10.0.14972). func TestSymlinkCreation(t *testing.T) { - if !isWindowsDeveloperModeActive() { + if !testenv.HasSymlink() && !isWindowsDeveloperModeActive() { t.Skip("Windows developer mode is not active") } + t.Parallel() temp, err := ioutil.TempDir("", "TestSymlinkCreation") if err != nil { @@ -1005,6 +1006,122 @@ func isWindowsDeveloperModeActive() bool { return val != 0 } +// TestRootRelativeDirSymlink verifies that symlinks to paths relative to the +// drive root (beginning with "\" but no volume name) are created with the +// correct symlink type. +// (See https://golang.org/issue/39183#issuecomment-632175728.) +func TestRootRelativeDirSymlink(t *testing.T) { + testenv.MustHaveSymlink(t) + t.Parallel() + + temp := t.TempDir() + dir := filepath.Join(temp, "dir") + if err := os.Mkdir(dir, 0755); err != nil { + t.Fatal(err) + } + + volumeRelDir := strings.TrimPrefix(dir, filepath.VolumeName(dir)) // leaves leading backslash + + link := filepath.Join(temp, "link") + err := os.Symlink(volumeRelDir, link) + if err != nil { + t.Fatal(err) + } + t.Logf("Symlink(%#q, %#q)", volumeRelDir, link) + + f, err := os.Open(link) + if err != nil { + t.Fatal(err) + } + defer f.Close() + if fi, err := f.Stat(); err != nil { + t.Fatal(err) + } else if !fi.IsDir() { + t.Errorf("Open(%#q).Stat().IsDir() = false; want true", f.Name()) + } +} + +// TestWorkingDirectoryRelativeSymlink verifies that symlinks to paths relative +// to the current working directory for the drive, such as "C:File.txt", are +// correctly converted to absolute links of the correct symlink type (per +// https://docs.microsoft.com/en-us/windows/win32/fileio/creating-symbolic-links). +func TestWorkingDirectoryRelativeSymlink(t *testing.T) { + testenv.MustHaveSymlink(t) + + // Construct a directory to be symlinked. + temp := t.TempDir() + if v := filepath.VolumeName(temp); len(v) < 2 || v[1] != ':' { + t.Skipf("Can't test relative symlinks: t.TempDir() (%#q) does not begin with a drive letter.", temp) + } + + absDir := filepath.Join(temp, `dir\sub`) + if err := os.MkdirAll(absDir, 0755); err != nil { + t.Fatal(err) + } + + // Change to the temporary directory and construct a + // working-directory-relative symlink. + oldwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer func() { + if err := os.Chdir(oldwd); err != nil { + t.Fatal(err) + } + }() + if err := os.Chdir(temp); err != nil { + t.Fatal(err) + } + t.Logf("Chdir(%#q)", temp) + + wdRelDir := filepath.VolumeName(temp) + `dir\sub` // no backslash after volume. + absLink := filepath.Join(temp, "link") + err = os.Symlink(wdRelDir, absLink) + if err != nil { + t.Fatal(err) + } + t.Logf("Symlink(%#q, %#q)", wdRelDir, absLink) + + // Now change back to the original working directory and verify that the + // symlink still refers to its original path and is correctly marked as a + // directory. + if err := os.Chdir(oldwd); err != nil { + t.Fatal(err) + } + t.Logf("Chdir(%#q)", oldwd) + + resolved, err := os.Readlink(absLink) + if err != nil { + t.Errorf("Readlink(%#q): %v", absLink, err) + } else if resolved != absDir { + t.Errorf("Readlink(%#q) = %#q; want %#q", absLink, resolved, absDir) + } + + linkFile, err := os.Open(absLink) + if err != nil { + t.Fatal(err) + } + defer linkFile.Close() + + linkInfo, err := linkFile.Stat() + if err != nil { + t.Fatal(err) + } + if !linkInfo.IsDir() { + t.Errorf("Open(%#q).Stat().IsDir() = false; want true", absLink) + } + + absInfo, err := os.Stat(absDir) + if err != nil { + t.Fatal(err) + } + + if !os.SameFile(absInfo, linkInfo) { + t.Errorf("SameFile(Stat(%#q), Open(%#q).Stat()) = false; want true", absDir, absLink) + } +} + // TestStatOfInvalidName is regression test for issue #24999. func TestStatOfInvalidName(t *testing.T) { _, err := os.Stat("*.go") diff --git a/src/run.bat b/src/run.bat index 896c4ac3ec..69c181854b 100644 --- a/src/run.bat +++ b/src/run.bat @@ -30,7 +30,7 @@ rem TODO avoid rebuild if possible if x%1==x--no-rebuild goto norebuild echo ##### Building packages and commands. -go install -a -v std cmd +..\bin\go install -a -v std cmd if errorlevel 1 goto fail echo. :norebuild diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go index 5333b60646..34f30c9a37 100644 --- a/src/runtime/crash_test.go +++ b/src/runtime/crash_test.go @@ -55,6 +55,16 @@ func runTestProg(t *testing.T, binary, name string, env ...string) string { t.Fatal(err) } + return runBuiltTestProg(t, exe, name, env...) +} + +func runBuiltTestProg(t *testing.T, exe, name string, env ...string) string { + if *flagQuick { + t.Skip("-quick") + } + + testenv.MustHaveGoBuild(t) + cmd := testenv.CleanCmdEnv(exec.Command(exe, name)) cmd.Env = append(cmd.Env, env...) if testing.Short() { @@ -64,7 +74,7 @@ func runTestProg(t *testing.T, binary, name string, env ...string) string { cmd.Stdout = &b cmd.Stderr = &b if err := cmd.Start(); err != nil { - t.Fatalf("starting %s %s: %v", binary, name, err) + t.Fatalf("starting %s %s: %v", exe, name, err) } // If the process doesn't complete within 1 minute, @@ -92,7 +102,7 @@ func runTestProg(t *testing.T, binary, name string, env ...string) string { }() if err := cmd.Wait(); err != nil { - t.Logf("%s %s exit status: %v", binary, name, err) + t.Logf("%s %s exit status: %v", exe, name, err) } close(done) diff --git a/src/runtime/gc_test.go b/src/runtime/gc_test.go index 4c281ce52b..c5c8a4cecf 100644 --- a/src/runtime/gc_test.go +++ b/src/runtime/gc_test.go @@ -12,6 +12,7 @@ import ( "runtime" "runtime/debug" "sort" + "strings" "sync" "sync/atomic" "testing" @@ -192,6 +193,15 @@ func TestPeriodicGC(t *testing.T) { } } +func TestGcZombieReporting(t *testing.T) { + // This test is somewhat sensitive to how the allocator works. + got := runTestProg(t, "testprog", "GCZombie") + want := "found pointer to free object" + if !strings.Contains(got, want) { + t.Fatalf("expected %q in output, but got %q", want, got) + } +} + func BenchmarkSetTypePtr(b *testing.B) { benchSetType(b, new(*byte)) } diff --git a/src/runtime/mgcsweep.go b/src/runtime/mgcsweep.go index f9b03d3594..3aa3afc028 100644 --- a/src/runtime/mgcsweep.go +++ b/src/runtime/mgcsweep.go @@ -439,10 +439,31 @@ func (s *mspan) sweep(preserve bool) bool { } } + // Check for zombie objects. + if s.freeindex < s.nelems { + // Everything < freeindex is allocated and hence + // cannot be zombies. + // + // Check the first bitmap byte, where we have to be + // careful with freeindex. + obj := s.freeindex + if (*s.gcmarkBits.bytep(obj / 8)&^*s.allocBits.bytep(obj / 8))>>(obj%8) != 0 { + s.reportZombies() + } + // Check remaining bytes. + for i := obj/8 + 1; i < divRoundUp(s.nelems, 8); i++ { + if *s.gcmarkBits.bytep(i)&^*s.allocBits.bytep(i) != 0 { + s.reportZombies() + } + } + } + // Count the number of free objects in this span. nalloc := uint16(s.countAlloc()) nfreed := s.allocCount - nalloc if nalloc > s.allocCount { + // The zombie check above should have caught this in + // more detail. print("runtime: nelems=", s.nelems, " nalloc=", nalloc, " previous allocCount=", s.allocCount, " nfreed=", nfreed, "\n") throw("sweep increased allocation count") } @@ -755,6 +776,57 @@ func (s *mspan) oldSweep(preserve bool) bool { return res } +// reportZombies reports any marked but free objects in s and throws. +// +// This generally means one of the following: +// +// 1. User code converted a pointer to a uintptr and then back +// unsafely, and a GC ran while the uintptr was the only reference to +// an object. +// +// 2. User code (or a compiler bug) constructed a bad pointer that +// points to a free slot, often a past-the-end pointer. +// +// 3. The GC two cycles ago missed a pointer and freed a live object, +// but it was still live in the last cycle, so this GC cycle found a +// pointer to that object and marked it. +func (s *mspan) reportZombies() { + printlock() + print("runtime: marked free object in span ", s, ", elemsize=", s.elemsize, " freeindex=", s.freeindex, " (bad use of unsafe.Pointer? try -d=checkptr)\n") + mbits := s.markBitsForBase() + abits := s.allocBitsForIndex(0) + for i := uintptr(0); i < s.nelems; i++ { + addr := s.base() + i*s.elemsize + print(hex(addr)) + alloc := i < s.freeindex || abits.isMarked() + if alloc { + print(" alloc") + } else { + print(" free ") + } + if mbits.isMarked() { + print(" marked ") + } else { + print(" unmarked") + } + zombie := mbits.isMarked() && !alloc + if zombie { + print(" zombie") + } + print("\n") + if zombie { + length := s.elemsize + if length > 1024 { + length = 1024 + } + hexdumpWords(addr, addr+length, nil) + } + mbits.advance() + abits.advance() + } + throw("found pointer to free object") +} + // deductSweepCredit deducts sweep credit for allocating a span of // size spanBytes. This must be performed *before* the span is // allocated to ensure the system has enough credit. If necessary, it diff --git a/src/runtime/proc.go b/src/runtime/proc.go index b423026c0e..e5823dd804 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -1820,10 +1820,16 @@ func startTemplateThread() { if GOARCH == "wasm" { // no threads on wasm yet return } + + // Disable preemption to guarantee that the template thread will be + // created before a park once haveTemplateThread is set. + mp := acquirem() if !atomic.Cas(&newmHandoff.haveTemplateThread, 0, 1) { + releasem(mp) return } newm(templateThread, nil) + releasem(mp) } // templateThread is a thread in a known-good state that exists solely diff --git a/src/runtime/proc_test.go b/src/runtime/proc_test.go index 764a279fca..de4dec36ce 100644 --- a/src/runtime/proc_test.go +++ b/src/runtime/proc_test.go @@ -7,6 +7,7 @@ package runtime_test import ( "fmt" "internal/race" + "internal/testenv" "math" "net" "runtime" @@ -929,6 +930,29 @@ func TestLockOSThreadAvoidsStatePropagation(t *testing.T) { } } +func TestLockOSThreadTemplateThreadRace(t *testing.T) { + testenv.MustHaveGoRun(t) + + exe, err := buildTestProg(t, "testprog") + if err != nil { + t.Fatal(err) + } + + iterations := 100 + if testing.Short() { + // Reduce run time to ~100ms, with much lower probability of + // catching issues. + iterations = 5 + } + for i := 0; i < iterations; i++ { + want := "OK\n" + output := runBuiltTestProg(t, exe, "LockOSThreadTemplateThreadRace") + if output != want { + t.Fatalf("run %d: want %q, got %q", i, want, output) + } + } +} + // fakeSyscall emulates a system call. //go:nosplit func fakeSyscall(duration time.Duration) { diff --git a/src/runtime/rt0_openbsd_arm64.s b/src/runtime/rt0_openbsd_arm64.s index ab8ea97f4f..12408f2eec 100644 --- a/src/runtime/rt0_openbsd_arm64.s +++ b/src/runtime/rt0_openbsd_arm64.s @@ -4,6 +4,12 @@ #include "textflag.h" +// See comment in runtime/sys_openbsd_arm64.s re this construction. +#define INVOKE_SYSCALL \ + SVC; \ + NOOP; \ + NOOP + TEXT _rt0_arm64_openbsd(SB),NOSPLIT|NOFRAME,$0 MOVD 0(RSP), R0 // argc ADD $8, RSP, R1 // argv @@ -101,5 +107,5 @@ TEXT main(SB),NOSPLIT|NOFRAME,$0 exit: MOVD $0, R0 MOVD $1, R8 // sys_exit - SVC + INVOKE_SYSCALL B exit diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index bb625aa406..2818ada3e0 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -241,8 +241,11 @@ func testGdbPython(t *testing.T, cgo bool) { "-ex", "echo END\n", filepath.Join(dir, "a.exe"), ) - got, _ := exec.Command("gdb", args...).CombinedOutput() - t.Logf("gdb output: %s\n", got) + got, err := exec.Command("gdb", args...).CombinedOutput() + t.Logf("gdb output:\n%s", got) + if err != nil { + t.Fatalf("gdb exited with error: %v", err) + } firstLine := bytes.SplitN(got, []byte("\n"), 2)[0] if string(firstLine) != "Loading Go Runtime support." { @@ -388,7 +391,11 @@ func TestGdbBacktrace(t *testing.T) { "-ex", "continue", filepath.Join(dir, "a.exe"), } - got, _ := exec.Command("gdb", args...).CombinedOutput() + got, err := exec.Command("gdb", args...).CombinedOutput() + t.Logf("gdb output:\n%s", got) + if err != nil { + t.Fatalf("gdb exited with error: %v", err) + } // Check that the backtrace matches the source code. bt := []string{ @@ -403,8 +410,7 @@ func TestGdbBacktrace(t *testing.T) { s := fmt.Sprintf("#%v.*main\\.%v", i, name) re := regexp.MustCompile(s) if found := re.Find(got) != nil; !found { - t.Errorf("could not find '%v' in backtrace", s) - t.Fatalf("gdb output:\n%v", string(got)) + t.Fatalf("could not find '%v' in backtrace", s) } } } @@ -463,7 +469,11 @@ func TestGdbAutotmpTypes(t *testing.T) { "-ex", "info types astruct", filepath.Join(dir, "a.exe"), } - got, _ := exec.Command("gdb", args...).CombinedOutput() + got, err := exec.Command("gdb", args...).CombinedOutput() + t.Logf("gdb output:\n%s", got) + if err != nil { + t.Fatalf("gdb exited with error: %v", err) + } sgot := string(got) @@ -477,8 +487,7 @@ func TestGdbAutotmpTypes(t *testing.T) { } for _, name := range types { if !strings.Contains(sgot, name) { - t.Errorf("could not find %s in 'info typrs astruct' output", name) - t.Fatalf("gdb output:\n%v", sgot) + t.Fatalf("could not find %s in 'info typrs astruct' output", name) } } } @@ -532,12 +541,14 @@ func TestGdbConst(t *testing.T) { "-ex", "print 'runtime._PageSize'", filepath.Join(dir, "a.exe"), } - got, _ := exec.Command("gdb", args...).CombinedOutput() + got, err := exec.Command("gdb", args...).CombinedOutput() + t.Logf("gdb output:\n%s", got) + if err != nil { + t.Fatalf("gdb exited with error: %v", err) + } sgot := strings.ReplaceAll(string(got), "\r\n", "\n") - t.Logf("output %q", sgot) - if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") { t.Fatalf("output mismatch") } @@ -592,7 +603,11 @@ func TestGdbPanic(t *testing.T) { "-ex", "backtrace", filepath.Join(dir, "a.exe"), } - got, _ := exec.Command("gdb", args...).CombinedOutput() + got, err := exec.Command("gdb", args...).CombinedOutput() + t.Logf("gdb output:\n%s", got) + if err != nil { + t.Fatalf("gdb exited with error: %v", err) + } // Check that the backtrace matches the source code. bt := []string{ @@ -603,8 +618,7 @@ func TestGdbPanic(t *testing.T) { s := fmt.Sprintf("(#.* .* in )?main\\.%v", name) re := regexp.MustCompile(s) if found := re.Find(got) != nil; !found { - t.Errorf("could not find '%v' in backtrace", s) - t.Fatalf("gdb output:\n%v", string(got)) + t.Fatalf("could not find '%v' in backtrace", s) } } } @@ -671,7 +685,11 @@ func TestGdbInfCallstack(t *testing.T) { "-ex", "continue", filepath.Join(dir, "a.exe"), } - got, _ := exec.Command("gdb", args...).CombinedOutput() + got, err := exec.Command("gdb", args...).CombinedOutput() + t.Logf("gdb output:\n%s", got) + if err != nil { + t.Fatalf("gdb exited with error: %v", err) + } // Check that the backtrace matches // We check the 3 inner most frames only as they are present certainly, according to gcc__arm64.c @@ -684,8 +702,7 @@ func TestGdbInfCallstack(t *testing.T) { s := fmt.Sprintf("#%v.*%v", i, name) re := regexp.MustCompile(s) if found := re.Find(got) != nil; !found { - t.Errorf("could not find '%v' in backtrace", s) - t.Fatalf("gdb output:\n%v", string(got)) + t.Fatalf("could not find '%v' in backtrace", s) } } } diff --git a/src/runtime/sizeof_test.go b/src/runtime/sizeof_test.go index d6156902c1..736e848f8c 100644 --- a/src/runtime/sizeof_test.go +++ b/src/runtime/sizeof_test.go @@ -21,7 +21,7 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {runtime.G{}, 216, 376}, // g, but exported for testing + {runtime.G{}, 216, 376}, // g, but exported for testing {runtime.Sudog{}, 56, 88}, // sudog, but exported for testing } diff --git a/src/runtime/sys_openbsd_arm.s b/src/runtime/sys_openbsd_arm.s index 11f6e00100..9e18ce0e16 100644 --- a/src/runtime/sys_openbsd_arm.s +++ b/src/runtime/sys_openbsd_arm.s @@ -13,11 +13,23 @@ #define CLOCK_REALTIME $0 #define CLOCK_MONOTONIC $3 +// With OpenBSD 6.7 onwards, an armv7 syscall returns two instructions +// after the SWI instruction, to allow for a speculative execution +// barrier to be placed after the SWI without impacting performance. +// For now use hardware no-ops as this works with both older and newer +// kernels. After OpenBSD 6.8 is released this should be changed to +// speculation barriers. +#define NOOP MOVW R0, R0 +#define INVOKE_SYSCALL \ + SWI $0; \ + NOOP; \ + NOOP + // Exit the entire program (like C exit) TEXT runtime·exit(SB),NOSPLIT|NOFRAME,$0 MOVW code+0(FP), R0 // arg 1 - status MOVW $1, R12 // sys_exit - SWI $0 + INVOKE_SYSCALL MOVW.CS $0, R8 // crash on syscall failure MOVW.CS R8, (R8) RET @@ -26,7 +38,7 @@ TEXT runtime·exit(SB),NOSPLIT|NOFRAME,$0 TEXT runtime·exitThread(SB),NOSPLIT,$0-4 MOVW wait+0(FP), R0 // arg 1 - notdead MOVW $302, R12 // sys___threxit - SWI $0 + INVOKE_SYSCALL MOVW.CS $1, R8 // crash on syscall failure MOVW.CS R8, (R8) JMP 0(PC) @@ -36,7 +48,7 @@ TEXT runtime·open(SB),NOSPLIT|NOFRAME,$0 MOVW mode+4(FP), R1 // arg 2 - mode MOVW perm+8(FP), R2 // arg 3 - perm MOVW $5, R12 // sys_open - SWI $0 + INVOKE_SYSCALL MOVW.CS $-1, R0 MOVW R0, ret+12(FP) RET @@ -44,7 +56,7 @@ TEXT runtime·open(SB),NOSPLIT|NOFRAME,$0 TEXT runtime·closefd(SB),NOSPLIT|NOFRAME,$0 MOVW fd+0(FP), R0 // arg 1 - fd MOVW $6, R12 // sys_close - SWI $0 + INVOKE_SYSCALL MOVW.CS $-1, R0 MOVW R0, ret+4(FP) RET @@ -54,7 +66,7 @@ TEXT runtime·read(SB),NOSPLIT|NOFRAME,$0 MOVW p+4(FP), R1 // arg 2 - buf MOVW n+8(FP), R2 // arg 3 - nbyte MOVW $3, R12 // sys_read - SWI $0 + INVOKE_SYSCALL RSB.CS $0, R0 // caller expects negative errno MOVW R0, ret+12(FP) RET @@ -63,7 +75,7 @@ TEXT runtime·read(SB),NOSPLIT|NOFRAME,$0 TEXT runtime·pipe(SB),NOSPLIT,$0-12 MOVW $r+0(FP), R0 MOVW $263, R12 - SWI $0 + INVOKE_SYSCALL MOVW R0, errno+8(FP) RET @@ -72,7 +84,7 @@ TEXT runtime·pipe2(SB),NOSPLIT,$0-16 MOVW $r+4(FP), R0 MOVW flags+0(FP), R1 MOVW $101, R12 - SWI $0 + INVOKE_SYSCALL MOVW R0, errno+12(FP) RET @@ -81,7 +93,7 @@ TEXT runtime·write1(SB),NOSPLIT|NOFRAME,$0 MOVW p+4(FP), R1 // arg 2 - buf MOVW n+8(FP), R2 // arg 3 - nbyte MOVW $4, R12 // sys_write - SWI $0 + INVOKE_SYSCALL RSB.CS $0, R0 // caller expects negative errno MOVW R0, ret+12(FP) RET @@ -99,12 +111,12 @@ TEXT runtime·usleep(SB),NOSPLIT,$16 MOVW $4(R13), R0 // arg 1 - rqtp MOVW $0, R1 // arg 2 - rmtp MOVW $91, R12 // sys_nanosleep - SWI $0 + INVOKE_SYSCALL RET TEXT runtime·getthrid(SB),NOSPLIT,$0-4 MOVW $299, R12 // sys_getthrid - SWI $0 + INVOKE_SYSCALL MOVW R0, ret+0(FP) RET @@ -113,16 +125,16 @@ TEXT runtime·thrkill(SB),NOSPLIT,$0-8 MOVW sig+4(FP), R1 // arg 2 - signum MOVW $0, R2 // arg 3 - tcb MOVW $119, R12 // sys_thrkill - SWI $0 + INVOKE_SYSCALL RET TEXT runtime·raiseproc(SB),NOSPLIT,$12 - MOVW $20, R12 - SWI $0 // sys_getpid + MOVW $20, R12 // sys_getpid + INVOKE_SYSCALL // arg 1 - pid, already in R0 MOVW sig+0(FP), R1 // arg 2 - signum MOVW $122, R12 // sys_kill - SWI $0 + INVOKE_SYSCALL RET TEXT runtime·mmap(SB),NOSPLIT,$16 @@ -140,7 +152,7 @@ TEXT runtime·mmap(SB),NOSPLIT,$16 MOVW R7, 16(R13) // high 32 bits ADD $4, R13 MOVW $197, R12 // sys_mmap - SWI $0 + INVOKE_SYSCALL SUB $4, R13 MOVW $0, R1 MOVW.CS R0, R1 // if error, move to R1 @@ -153,7 +165,7 @@ TEXT runtime·munmap(SB),NOSPLIT,$0 MOVW addr+0(FP), R0 // arg 1 - addr MOVW n+4(FP), R1 // arg 2 - len MOVW $73, R12 // sys_munmap - SWI $0 + INVOKE_SYSCALL MOVW.CS $0, R8 // crash on syscall failure MOVW.CS R8, (R8) RET @@ -163,7 +175,7 @@ TEXT runtime·madvise(SB),NOSPLIT,$0 MOVW n+4(FP), R1 // arg 2 - len MOVW flags+8(FP), R2 // arg 2 - flags MOVW $75, R12 // sys_madvise - SWI $0 + INVOKE_SYSCALL MOVW.CS $-1, R0 MOVW R0, ret+12(FP) RET @@ -173,7 +185,7 @@ TEXT runtime·setitimer(SB),NOSPLIT,$0 MOVW new+4(FP), R1 // arg 2 - new value MOVW old+8(FP), R2 // arg 3 - old value MOVW $69, R12 // sys_setitimer - SWI $0 + INVOKE_SYSCALL RET // func walltime1() (sec int64, nsec int32) @@ -181,7 +193,7 @@ TEXT runtime·walltime1(SB), NOSPLIT, $32 MOVW CLOCK_REALTIME, R0 // arg 1 - clock_id MOVW $8(R13), R1 // arg 2 - tp MOVW $87, R12 // sys_clock_gettime - SWI $0 + INVOKE_SYSCALL MOVW 8(R13), R0 // sec - l32 MOVW 12(R13), R1 // sec - h32 @@ -199,7 +211,7 @@ TEXT runtime·nanotime1(SB),NOSPLIT,$32 MOVW CLOCK_MONOTONIC, R0 // arg 1 - clock_id MOVW $8(R13), R1 // arg 2 - tp MOVW $87, R12 // sys_clock_gettime - SWI $0 + INVOKE_SYSCALL MOVW 8(R13), R0 // sec - l32 MOVW 12(R13), R4 // sec - h32 @@ -220,7 +232,7 @@ TEXT runtime·sigaction(SB),NOSPLIT,$0 MOVW new+4(FP), R1 // arg 2 - new sigaction MOVW old+8(FP), R2 // arg 3 - old sigaction MOVW $46, R12 // sys_sigaction - SWI $0 + INVOKE_SYSCALL MOVW.CS $3, R8 // crash on syscall failure MOVW.CS R8, (R8) RET @@ -229,7 +241,7 @@ TEXT runtime·obsdsigprocmask(SB),NOSPLIT,$0 MOVW how+0(FP), R0 // arg 1 - mode MOVW new+4(FP), R1 // arg 2 - new MOVW $48, R12 // sys_sigprocmask - SWI $0 + INVOKE_SYSCALL MOVW.CS $3, R8 // crash on syscall failure MOVW.CS R8, (R8) MOVW R0, ret+8(FP) @@ -280,7 +292,7 @@ TEXT runtime·tfork(SB),NOSPLIT,$0 MOVW param+0(FP), R0 // arg 1 - param MOVW psize+4(FP), R1 // arg 2 - psize MOVW $8, R12 // sys___tfork - SWI $0 + INVOKE_SYSCALL // Return if syscall failed. B.CC 4(PC) @@ -313,14 +325,14 @@ TEXT runtime·sigaltstack(SB),NOSPLIT,$0 MOVW new+0(FP), R0 // arg 1 - new sigaltstack MOVW old+4(FP), R1 // arg 2 - old sigaltstack MOVW $288, R12 // sys_sigaltstack - SWI $0 + INVOKE_SYSCALL MOVW.CS $0, R8 // crash on syscall failure MOVW.CS R8, (R8) RET TEXT runtime·osyield(SB),NOSPLIT,$0 MOVW $298, R12 // sys_sched_yield - SWI $0 + INVOKE_SYSCALL RET TEXT runtime·thrsleep(SB),NOSPLIT,$4 @@ -332,7 +344,7 @@ TEXT runtime·thrsleep(SB),NOSPLIT,$4 MOVW R4, 4(R13) ADD $4, R13 MOVW $94, R12 // sys___thrsleep - SWI $0 + INVOKE_SYSCALL SUB $4, R13 MOVW R0, ret+20(FP) RET @@ -341,7 +353,7 @@ TEXT runtime·thrwakeup(SB),NOSPLIT,$0 MOVW ident+0(FP), R0 // arg 1 - ident MOVW n+4(FP), R1 // arg 2 - n MOVW $301, R12 // sys___thrwakeup - SWI $0 + INVOKE_SYSCALL MOVW R0, ret+8(FP) RET @@ -356,7 +368,7 @@ TEXT runtime·sysctl(SB),NOSPLIT,$8 MOVW R5, 8(R13) ADD $4, R13 MOVW $202, R12 // sys___sysctl - SWI $0 + INVOKE_SYSCALL SUB $4, R13 MOVW.CC $0, R0 RSB.CS $0, R0 @@ -366,7 +378,7 @@ TEXT runtime·sysctl(SB),NOSPLIT,$8 // int32 runtime·kqueue(void); TEXT runtime·kqueue(SB),NOSPLIT,$0 MOVW $269, R12 // sys_kqueue - SWI $0 + INVOKE_SYSCALL RSB.CS $0, R0 MOVW R0, ret+0(FP) RET @@ -383,7 +395,7 @@ TEXT runtime·kevent(SB),NOSPLIT,$8 MOVW R5, 8(R13) ADD $4, R13 MOVW $72, R12 // sys_kevent - SWI $0 + INVOKE_SYSCALL RSB.CS $0, R0 SUB $4, R13 MOVW R0, ret+24(FP) @@ -395,7 +407,7 @@ TEXT runtime·closeonexec(SB),NOSPLIT,$0 MOVW $2, R1 // arg 2 - cmd (F_SETFD) MOVW $1, R2 // arg 3 - arg (FD_CLOEXEC) MOVW $92, R12 // sys_fcntl - SWI $0 + INVOKE_SYSCALL RET // func runtime·setNonblock(fd int32) @@ -404,12 +416,12 @@ TEXT runtime·setNonblock(SB),NOSPLIT,$0-4 MOVW $3, R1 // F_GETFL MOVW $0, R2 MOVW $92, R12 - SWI $0 + INVOKE_SYSCALL ORR $0x4, R0, R2 // O_NONBLOCK MOVW fd+0(FP), R0 // fd MOVW $4, R1 // F_SETFL MOVW $92, R12 - SWI $0 + INVOKE_SYSCALL RET TEXT ·publicationBarrier(SB),NOSPLIT|NOFRAME,$0-0 @@ -418,6 +430,6 @@ TEXT ·publicationBarrier(SB),NOSPLIT|NOFRAME,$0-0 TEXT runtime·read_tls_fallback(SB),NOSPLIT|NOFRAME,$0 MOVM.WP [R1, R2, R3, R12], (R13) MOVW $330, R12 // sys___get_tcb - SWI $0 + INVOKE_SYSCALL MOVM.IAW (R13), [R1, R2, R3, R12] RET diff --git a/src/runtime/sys_openbsd_arm64.s b/src/runtime/sys_openbsd_arm64.s index 839aa57062..621b1b1a42 100644 --- a/src/runtime/sys_openbsd_arm64.s +++ b/src/runtime/sys_openbsd_arm64.s @@ -13,11 +13,22 @@ #define CLOCK_REALTIME $0 #define CLOCK_MONOTONIC $3 +// With OpenBSD 6.7 onwards, an arm64 syscall returns two instructions +// after the SVC instruction, to allow for a speculative execution +// barrier to be placed after the SVC without impacting performance. +// For now use hardware no-ops as this works with both older and newer +// kernels. After OpenBSD 6.8 is released this should be changed to +// speculation barriers. +#define INVOKE_SYSCALL \ + SVC; \ + NOOP; \ + NOOP + // Exit the entire program (like C exit) TEXT runtime·exit(SB),NOSPLIT|NOFRAME,$0 MOVW code+0(FP), R0 // arg 1 - status MOVD $1, R8 // sys_exit - SVC + INVOKE_SYSCALL BCC 3(PC) MOVD $0, R0 // crash on syscall failure MOVD R0, (R0) @@ -27,7 +38,7 @@ TEXT runtime·exit(SB),NOSPLIT|NOFRAME,$0 TEXT runtime·exitThread(SB),NOSPLIT,$0 MOVD wait+0(FP), R0 // arg 1 - notdead MOVD $302, R8 // sys___threxit - SVC + INVOKE_SYSCALL MOVD $0, R0 // crash on syscall failure MOVD R0, (R0) JMP 0(PC) @@ -37,7 +48,7 @@ TEXT runtime·open(SB),NOSPLIT|NOFRAME,$0 MOVW mode+8(FP), R1 // arg 2 - mode MOVW perm+12(FP), R2 // arg 3 - perm MOVD $5, R8 // sys_open - SVC + INVOKE_SYSCALL BCC 2(PC) MOVW $-1, R0 MOVW R0, ret+16(FP) @@ -46,7 +57,7 @@ TEXT runtime·open(SB),NOSPLIT|NOFRAME,$0 TEXT runtime·closefd(SB),NOSPLIT|NOFRAME,$0 MOVW fd+0(FP), R0 // arg 1 - fd MOVD $6, R8 // sys_close - SVC + INVOKE_SYSCALL BCC 2(PC) MOVW $-1, R0 MOVW R0, ret+8(FP) @@ -57,7 +68,7 @@ TEXT runtime·read(SB),NOSPLIT|NOFRAME,$0 MOVD p+8(FP), R1 // arg 2 - buf MOVW n+16(FP), R2 // arg 3 - nbyte MOVD $3, R8 // sys_read - SVC + INVOKE_SYSCALL BCC 2(PC) NEG R0, R0 MOVW R0, ret+24(FP) @@ -68,7 +79,7 @@ TEXT runtime·pipe(SB),NOSPLIT|NOFRAME,$0-12 MOVD $r+0(FP), R0 MOVW $0, R1 MOVD $101, R8 // sys_pipe2 - SVC + INVOKE_SYSCALL BCC 2(PC) NEG R0, R0 MOVW R0, errno+8(FP) @@ -79,7 +90,7 @@ TEXT runtime·pipe2(SB),NOSPLIT|NOFRAME,$0-20 MOVD $r+8(FP), R0 MOVW flags+0(FP), R1 MOVD $101, R8 // sys_pipe2 - SVC + INVOKE_SYSCALL BCC 2(PC) NEG R0, R0 MOVW R0, errno+16(FP) @@ -90,7 +101,7 @@ TEXT runtime·write1(SB),NOSPLIT|NOFRAME,$0 MOVD p+8(FP), R1 // arg 2 - buf MOVW n+16(FP), R2 // arg 3 - nbyte MOVD $4, R8 // sys_write - SVC + INVOKE_SYSCALL BCC 2(PC) NEG R0, R0 MOVW R0, ret+24(FP) @@ -111,12 +122,12 @@ TEXT runtime·usleep(SB),NOSPLIT,$24-4 ADD $8, RSP, R0 // arg 1 - rqtp MOVD $0, R1 // arg 2 - rmtp MOVD $91, R8 // sys_nanosleep - SVC + INVOKE_SYSCALL RET TEXT runtime·getthrid(SB),NOSPLIT,$0-4 MOVD $299, R8 // sys_getthrid - SVC + INVOKE_SYSCALL MOVW R0, ret+0(FP) RET @@ -125,16 +136,16 @@ TEXT runtime·thrkill(SB),NOSPLIT,$0-16 MOVD sig+8(FP), R1 // arg 2 - signum MOVW $0, R2 // arg 3 - tcb MOVD $119, R8 // sys_thrkill - SVC + INVOKE_SYSCALL RET TEXT runtime·raiseproc(SB),NOSPLIT,$0 MOVD $20, R8 // sys_getpid - SVC + INVOKE_SYSCALL // arg 1 - pid, already in R0 MOVW sig+0(FP), R1 // arg 2 - signum MOVD $122, R8 // sys_kill - SVC + INVOKE_SYSCALL RET TEXT runtime·mmap(SB),NOSPLIT,$0 @@ -146,7 +157,7 @@ TEXT runtime·mmap(SB),NOSPLIT,$0 MOVW $0, R5 // arg 6 - pad MOVW off+28(FP), R6 // arg 7 - offset MOVD $197, R8 // sys_mmap - SVC + INVOKE_SYSCALL MOVD $0, R1 BCC 3(PC) MOVD R0, R1 // if error, move to R1 @@ -159,7 +170,7 @@ TEXT runtime·munmap(SB),NOSPLIT,$0 MOVD addr+0(FP), R0 // arg 1 - addr MOVD n+8(FP), R1 // arg 2 - len MOVD $73, R8 // sys_munmap - SVC + INVOKE_SYSCALL BCC 3(PC) MOVD $0, R0 // crash on syscall failure MOVD R0, (R0) @@ -170,7 +181,7 @@ TEXT runtime·madvise(SB),NOSPLIT,$0 MOVD n+8(FP), R1 // arg 2 - len MOVW flags+16(FP), R2 // arg 2 - flags MOVD $75, R8 // sys_madvise - SVC + INVOKE_SYSCALL BCC 2(PC) MOVW $-1, R0 MOVW R0, ret+24(FP) @@ -181,7 +192,7 @@ TEXT runtime·setitimer(SB),NOSPLIT,$0 MOVD new+8(FP), R1 // arg 2 - new value MOVD old+16(FP), R2 // arg 3 - old value MOVD $69, R8 // sys_setitimer - SVC + INVOKE_SYSCALL RET // func walltime1() (sec int64, nsec int32) @@ -189,7 +200,7 @@ TEXT runtime·walltime1(SB), NOSPLIT, $32 MOVW CLOCK_REALTIME, R0 // arg 1 - clock_id MOVD $8(RSP), R1 // arg 2 - tp MOVD $87, R8 // sys_clock_gettime - SVC + INVOKE_SYSCALL MOVD 8(RSP), R0 // sec MOVD 16(RSP), R1 // nsec @@ -204,7 +215,7 @@ TEXT runtime·nanotime1(SB),NOSPLIT,$32 MOVW CLOCK_MONOTONIC, R0 // arg 1 - clock_id MOVD $8(RSP), R1 // arg 2 - tp MOVD $87, R8 // sys_clock_gettime - SVC + INVOKE_SYSCALL MOVW 8(RSP), R3 // sec MOVW 16(RSP), R5 // nsec @@ -220,7 +231,7 @@ TEXT runtime·sigaction(SB),NOSPLIT,$0 MOVD new+8(FP), R1 // arg 2 - new sigaction MOVD old+16(FP), R2 // arg 3 - old sigaction MOVD $46, R8 // sys_sigaction - SVC + INVOKE_SYSCALL BCC 3(PC) MOVD $3, R0 // crash on syscall failure MOVD R0, (R0) @@ -230,7 +241,7 @@ TEXT runtime·obsdsigprocmask(SB),NOSPLIT,$0 MOVW how+0(FP), R0 // arg 1 - mode MOVW new+4(FP), R1 // arg 2 - new MOVD $48, R8 // sys_sigprocmask - SVC + INVOKE_SYSCALL BCC 3(PC) MOVD $3, R8 // crash on syscall failure MOVD R8, (R8) @@ -314,7 +325,7 @@ TEXT runtime·tfork(SB),NOSPLIT,$0 MOVD param+0(FP), R0 // arg 1 - param MOVD psize+8(FP), R1 // arg 2 - psize MOVD $8, R8 // sys___tfork - SVC + INVOKE_SYSCALL // Return if syscall failed. BCC 4(PC) @@ -344,7 +355,7 @@ TEXT runtime·sigaltstack(SB),NOSPLIT,$0 MOVD new+0(FP), R0 // arg 1 - new sigaltstack MOVD old+8(FP), R1 // arg 2 - old sigaltstack MOVD $288, R8 // sys_sigaltstack - SVC + INVOKE_SYSCALL BCC 3(PC) MOVD $0, R8 // crash on syscall failure MOVD R8, (R8) @@ -352,7 +363,7 @@ TEXT runtime·sigaltstack(SB),NOSPLIT,$0 TEXT runtime·osyield(SB),NOSPLIT,$0 MOVD $298, R8 // sys_sched_yield - SVC + INVOKE_SYSCALL RET TEXT runtime·thrsleep(SB),NOSPLIT,$0 @@ -362,7 +373,7 @@ TEXT runtime·thrsleep(SB),NOSPLIT,$0 MOVD lock+24(FP), R3 // arg 4 - lock MOVD abort+32(FP), R4 // arg 5 - abort MOVD $94, R8 // sys___thrsleep - SVC + INVOKE_SYSCALL MOVW R0, ret+40(FP) RET @@ -370,7 +381,7 @@ TEXT runtime·thrwakeup(SB),NOSPLIT,$0 MOVD ident+0(FP), R0 // arg 1 - ident MOVW n+8(FP), R1 // arg 2 - n MOVD $301, R8 // sys___thrwakeup - SVC + INVOKE_SYSCALL MOVW R0, ret+16(FP) RET @@ -382,7 +393,7 @@ TEXT runtime·sysctl(SB),NOSPLIT,$0 MOVD dst+32(FP), R4 // arg 5 - dest MOVD ndst+40(FP), R5 // arg 6 - newlen MOVD $202, R8 // sys___sysctl - SVC + INVOKE_SYSCALL BCC 2(PC) NEG R0, R0 MOVW R0, ret+48(FP) @@ -391,7 +402,7 @@ TEXT runtime·sysctl(SB),NOSPLIT,$0 // int32 runtime·kqueue(void); TEXT runtime·kqueue(SB),NOSPLIT,$0 MOVD $269, R8 // sys_kqueue - SVC + INVOKE_SYSCALL BCC 2(PC) NEG R0, R0 MOVW R0, ret+0(FP) @@ -406,7 +417,7 @@ TEXT runtime·kevent(SB),NOSPLIT,$0 MOVW nev+32(FP), R4 // arg 5 - nevents MOVD ts+40(FP), R5 // arg 6 - timeout MOVD $72, R8 // sys_kevent - SVC + INVOKE_SYSCALL BCC 2(PC) NEG R0, R0 MOVW R0, ret+48(FP) @@ -418,7 +429,7 @@ TEXT runtime·closeonexec(SB),NOSPLIT,$0 MOVD $2, R1 // arg 2 - cmd (F_SETFD) MOVD $1, R2 // arg 3 - arg (FD_CLOEXEC) MOVD $92, R8 // sys_fcntl - SVC + INVOKE_SYSCALL RET // func runtime·setNonblock(int32 fd) @@ -427,11 +438,11 @@ TEXT runtime·setNonblock(SB),NOSPLIT|NOFRAME,$0-4 MOVD $3, R1 // arg 2 - cmd (F_GETFL) MOVD $0, R2 // arg 3 MOVD $92, R8 // sys_fcntl - SVC + INVOKE_SYSCALL MOVD $4, R2 // O_NONBLOCK ORR R0, R2 // arg 3 - flags MOVW fd+0(FP), R0 // arg 1 - fd MOVD $4, R1 // arg 2 - cmd (F_SETFL) MOVD $92, R8 // sys_fcntl - SVC + INVOKE_SYSCALL RET diff --git a/src/runtime/testdata/testprog/gc.go b/src/runtime/testdata/testprog/gc.go index f691a1d127..74732cd9f4 100644 --- a/src/runtime/testdata/testprog/gc.go +++ b/src/runtime/testdata/testprog/gc.go @@ -11,6 +11,7 @@ import ( "runtime/debug" "sync/atomic" "time" + "unsafe" ) func init() { @@ -19,6 +20,7 @@ func init() { register("GCSys", GCSys) register("GCPhys", GCPhys) register("DeferLiveness", DeferLiveness) + register("GCZombie", GCZombie) } func GCSys() { @@ -264,3 +266,37 @@ func DeferLiveness() { func escape(x interface{}) { sink2 = x; sink2 = nil } var sink2 interface{} + +// Test zombie object detection and reporting. +func GCZombie() { + // Allocate several objects of unusual size (so free slots are + // unlikely to all be re-allocated by the runtime). + const size = 190 + const count = 8192 / size + keep := make([]*byte, 0, (count+1)/2) + free := make([]uintptr, 0, (count+1)/2) + zombies := make([]*byte, 0, len(free)) + for i := 0; i < count; i++ { + obj := make([]byte, size) + p := &obj[0] + if i%2 == 0 { + keep = append(keep, p) + } else { + free = append(free, uintptr(unsafe.Pointer(p))) + } + } + + // Free the unreferenced objects. + runtime.GC() + + // Bring the free objects back to life. + for _, p := range free { + zombies = append(zombies, (*byte)(unsafe.Pointer(p))) + } + + // GC should detect the zombie objects. + runtime.GC() + println("failed") + runtime.KeepAlive(keep) + runtime.KeepAlive(zombies) +} diff --git a/src/runtime/testdata/testprog/lockosthread.go b/src/runtime/testdata/testprog/lockosthread.go index fd3123e647..e9d7fdbc44 100644 --- a/src/runtime/testdata/testprog/lockosthread.go +++ b/src/runtime/testdata/testprog/lockosthread.go @@ -7,6 +7,7 @@ package main import ( "os" "runtime" + "sync" "time" ) @@ -30,6 +31,7 @@ func init() { runtime.LockOSThread() }) register("LockOSThreadAvoidsStatePropagation", LockOSThreadAvoidsStatePropagation) + register("LockOSThreadTemplateThreadRace", LockOSThreadTemplateThreadRace) } func LockOSThreadMain() { @@ -195,3 +197,50 @@ func LockOSThreadAvoidsStatePropagation() { runtime.UnlockOSThread() println("OK") } + +func LockOSThreadTemplateThreadRace() { + // This test attempts to reproduce the race described in + // golang.org/issue/38931. To do so, we must have a stop-the-world + // (achieved via ReadMemStats) racing with two LockOSThread calls. + // + // While this test attempts to line up the timing, it is only expected + // to fail (and thus hang) around 2% of the time if the race is + // present. + + // Ensure enough Ps to actually run everything in parallel. Though on + // <4 core machines, we are still at the whim of the kernel scheduler. + runtime.GOMAXPROCS(4) + + go func() { + // Stop the world; race with LockOSThread below. + var m runtime.MemStats + for { + runtime.ReadMemStats(&m) + } + }() + + // Try to synchronize both LockOSThreads. + start := time.Now().Add(10 * time.Millisecond) + + var wg sync.WaitGroup + wg.Add(2) + + for i := 0; i < 2; i++ { + go func() { + for time.Now().Before(start) { + } + + // Add work to the local runq to trigger early startm + // in handoffp. + go func() {}() + + runtime.LockOSThread() + runtime.Gosched() // add a preemption point. + wg.Done() + }() + } + + wg.Wait() + // If both LockOSThreads completed then we did not hit the race. + println("OK") +} diff --git a/src/syscall/asm_openbsd_arm.s b/src/syscall/asm_openbsd_arm.s index 9279ed960f..26fd791fda 100644 --- a/src/syscall/asm_openbsd_arm.s +++ b/src/syscall/asm_openbsd_arm.s @@ -15,13 +15,20 @@ // func RawSyscall(trap int32, a1, a2, a3 int32) (r1, r2, err int32); // func RawSyscall6(trap int32, a1, a2, a3, a4, a5, a6 int32) (r1, r2, err int32); +// See comment in runtime/sys_openbsd_arm.s re this construction. +#define NOOP MOVW R0, R0 +#define INVOKE_SYSCALL \ + SWI $0; \ + NOOP; \ + NOOP + TEXT ·Syscall(SB),NOSPLIT,$0-28 BL runtime·entersyscall(SB) MOVW trap+0(FP), R12 // syscall number MOVW a1+4(FP), R0 // arg 1 MOVW a2+8(FP), R1 // arg 2 MOVW a3+12(FP), R2 // arg 3 - SWI $0 + INVOKE_SYSCALL MOVW $0, R2 BCS error MOVW R0, r1+16(FP) // ret 1 @@ -46,7 +53,7 @@ TEXT ·Syscall6(SB),NOSPLIT,$0-40 MOVW a4+16(FP), R3 // arg 4 MOVW R13, R4 MOVW $a5+20(FP), R13 // arg 5 to arg 6 are passed on stack - SWI $0 + INVOKE_SYSCALL MOVW R4, R13 MOVW $0, R2 BCS error6 @@ -72,7 +79,7 @@ TEXT ·Syscall9(SB),NOSPLIT,$0-52 MOVW a4+16(FP), R3 // arg 4 MOVW R13, R4 MOVW $a5+20(FP), R13 // arg 5 to arg 9 are passed on stack - SWI $0 + INVOKE_SYSCALL MOVW R4, R13 MOVW $0, R2 BCS error9 @@ -94,7 +101,7 @@ TEXT ·RawSyscall(SB),NOSPLIT,$0-28 MOVW a1+4(FP), R0 // arg 1 MOVW a2+8(FP), R1 // arg 2 MOVW a3+12(FP), R2 // arg 3 - SWI $0 + INVOKE_SYSCALL MOVW $0, R2 BCS errorr MOVW R0, r1+16(FP) // ret 1 @@ -116,7 +123,7 @@ TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 MOVW a4+16(FP), R3 // arg 4 MOVW R13, R4 MOVW $a5+20(FP), R13 // arg 5 to arg 6 are passed on stack - SWI $0 + INVOKE_SYSCALL MOVW R4, R13 MOVW $0, R2 BCS errorr6 diff --git a/src/syscall/asm_openbsd_arm64.s b/src/syscall/asm_openbsd_arm64.s index 16be5fb854..dcbed10cbe 100644 --- a/src/syscall/asm_openbsd_arm64.s +++ b/src/syscall/asm_openbsd_arm64.s @@ -4,6 +4,12 @@ #include "textflag.h" +// See comment in runtime/sys_openbsd_arm64.s re this construction. +#define INVOKE_SYSCALL \ + SVC; \ + NOOP; \ + NOOP + // func Syscall(trap int64, a1, a2, a3 int64) (r1, r2, err int64); TEXT ·Syscall(SB),NOSPLIT,$0-56 BL runtime·entersyscall(SB) @@ -14,7 +20,7 @@ TEXT ·Syscall(SB),NOSPLIT,$0-56 MOVD $0, R4 MOVD $0, R5 MOVD trap+0(FP), R8 // syscall number - SVC + INVOKE_SYSCALL BCC ok MOVD $-1, R4 MOVD R4, r1+32(FP) // r1 @@ -38,7 +44,7 @@ TEXT ·Syscall6(SB),NOSPLIT,$0-80 MOVD a5+40(FP), R4 MOVD a6+48(FP), R5 MOVD trap+0(FP), R8 // syscall number - SVC + INVOKE_SYSCALL BCC ok MOVD $-1, R4 MOVD R4, r1+56(FP) // r1 @@ -66,7 +72,7 @@ TEXT ·Syscall9(SB),NOSPLIT,$0-104 MOVD a9+72(FP), R8 // on stack MOVD R8, 8(RSP) MOVD num+0(FP), R8 // syscall number - SVC + INVOKE_SYSCALL BCC ok MOVD $-1, R4 MOVD R4, r1+80(FP) // r1 @@ -89,7 +95,7 @@ TEXT ·RawSyscall(SB),NOSPLIT,$0-56 MOVD $0, R4 MOVD $0, R5 MOVD trap+0(FP), R8 // syscall number - SVC + INVOKE_SYSCALL BCC ok MOVD $-1, R4 MOVD R4, r1+32(FP) // r1 @@ -110,7 +116,7 @@ TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 MOVD a5+40(FP), R4 MOVD a6+48(FP), R5 MOVD trap+0(FP), R8 // syscall number - SVC + INVOKE_SYSCALL BCC ok MOVD $-1, R4 MOVD R4, r1+56(FP) // r1 diff --git a/src/syscall/js/func.go b/src/syscall/js/func.go index 9e99027e9f..da4cf68774 100644 --- a/src/syscall/js/func.go +++ b/src/syscall/js/func.go @@ -39,7 +39,7 @@ type Func struct { // immediate deadlock. Therefore a blocking function should explicitly start a // new goroutine. // -// Func.Release must be called to free up resources when the function will not be used any more. +// Func.Release must be called to free up resources when the function will not be invoked any more. func FuncOf(fn func(this Value, args []Value) interface{}) Func { funcsMu.Lock() id := nextFuncID @@ -54,6 +54,7 @@ func FuncOf(fn func(this Value, args []Value) interface{}) Func { // Release frees up resources allocated for the function. // The function must not be invoked after calling Release. +// It is allowed to call Release while the function is still running. func (c Func) Release() { funcsMu.Lock() delete(funcs, c.id) diff --git a/src/syscall/syscall_dup2_linux.go b/src/syscall/syscall_dup2_linux.go new file mode 100644 index 0000000000..f03a923112 --- /dev/null +++ b/src/syscall/syscall_dup2_linux.go @@ -0,0 +1,10 @@ +// Copyright 2020 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. + +// +build !android +// +build 386 amd64 arm mips mipsle mips64 mips64le ppc64 ppc64le s390x + +package syscall + +const _SYS_dup = SYS_DUP2 diff --git a/src/syscall/syscall_dup3_linux.go b/src/syscall/syscall_dup3_linux.go new file mode 100644 index 0000000000..1ebdcb20a2 --- /dev/null +++ b/src/syscall/syscall_dup3_linux.go @@ -0,0 +1,9 @@ +// Copyright 2020 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. + +// +build android arm64 riscv64 + +package syscall + +const _SYS_dup = SYS_DUP3 diff --git a/src/syscall/syscall_linux_386.go b/src/syscall/syscall_linux_386.go index 3c1e6e4218..5076dd97ab 100644 --- a/src/syscall/syscall_linux_386.go +++ b/src/syscall/syscall_linux_386.go @@ -9,10 +9,7 @@ package syscall import "unsafe" -const ( - _SYS_dup = SYS_DUP2 - _SYS_setgroups = SYS_SETGROUPS32 -) +const _SYS_setgroups = SYS_SETGROUPS32 func setTimespec(sec, nsec int64) Timespec { return Timespec{Sec: int32(sec), Nsec: int32(nsec)} diff --git a/src/syscall/syscall_linux_amd64.go b/src/syscall/syscall_linux_amd64.go index 0f28b55d47..bf340d9996 100644 --- a/src/syscall/syscall_linux_amd64.go +++ b/src/syscall/syscall_linux_amd64.go @@ -4,10 +4,7 @@ package syscall -const ( - _SYS_dup = SYS_DUP2 - _SYS_setgroups = SYS_SETGROUPS -) +const _SYS_setgroups = SYS_SETGROUPS //sys Dup2(oldfd int, newfd int) (err error) //sysnb EpollCreate(size int) (fd int, err error) diff --git a/src/syscall/syscall_linux_arm.go b/src/syscall/syscall_linux_arm.go index d346029a1f..c4c403a400 100644 --- a/src/syscall/syscall_linux_arm.go +++ b/src/syscall/syscall_linux_arm.go @@ -6,10 +6,7 @@ package syscall import "unsafe" -const ( - _SYS_dup = SYS_DUP2 - _SYS_setgroups = SYS_SETGROUPS32 -) +const _SYS_setgroups = SYS_SETGROUPS32 func setTimespec(sec, nsec int64) Timespec { return Timespec{Sec: int32(sec), Nsec: int32(nsec)} diff --git a/src/syscall/syscall_linux_arm64.go b/src/syscall/syscall_linux_arm64.go index 1ad9dd8ea3..61014b264a 100644 --- a/src/syscall/syscall_linux_arm64.go +++ b/src/syscall/syscall_linux_arm64.go @@ -6,10 +6,7 @@ package syscall import "unsafe" -const ( - _SYS_dup = SYS_DUP3 - _SYS_setgroups = SYS_SETGROUPS -) +const _SYS_setgroups = SYS_SETGROUPS func EpollCreate(size int) (fd int, err error) { if size <= 0 { diff --git a/src/syscall/syscall_linux_mips64x.go b/src/syscall/syscall_linux_mips64x.go index 157c32326b..e3683def67 100644 --- a/src/syscall/syscall_linux_mips64x.go +++ b/src/syscall/syscall_linux_mips64x.go @@ -7,10 +7,7 @@ package syscall -const ( - _SYS_dup = SYS_DUP2 - _SYS_setgroups = SYS_SETGROUPS -) +const _SYS_setgroups = SYS_SETGROUPS //sys Dup2(oldfd int, newfd int) (err error) //sysnb EpollCreate(size int) (fd int, err error) diff --git a/src/syscall/syscall_linux_mipsx.go b/src/syscall/syscall_linux_mipsx.go index f2fea71aac..cbe2f0233f 100644 --- a/src/syscall/syscall_linux_mipsx.go +++ b/src/syscall/syscall_linux_mipsx.go @@ -9,10 +9,7 @@ package syscall import "unsafe" -const ( - _SYS_dup = SYS_DUP2 - _SYS_setgroups = SYS_SETGROUPS -) +const _SYS_setgroups = SYS_SETGROUPS func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno) diff --git a/src/syscall/syscall_linux_ppc64x.go b/src/syscall/syscall_linux_ppc64x.go index 22d6e56010..ba52e5a3ac 100644 --- a/src/syscall/syscall_linux_ppc64x.go +++ b/src/syscall/syscall_linux_ppc64x.go @@ -7,10 +7,7 @@ package syscall -const ( - _SYS_dup = SYS_DUP2 - _SYS_setgroups = SYS_SETGROUPS -) +const _SYS_setgroups = SYS_SETGROUPS //sys Dup2(oldfd int, newfd int) (err error) //sysnb EpollCreate(size int) (fd int, err error) diff --git a/src/syscall/syscall_linux_riscv64.go b/src/syscall/syscall_linux_riscv64.go index 61e9c60e70..d54bd38510 100644 --- a/src/syscall/syscall_linux_riscv64.go +++ b/src/syscall/syscall_linux_riscv64.go @@ -6,10 +6,7 @@ package syscall import "unsafe" -const ( - _SYS_dup = SYS_DUP3 - _SYS_setgroups = SYS_SETGROUPS -) +const _SYS_setgroups = SYS_SETGROUPS func EpollCreate(size int) (fd int, err error) { if size <= 0 { diff --git a/src/syscall/syscall_linux_s390x.go b/src/syscall/syscall_linux_s390x.go index fcedf5909a..80cb1ccc19 100644 --- a/src/syscall/syscall_linux_s390x.go +++ b/src/syscall/syscall_linux_s390x.go @@ -6,10 +6,7 @@ package syscall import "unsafe" -const ( - _SYS_dup = SYS_DUP2 - _SYS_setgroups = SYS_SETGROUPS -) +const _SYS_setgroups = SYS_SETGROUPS //sys Dup2(oldfd int, newfd int) (err error) //sysnb EpollCreate(size int) (fd int, err error) diff --git a/src/testing/benchmark.go b/src/testing/benchmark.go index 88ba0f0242..52766005bf 100644 --- a/src/testing/benchmark.go +++ b/src/testing/benchmark.go @@ -526,6 +526,7 @@ func runBenchmarks(importPath string, matchString func(pat, str string) (bool, e name: "Main", w: os.Stdout, chatty: *chatty, + bench: true, }, importPath: importPath, benchFunc: func(b *B) { @@ -559,6 +560,7 @@ func (ctx *benchContext) processBench(b *B) { name: b.name, w: b.w, chatty: b.chatty, + bench: true, }, benchFunc: b.benchFunc, benchTime: b.benchTime, @@ -624,6 +626,7 @@ func (b *B) Run(name string, f func(b *B)) bool { creator: pc[:n], w: b.w, chatty: b.chatty, + bench: true, }, importPath: b.importPath, benchFunc: f, diff --git a/src/testing/sub_test.go b/src/testing/sub_test.go index 95f8220f81..8eb0084b1c 100644 --- a/src/testing/sub_test.go +++ b/src/testing/sub_test.go @@ -438,8 +438,6 @@ func TestTRun(t *T) { }, { // A chatty test should always log with fmt.Print, even if the // parent test has completed. - // TODO(deklerk) Capture the log of fmt.Print and assert that the - // subtest message is not lost. desc: "log in finished sub test with chatty", ok: false, chatty: true, @@ -477,35 +475,37 @@ func TestTRun(t *T) { }, }} for _, tc := range testCases { - ctx := newTestContext(tc.maxPar, newMatcher(regexp.MatchString, "", "")) - buf := &bytes.Buffer{} - root := &T{ - common: common{ - signal: make(chan bool), - name: "Test", - w: buf, - chatty: tc.chatty, - }, - context: ctx, - } - ok := root.Run(tc.desc, tc.f) - ctx.release() + t.Run(tc.desc, func(t *T) { + ctx := newTestContext(tc.maxPar, newMatcher(regexp.MatchString, "", "")) + buf := &bytes.Buffer{} + root := &T{ + common: common{ + signal: make(chan bool), + name: "Test", + w: buf, + chatty: tc.chatty, + }, + context: ctx, + } + ok := root.Run(tc.desc, tc.f) + ctx.release() - if ok != tc.ok { - t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, tc.ok) - } - if ok != !root.Failed() { - t.Errorf("%s:root failed: got %v; want %v", tc.desc, !ok, root.Failed()) - } - if ctx.running != 0 || ctx.numWaiting != 0 { - t.Errorf("%s:running and waiting non-zero: got %d and %d", tc.desc, ctx.running, ctx.numWaiting) - } - got := strings.TrimSpace(buf.String()) - want := strings.TrimSpace(tc.output) - re := makeRegexp(want) - if ok, err := regexp.MatchString(re, got); !ok || err != nil { - t.Errorf("%s:output:\ngot:\n%s\nwant:\n%s", tc.desc, got, want) - } + if ok != tc.ok { + t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, tc.ok) + } + if ok != !root.Failed() { + t.Errorf("%s:root failed: got %v; want %v", tc.desc, !ok, root.Failed()) + } + if ctx.running != 0 || ctx.numWaiting != 0 { + t.Errorf("%s:running and waiting non-zero: got %d and %d", tc.desc, ctx.running, ctx.numWaiting) + } + got := strings.TrimSpace(buf.String()) + want := strings.TrimSpace(tc.output) + re := makeRegexp(want) + if ok, err := regexp.MatchString(re, got); !ok || err != nil { + t.Errorf("%s:output:\ngot:\n%s\nwant:\n%s", tc.desc, got, want) + } + }) } } @@ -655,43 +655,45 @@ func TestBRun(t *T) { }, }} for _, tc := range testCases { - var ok bool - buf := &bytes.Buffer{} - // This is almost like the Benchmark function, except that we override - // the benchtime and catch the failure result of the subbenchmark. - root := &B{ - common: common{ - signal: make(chan bool), - name: "root", - w: buf, - chatty: tc.chatty, - }, - benchFunc: func(b *B) { ok = b.Run("test", tc.f) }, // Use Run to catch failure. - benchTime: benchTimeFlag{d: 1 * time.Microsecond}, - } - root.runN(1) - if ok != !tc.failed { - t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, !tc.failed) - } - if !ok != root.Failed() { - t.Errorf("%s:root failed: got %v; want %v", tc.desc, !ok, root.Failed()) - } - // All tests are run as subtests - if root.result.N != 1 { - t.Errorf("%s: N for parent benchmark was %d; want 1", tc.desc, root.result.N) - } - got := strings.TrimSpace(buf.String()) - want := strings.TrimSpace(tc.output) - re := makeRegexp(want) - if ok, err := regexp.MatchString(re, got); !ok || err != nil { - t.Errorf("%s:output:\ngot:\n%s\nwant:\n%s", tc.desc, got, want) - } + t.Run(tc.desc, func(t *T) { + var ok bool + buf := &bytes.Buffer{} + // This is almost like the Benchmark function, except that we override + // the benchtime and catch the failure result of the subbenchmark. + root := &B{ + common: common{ + signal: make(chan bool), + name: "root", + w: buf, + chatty: tc.chatty, + }, + benchFunc: func(b *B) { ok = b.Run("test", tc.f) }, // Use Run to catch failure. + benchTime: benchTimeFlag{d: 1 * time.Microsecond}, + } + root.runN(1) + if ok != !tc.failed { + t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, !tc.failed) + } + if !ok != root.Failed() { + t.Errorf("%s:root failed: got %v; want %v", tc.desc, !ok, root.Failed()) + } + // All tests are run as subtests + if root.result.N != 1 { + t.Errorf("%s: N for parent benchmark was %d; want 1", tc.desc, root.result.N) + } + got := strings.TrimSpace(buf.String()) + want := strings.TrimSpace(tc.output) + re := makeRegexp(want) + if ok, err := regexp.MatchString(re, got); !ok || err != nil { + t.Errorf("%s:output:\ngot:\n%s\nwant:\n%s", tc.desc, got, want) + } + }) } } func makeRegexp(s string) string { s = regexp.QuoteMeta(s) - s = strings.ReplaceAll(s, ":NNN:", `:\d\d\d:`) + s = strings.ReplaceAll(s, ":NNN:", `:\d\d\d\d?:`) s = strings.ReplaceAll(s, "N\\.NNs", `\d*\.\d*s`) return s } diff --git a/src/testing/testing.go b/src/testing/testing.go index 608bb39671..4a14d49a91 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -325,6 +325,7 @@ var ( cpuListStr *string parallel *int testlog *string + printer *testPrinter haveExamples bool // are there examples? @@ -334,6 +335,49 @@ var ( numFailed uint32 // number of test failures ) +type testPrinter struct { + chatty bool + + lastNameMu sync.Mutex // guards lastName + lastName string // last printed test name in chatty mode +} + +func newTestPrinter(chatty bool) *testPrinter { + return &testPrinter{ + chatty: chatty, + } +} + +func (p *testPrinter) Print(testName, out string) { + p.Fprint(os.Stdout, testName, out) +} + +func (p *testPrinter) Fprint(w io.Writer, testName, out string) { + if !p.chatty || strings.HasPrefix(out, "--- PASS") || strings.HasPrefix(out, "--- FAIL") { + fmt.Fprint(w, out) + return + } + + p.lastNameMu.Lock() + defer p.lastNameMu.Unlock() + + if strings.HasPrefix(out, "=== CONT") || strings.HasPrefix(out, "=== RUN") { + p.lastName = testName + fmt.Fprint(w, out) + return + } + + if p.lastName == "" { + p.lastName = testName + } else if p.lastName != testName { + // Always printed as-is, with 0 decoration or indentation. So, we skip + // printing to w. + fmt.Printf("=== CONT %s\n", testName) + p.lastName = testName + } + fmt.Fprint(w, out) +} + // The maximum number of stack frames to go through when skipping helper functions for // the purpose of decorating log messages. const maxStackLen = 50 @@ -354,10 +398,11 @@ type common struct { cleanupPc []uintptr // The stack trace at the point where Cleanup was called. chatty bool // A copy of the chatty flag. + bench bool // Whether the current test is a benchmark. finished bool // Test function has completed. - hasSub int32 // written atomically - raceErrors int // number of races detected during test - runner string // function name of tRunner running the test + hasSub int32 // Written atomically. + raceErrors int // Number of races detected during test. + runner string // Function name of tRunner running the test. parent *common level int // Nesting depth of test or benchmark. @@ -496,9 +541,6 @@ func (c *common) decorate(s string, skip int) string { buf := new(strings.Builder) // Every line is indented at least 4 spaces. buf.WriteString(" ") - if c.chatty { - fmt.Fprintf(buf, "%s: ", c.name) - } fmt.Fprintf(buf, "%s:%d: ", file, line) lines := strings.Split(s, "\n") if l := len(lines); l > 1 && lines[l-1] == "" { @@ -517,12 +559,12 @@ func (c *common) decorate(s string, skip int) string { // flushToParent writes c.output to the parent after first writing the header // with the given format and arguments. -func (c *common) flushToParent(format string, args ...interface{}) { +func (c *common) flushToParent(testName, format string, args ...interface{}) { p := c.parent p.mu.Lock() defer p.mu.Unlock() - fmt.Fprintf(p.w, format, args...) + printer.Fprint(p.w, testName, fmt.Sprintf(format, args...)) c.mu.Lock() defer c.mu.Unlock() @@ -697,7 +739,14 @@ func (c *common) logDepth(s string, depth int) { panic("Log in goroutine after " + c.name + " has completed") } else { if c.chatty { - fmt.Print(c.decorate(s, depth+1)) + if c.bench { + // Benchmarks don't print === CONT, so we should skip the test + // printer and just print straight to stdout. + fmt.Print(c.decorate(s, depth+1)) + } else { + printer.Print(c.name, c.decorate(s, depth+1)) + } + return } c.output = append(c.output, c.decorate(s, depth+1)...) @@ -942,7 +991,7 @@ func (t *T) Parallel() { for ; root.parent != nil; root = root.parent { } root.mu.Lock() - fmt.Fprintf(root.w, "=== CONT %s\n", t.name) + printer.Fprint(root.w, t.name, fmt.Sprintf("=== CONT %s\n", t.name)) root.mu.Unlock() } @@ -1001,7 +1050,7 @@ func tRunner(t *T, fn func(t *T)) { root.duration += time.Since(root.start) d := root.duration root.mu.Unlock() - root.flushToParent("--- FAIL: %s (%s)\n", root.name, fmtDuration(d)) + root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d)) if r := root.parent.runCleanup(recoverAndReturnPanic); r != nil { fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r) } @@ -1100,7 +1149,7 @@ func (t *T) Run(name string, f func(t *T)) bool { for ; root.parent != nil; root = root.parent { } root.mu.Lock() - fmt.Fprintf(root.w, "=== RUN %s\n", t.name) + printer.Fprint(root.w, t.name, fmt.Sprintf("=== RUN %s\n", t.name)) root.mu.Unlock() } // Instead of reducing the running count of this test before calling the @@ -1266,6 +1315,8 @@ func (m *M) Run() (code int) { flag.Parse() } + printer = newTestPrinter(Verbose()) + if *parallel < 1 { fmt.Fprintln(os.Stderr, "testing: -parallel can only be given a positive integer") flag.Usage() @@ -1309,12 +1360,12 @@ func (t *T) report() { dstr := fmtDuration(t.duration) format := "--- %s: %s (%s)\n" if t.Failed() { - t.flushToParent(format, "FAIL", t.name, dstr) + t.flushToParent(t.name, format, "FAIL", t.name, dstr) } else if t.chatty { if t.Skipped() { - t.flushToParent(format, "SKIP", t.name, dstr) + t.flushToParent(t.name, format, "SKIP", t.name, dstr) } else { - t.flushToParent(format, "PASS", t.name, dstr) + t.flushToParent(t.name, format, "PASS", t.name, dstr) } } } diff --git a/src/text/template/link_test.go b/src/text/template/link_test.go index b7415d29bb..4eac7e6755 100644 --- a/src/text/template/link_test.go +++ b/src/text/template/link_test.go @@ -49,7 +49,7 @@ func main() { if err := ioutil.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0644); err != nil { t.Fatal(err) } - cmd := exec.Command("go", "build", "-o", "x.exe", "x.go") + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "x.exe", "x.go") cmd.Dir = td if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("go build: %v, %s", err, out) diff --git a/src/time/example_test.go b/src/time/example_test.go index 15811a62d3..0afb18aba6 100644 --- a/src/time/example_test.go +++ b/src/time/example_test.go @@ -50,10 +50,11 @@ func ExampleDuration_Round() { } func ExampleDuration_String() { - t1 := time.Date(2016, time.August, 15, 0, 0, 0, 0, time.UTC) - t2 := time.Date(2017, time.February, 16, 0, 0, 0, 0, time.UTC) - fmt.Println(t2.Sub(t1).String()) - // Output: 4440h0m0s + fmt.Println(1*time.Hour + 2*time.Minute + 300*time.Millisecond) + fmt.Println(300 * time.Millisecond) + // Output: + // 1h2m0.3s + // 300ms } func ExampleDuration_Truncate() { diff --git a/test/codegen/comparisons.go b/test/codegen/comparisons.go index c020ea8eb7..eb2f3317c9 100644 --- a/test/codegen/comparisons.go +++ b/test/codegen/comparisons.go @@ -248,3 +248,139 @@ func CmpLogicalToZero(a, b, c uint32, d, e uint64) uint64 { } return 0 } + +// The following CmpToZero_ex* check that cmp|cmn with bmi|bpl are generated for +// 'comparing to zero' expressions + +// var + const +func CmpToZero_ex1(a int64, e int32) int { + // arm64:`CMN`,-`ADD`,`(BMI|BPL)` + if a+3 < 0 { + return 1 + } + + // arm64:`CMN`,-`ADD`,`BEQ`,`(BMI|BPL)` + if a+5 <= 0 { + return 1 + } + + // arm64:`CMN`,-`ADD`,`(BMI|BPL)` + if a+13 >= 0 { + return 2 + } + + // arm64:`CMP`,-`SUB`,`(BMI|BPL)` + if a-7 < 0 { + return 3 + } + + // arm64:`CMP`,-`SUB`,`(BMI|BPL)` + if a-11 >= 0 { + return 4 + } + + // arm64:`CMP`,-`SUB`,`BEQ`,`(BMI|BPL)` + if a-19 > 0 { + return 4 + } + + // arm64:`CMNW`,-`ADDW`,`(BMI|BPL)` + if e+3 < 0 { + return 5 + } + + // arm64:`CMNW`,-`ADDW`,`(BMI|BPL)` + if e+13 >= 0 { + return 6 + } + + // arm64:`CMPW`,-`SUBW`,`(BMI|BPL)` + if e-7 < 0 { + return 7 + } + + // arm64:`CMPW`,-`SUBW`,`(BMI|BPL)` + if e-11 >= 0 { + return 8 + } + + return 0 +} + +// var + var +// TODO: optimize 'var - var' +func CmpToZero_ex2(a, b, c int64, e, f, g int32) int { + // arm64:`CMN`,-`ADD`,`(BMI|BPL)` + if a+b < 0 { + return 1 + } + + // arm64:`CMN`,-`ADD`,`BEQ`,`(BMI|BPL)` + if a+c <= 0 { + return 1 + } + + // arm64:`CMN`,-`ADD`,`(BMI|BPL)` + if b+c >= 0 { + return 2 + } + + // arm64:`CMNW`,-`ADDW`,`(BMI|BPL)` + if e+f < 0 { + return 5 + } + + // arm64:`CMNW`,-`ADDW`,`(BMI|BPL)` + if f+g >= 0 { + return 6 + } + return 0 +} + +// var + var*var +func CmpToZero_ex3(a, b, c, d int64, e, f, g, h int32) int { + // arm64:`CMN`,-`MADD`,`MUL`,`(BMI|BPL)` + if a+b*c < 0 { + return 1 + } + + // arm64:`CMN`,-`MADD`,`MUL`,`(BMI|BPL)` + if b+c*d >= 0 { + return 2 + } + + // arm64:`CMNW`,-`MADDW`,`MULW`,`BEQ`,`(BMI|BPL)` + if e+f*g > 0 { + return 5 + } + + // arm64:`CMNW`,-`MADDW`,`MULW`,`BEQ`,`(BMI|BPL)` + if f+g*h <= 0 { + return 6 + } + return 0 +} + +// var - var*var +func CmpToZero_ex4(a, b, c, d int64, e, f, g, h int32) int { + // arm64:`CMP`,-`MSUB`,`MUL`,`BEQ`,`(BMI|BPL)` + if a-b*c > 0 { + return 1 + } + + // arm64:`CMP`,-`MSUB`,`MUL`,`(BMI|BPL)` + if b-c*d >= 0 { + return 2 + } + + // arm64:`CMPW`,-`MSUBW`,`MULW`,`(BMI|BPL)` + if e-f*g < 0 { + return 5 + } + + // arm64:`CMPW`,-`MSUBW`,`MULW`,`(BMI|BPL)` + if f-g*h >= 0 { + return 6 + } + return 0 +}