Updates #59488.
Change-Id: I254da7cca071eeb5af2f8aecdcd9461703fe8677
Reviewed-on: https://go-review.googlesource.com/c/go/+/496257
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Auto-Submit: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Keith Randall <khr@google.com>
argument(e.discardHole(), &call.X)
argument(e.discardHole(), &call.Y)
- case ir.ODELETE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER:
+ case ir.ODELETE, ir.OMAX, ir.OMIN, ir.OPRINT, ir.OPRINTN, ir.ORECOVER:
call := call.(*ir.CallExpr)
fixRecoverCall(call)
for i := range call.Args {
e.discard(n.X)
case ir.OCALLMETH, ir.OCALLFUNC, ir.OCALLINTER, ir.OINLCALL,
- ir.OLEN, ir.OCAP, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCOPY, ir.ORECOVER,
+ ir.OLEN, ir.OCAP, ir.OMIN, ir.OMAX, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCOPY, ir.ORECOVER,
ir.OUNSAFEADD, ir.OUNSAFESLICE, ir.OUNSAFESTRING, ir.OUNSAFESTRINGDATA, ir.OUNSAFESLICEDATA:
e.call([]hole{k}, n)
OCALL, OCALLFUNC, OCALLINTER, OCALLMETH,
ODELETE,
OGETG, OGETCALLERPC, OGETCALLERSP,
- OMAKE, OPRINT, OPRINTN,
+ OMAKE, OMAX, OMIN, OPRINT, OPRINTN,
ORECOVER, ORECOVERFP:
n.op = op
}
OLT: "<",
OMAKE: "make",
ONEG: "-",
+ OMAX: "max",
+ OMIN: "min",
OMOD: "%",
OMUL: "*",
ONEW: "new",
OMAKESLICECOPY: 8,
OMAKE: 8,
OMAPLIT: 8,
+ OMAX: 8,
+ OMIN: 8,
ONAME: 8,
ONEW: 8,
ONIL: 8,
case OAPPEND,
ODELETE,
OMAKE,
+ OMAX,
+ OMIN,
ORECOVER,
OPRINT,
OPRINTN:
ORECV // <-X
ORUNESTR // Type(X) (Type is string, X is rune)
OSELRECV2 // like OAS2: Lhs = Rhs where len(Lhs)=2, len(Rhs)=1, Rhs[0].Op = ORECV (appears as .Var of OCASE)
+ OMIN // min(List)
+ OMAX // max(List)
OREAL // real(X)
OIMAG // imag(X)
OCOMPLEX // complex(X, Y)
_ = x[ORECV-104]
_ = x[ORUNESTR-105]
_ = x[OSELRECV2-106]
- _ = x[OREAL-107]
- _ = x[OIMAG-108]
- _ = x[OCOMPLEX-109]
- _ = x[OALIGNOF-110]
- _ = x[OOFFSETOF-111]
- _ = x[OSIZEOF-112]
- _ = x[OUNSAFEADD-113]
- _ = x[OUNSAFESLICE-114]
- _ = x[OUNSAFESLICEDATA-115]
- _ = x[OUNSAFESTRING-116]
- _ = x[OUNSAFESTRINGDATA-117]
- _ = x[OMETHEXPR-118]
- _ = x[OMETHVALUE-119]
- _ = x[OBLOCK-120]
- _ = x[OBREAK-121]
- _ = x[OCASE-122]
- _ = x[OCONTINUE-123]
- _ = x[ODEFER-124]
- _ = x[OFALL-125]
- _ = x[OFOR-126]
- _ = x[OGOTO-127]
- _ = x[OIF-128]
- _ = x[OLABEL-129]
- _ = x[OGO-130]
- _ = x[ORANGE-131]
- _ = x[ORETURN-132]
- _ = x[OSELECT-133]
- _ = x[OSWITCH-134]
- _ = x[OTYPESW-135]
- _ = x[OFUNCINST-136]
- _ = x[OINLCALL-137]
- _ = x[OEFACE-138]
- _ = x[OITAB-139]
- _ = x[OIDATA-140]
- _ = x[OSPTR-141]
- _ = x[OCFUNC-142]
- _ = x[OCHECKNIL-143]
- _ = x[ORESULT-144]
- _ = x[OINLMARK-145]
- _ = x[OLINKSYMOFFSET-146]
- _ = x[OJUMPTABLE-147]
- _ = x[ODYNAMICDOTTYPE-148]
- _ = x[ODYNAMICDOTTYPE2-149]
- _ = x[ODYNAMICTYPE-150]
- _ = x[OTAILCALL-151]
- _ = x[OGETG-152]
- _ = x[OGETCALLERPC-153]
- _ = x[OGETCALLERSP-154]
- _ = x[OEND-155]
+ _ = x[OMIN-107]
+ _ = x[OMAX-108]
+ _ = x[OREAL-109]
+ _ = x[OIMAG-110]
+ _ = x[OCOMPLEX-111]
+ _ = x[OALIGNOF-112]
+ _ = x[OOFFSETOF-113]
+ _ = x[OSIZEOF-114]
+ _ = x[OUNSAFEADD-115]
+ _ = x[OUNSAFESLICE-116]
+ _ = x[OUNSAFESLICEDATA-117]
+ _ = x[OUNSAFESTRING-118]
+ _ = x[OUNSAFESTRINGDATA-119]
+ _ = x[OMETHEXPR-120]
+ _ = x[OMETHVALUE-121]
+ _ = x[OBLOCK-122]
+ _ = x[OBREAK-123]
+ _ = x[OCASE-124]
+ _ = x[OCONTINUE-125]
+ _ = x[ODEFER-126]
+ _ = x[OFALL-127]
+ _ = x[OFOR-128]
+ _ = x[OGOTO-129]
+ _ = x[OIF-130]
+ _ = x[OLABEL-131]
+ _ = x[OGO-132]
+ _ = x[ORANGE-133]
+ _ = x[ORETURN-134]
+ _ = x[OSELECT-135]
+ _ = x[OSWITCH-136]
+ _ = x[OTYPESW-137]
+ _ = x[OFUNCINST-138]
+ _ = x[OINLCALL-139]
+ _ = x[OEFACE-140]
+ _ = x[OITAB-141]
+ _ = x[OIDATA-142]
+ _ = x[OSPTR-143]
+ _ = x[OCFUNC-144]
+ _ = x[OCHECKNIL-145]
+ _ = x[ORESULT-146]
+ _ = x[OINLMARK-147]
+ _ = x[OLINKSYMOFFSET-148]
+ _ = x[OJUMPTABLE-149]
+ _ = x[ODYNAMICDOTTYPE-150]
+ _ = x[ODYNAMICDOTTYPE2-151]
+ _ = x[ODYNAMICTYPE-152]
+ _ = x[OTAILCALL-153]
+ _ = x[OGETG-154]
+ _ = x[OGETCALLERPC-155]
+ _ = x[OGETCALLERSP-156]
+ _ = x[OEND-157]
}
-const _Op_name = "XXXNAMENONAMETYPELITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESSLICE2ARRSLICE2ARRPTRASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCAPCLEARCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVIDATACONVNOPCOPYDCLDCLFUNCDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNOTBITNOTPLUSNEGORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERSTRINGHEADERRECOVERRECOVERFPRECVRUNESTRSELRECV2REALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFUNSAFEADDUNSAFESLICEUNSAFESLICEDATAUNSAFESTRINGUNSAFESTRINGDATAMETHEXPRMETHVALUEBLOCKBREAKCASECONTINUEDEFERFALLFORGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWFUNCINSTINLCALLEFACEITABIDATASPTRCFUNCCHECKNILRESULTINLMARKLINKSYMOFFSETJUMPTABLEDYNAMICDOTTYPEDYNAMICDOTTYPE2DYNAMICTYPETAILCALLGETGGETCALLERPCGETCALLERSPEND"
+const _Op_name = "XXXNAMENONAMETYPELITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESSLICE2ARRSLICE2ARRPTRASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCAPCLEARCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVIDATACONVNOPCOPYDCLDCLFUNCDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNOTBITNOTPLUSNEGORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERSTRINGHEADERRECOVERRECOVERFPRECVRUNESTRSELRECV2MINMAXREALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFUNSAFEADDUNSAFESLICEUNSAFESLICEDATAUNSAFESTRINGUNSAFESTRINGDATAMETHEXPRMETHVALUEBLOCKBREAKCASECONTINUEDEFERFALLFORGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWFUNCINSTINLCALLEFACEITABIDATASPTRCFUNCCHECKNILRESULTINLMARKLINKSYMOFFSETJUMPTABLEDYNAMICDOTTYPEDYNAMICDOTTYPE2DYNAMICTYPETAILCALLGETGGETCALLERPCGETCALLERSPEND"
-var _Op_index = [...]uint16{0, 3, 7, 13, 17, 24, 27, 30, 33, 35, 38, 44, 48, 54, 60, 69, 81, 90, 99, 111, 120, 129, 141, 143, 146, 156, 163, 170, 177, 181, 185, 193, 201, 210, 213, 218, 223, 230, 237, 243, 252, 260, 268, 274, 278, 287, 296, 303, 307, 310, 317, 325, 332, 338, 341, 347, 354, 362, 366, 373, 381, 383, 385, 387, 389, 391, 393, 398, 403, 411, 414, 423, 426, 430, 438, 445, 454, 467, 470, 473, 476, 479, 482, 485, 491, 494, 497, 503, 507, 510, 514, 519, 524, 530, 535, 539, 544, 552, 560, 566, 575, 586, 598, 605, 614, 618, 625, 633, 637, 641, 648, 655, 663, 669, 678, 689, 704, 716, 732, 740, 749, 754, 759, 763, 771, 776, 780, 783, 787, 789, 794, 796, 801, 807, 813, 819, 825, 833, 840, 845, 849, 854, 858, 863, 871, 877, 884, 897, 906, 920, 935, 946, 954, 958, 969, 980, 983}
+var _Op_index = [...]uint16{0, 3, 7, 13, 17, 24, 27, 30, 33, 35, 38, 44, 48, 54, 60, 69, 81, 90, 99, 111, 120, 129, 141, 143, 146, 156, 163, 170, 177, 181, 185, 193, 201, 210, 213, 218, 223, 230, 237, 243, 252, 260, 268, 274, 278, 287, 296, 303, 307, 310, 317, 325, 332, 338, 341, 347, 354, 362, 366, 373, 381, 383, 385, 387, 389, 391, 393, 398, 403, 411, 414, 423, 426, 430, 438, 445, 454, 467, 470, 473, 476, 479, 482, 485, 491, 494, 497, 503, 507, 510, 514, 519, 524, 530, 535, 539, 544, 552, 560, 566, 575, 586, 598, 605, 614, 618, 625, 633, 636, 639, 643, 647, 654, 661, 669, 675, 684, 695, 710, 722, 738, 746, 755, 760, 765, 769, 777, 782, 786, 789, 793, 795, 800, 802, 807, 813, 819, 825, 831, 839, 846, 851, 855, 860, 864, 869, 877, 883, 890, 903, 912, 926, 941, 952, 960, 964, 975, 986, 989}
func (i Op) String() string {
if i >= Op(len(_Op_index)-1) {
case ir.OAPPEND:
return s.append(n.(*ir.CallExpr), false)
+ case ir.OMIN, ir.OMAX:
+ return s.minMax(n.(*ir.CallExpr))
+
case ir.OSTRUCTLIT, ir.OARRAYLIT:
// All literals with nonzero fields have already been
// rewritten during walk. Any that remain are just T{}
return s.newValue3(ssa.OpSliceMake, n.Type(), p, l, c)
}
+// minMax converts an OMIN/OMAX builtin call into SSA.
+func (s *state) minMax(n *ir.CallExpr) *ssa.Value {
+ // The OMIN/OMAX builtin is variadic, but its semantics are
+ // equivalent to left-folding a binary min/max operation across the
+ // arguments list.
+ fold := func(op func(x, a *ssa.Value) *ssa.Value) *ssa.Value {
+ x := s.expr(n.Args[0])
+ for _, arg := range n.Args[1:] {
+ x = op(x, s.expr(arg))
+ }
+ return x
+ }
+
+ typ := n.Type()
+
+ if typ.IsFloat() || typ.IsString() {
+ // min/max semantics for floats are tricky because of NaNs and
+ // negative zero, so we let the runtime handle this instead.
+ //
+ // Strings are conceptually simpler, but we currently desugar
+ // string comparisons during walk, not ssagen.
+
+ var name string
+ switch typ.Kind() {
+ case types.TFLOAT32:
+ switch n.Op() {
+ case ir.OMIN:
+ name = "fmin32"
+ case ir.OMAX:
+ name = "fmax32"
+ }
+ case types.TFLOAT64:
+ switch n.Op() {
+ case ir.OMIN:
+ name = "fmin64"
+ case ir.OMAX:
+ name = "fmax64"
+ }
+ case types.TSTRING:
+ switch n.Op() {
+ case ir.OMIN:
+ name = "strmin"
+ case ir.OMAX:
+ name = "strmax"
+ }
+ }
+ fn := typecheck.LookupRuntimeFunc(name)
+
+ return fold(func(x, a *ssa.Value) *ssa.Value {
+ return s.rtcall(fn, true, []*types.Type{typ}, x, a)[0]
+ })
+ }
+
+ lt := s.ssaOp(ir.OLT, typ)
+
+ return fold(func(x, a *ssa.Value) *ssa.Value {
+ switch n.Op() {
+ case ir.OMIN:
+ // a < x ? a : x
+ return s.ternary(s.newValue2(lt, types.Types[types.TBOOL], a, x), a, x)
+ case ir.OMAX:
+ // x < a ? a : x
+ return s.ternary(s.newValue2(lt, types.Types[types.TBOOL], x, a), a, x)
+ }
+ panic("unreachable")
+ })
+}
+
+// ternary emits code to evaluate cond ? x : y.
+func (s *state) ternary(cond, x, y *ssa.Value) *ssa.Value {
+ bThen := s.f.NewBlock(ssa.BlockPlain)
+ bElse := s.f.NewBlock(ssa.BlockPlain)
+
+ b := s.endBlock()
+ b.Kind = ssa.BlockIf
+ b.SetControl(cond)
+ b.AddEdgeTo(bThen)
+ b.AddEdgeTo(bElse)
+
+ s.startBlock(bElse)
+ s.endBlock().AddEdgeTo(bThen)
+
+ s.startBlock(bThen)
+ return s.newValue2(ssa.OpPhi, x.Type, x, y)
+}
+
// condBranch evaluates the boolean expression cond and branches to yes
// if cond is true and no if cond is false.
// This function is intended to handle && and || better than just calling
ir.OIMAG,
ir.OLEN,
ir.OMAKE,
+ ir.OMAX,
+ ir.OMIN,
ir.ONEW,
ir.OPANIC,
ir.OPRINT,
default:
base.Fatalf("unknown builtin %v", l)
- case ir.OAPPEND, ir.ODELETE, ir.OMAKE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER:
+ case ir.OAPPEND, ir.ODELETE, ir.OMAKE, ir.OMAX, ir.OMIN, ir.OPRINT, ir.OPRINTN, ir.ORECOVER:
n.SetOp(l.BuiltinOp)
n.X = nil
n.SetTypecheck(0) // re-typechecking new op is OK, not a loop
return n
}
+// tcMinMax typechecks an OMIN or OMAX node.
+func tcMinMax(n *ir.CallExpr) ir.Node {
+ typecheckargs(n)
+ arg0 := n.Args[0]
+ for _, arg := range n.Args[1:] {
+ if !types.Identical(arg.Type(), arg0.Type()) {
+ base.FatalfAt(n.Pos(), "mismatched arguments: %L and %L", arg0, arg)
+ }
+ }
+ n.SetType(arg0.Type())
+ return n
+}
+
// tcRealImag typechecks an OREAL or OIMAG node.
func tcRealImag(n *ir.UnaryExpr) ir.Node {
n.X = Expr(n.X)
ir.OCLOSE,
ir.OCOPY,
ir.ODELETE,
+ ir.OMAX,
+ ir.OMIN,
ir.OPANIC,
ir.OPRINT,
ir.OPRINTN,
isExpr = false
}
}
- case ir.OAPPEND:
- // Must be used (and not BinaryExpr/UnaryExpr).
+ case ir.OAPPEND, ir.OMIN, ir.OMAX:
+ // Must be used.
isStmt = false
case ir.OCLEAR, ir.OCLOSE, ir.ODELETE, ir.OPANIC, ir.OPRINT, ir.OPRINTN:
// Must not be used.
n := n.(*ir.UnaryExpr)
return tcLenCap(n)
+ case ir.OMIN, ir.OMAX:
+ n := n.(*ir.CallExpr)
+ return tcMinMax(n)
+
case ir.OREAL, ir.OIMAG:
n := n.(*ir.UnaryExpr)
return tcRealImag(n)
{"imag", ir.OIMAG},
{"len", ir.OLEN},
{"make", ir.OMAKE},
+ {"max", ir.OMAX},
+ {"min", ir.OMIN},
{"new", ir.ONEW},
{"panic", ir.OPANIC},
{"print", ir.OPRINT},
return n
}
+func walkMinMax(n *ir.CallExpr, init *ir.Nodes) ir.Node {
+ init.Append(ir.TakeInit(n)...)
+ walkExprList(n.Args, init)
+ return n
+}
+
// generate code for print.
func walkPrint(nn *ir.CallExpr, init *ir.Nodes) ir.Node {
// Hoist all the argument evaluation up before the lock.
n := n.(*ir.SelectorExpr)
return n.FuncName()
+ case ir.OMIN, ir.OMAX:
+ n := n.(*ir.CallExpr)
+ return walkMinMax(n, init)
+
case ir.ONOT, ir.ONEG, ir.OPLUS, ir.OBITNOT, ir.OREAL, ir.OIMAG, ir.OSPTR, ir.OITAB, ir.OIDATA:
n := n.(*ir.UnaryExpr)
n.X = walkExpr(n.X, init)
o.out = append(o.out, n)
o.popTemp(t)
- case ir.OPRINT, ir.OPRINTN, ir.ORECOVERFP:
+ case ir.OMAX, ir.OMIN, ir.OPRINT, ir.OPRINTN, ir.ORECOVERFP:
n := n.(*ir.CallExpr)
t := o.markTemp()
o.call(n)
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime
+
+import "unsafe"
+
+func strmin(x, y string) string {
+ if y < x {
+ return y
+ }
+ return x
+}
+
+func strmax(x, y string) string {
+ if y > x {
+ return y
+ }
+ return x
+}
+
+func fmin32(x, y float32) float32 { return fmin(x, y) }
+func fmin64(x, y float64) float64 { return fmin(x, y) }
+func fmax32(x, y float32) float32 { return fmax(x, y) }
+func fmax64(x, y float64) float64 { return fmax(x, y) }
+
+type floaty interface{ ~float32 | ~float64 }
+
+func fmin[F floaty](x, y F) F {
+ if y != y || y < x {
+ return y
+ }
+ if x != x || x < y || x != 0 {
+ return x
+ }
+ // x and y are both ±0
+ // if either is -0, return -0; else return +0
+ return forbits(x, y)
+}
+
+func fmax[F floaty](x, y F) F {
+ if y != y || y > x {
+ return y
+ }
+ if x != x || x > y || x != 0 {
+ return x
+ }
+ // x and y are both ±0
+ // if both are -0, return -0; else return +0
+ return fandbits(x, y)
+}
+
+func forbits[F floaty](x, y F) F {
+ switch unsafe.Sizeof(x) {
+ case 4:
+ *(*uint32)(unsafe.Pointer(&x)) |= *(*uint32)(unsafe.Pointer(&y))
+ case 8:
+ *(*uint64)(unsafe.Pointer(&x)) |= *(*uint64)(unsafe.Pointer(&y))
+ }
+ return x
+}
+
+func fandbits[F floaty](x, y F) F {
+ switch unsafe.Sizeof(x) {
+ case 4:
+ *(*uint32)(unsafe.Pointer(&x)) &= *(*uint32)(unsafe.Pointer(&y))
+ case 8:
+ *(*uint64)(unsafe.Pointer(&x)) &= *(*uint64)(unsafe.Pointer(&y))
+ }
+ return x
+}
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime_test
+
+import (
+ "math"
+ "strings"
+ "testing"
+ "unsafe"
+)
+
+var (
+ zero = math.Copysign(0, +1)
+ negZero = math.Copysign(0, -1)
+ inf = math.Inf(+1)
+ negInf = math.Inf(-1)
+ nan = math.NaN()
+)
+
+var tests = []struct{ min, max float64 }{
+ {1, 2},
+ {-2, 1},
+ {negZero, zero},
+ {zero, inf},
+ {negInf, zero},
+ {negInf, inf},
+ {1, inf},
+ {negInf, 1},
+}
+
+var all = []float64{1, 2, -1, -2, zero, negZero, inf, negInf, nan}
+
+func eq(x, y float64) bool {
+ return x == y && math.Signbit(x) == math.Signbit(y)
+}
+
+func TestMinFloat(t *testing.T) {
+ for _, tt := range tests {
+ if z := min(tt.min, tt.max); !eq(z, tt.min) {
+ t.Errorf("min(%v, %v) = %v, want %v", tt.min, tt.max, z, tt.min)
+ }
+ if z := min(tt.max, tt.min); !eq(z, tt.min) {
+ t.Errorf("min(%v, %v) = %v, want %v", tt.max, tt.min, z, tt.min)
+ }
+ }
+ for _, x := range all {
+ if z := min(nan, x); !math.IsNaN(z) {
+ t.Errorf("min(%v, %v) = %v, want %v", nan, x, z, nan)
+ }
+ if z := min(x, nan); !math.IsNaN(z) {
+ t.Errorf("min(%v, %v) = %v, want %v", nan, x, z, nan)
+ }
+ }
+}
+
+func TestMaxFloat(t *testing.T) {
+ for _, tt := range tests {
+ if z := max(tt.min, tt.max); !eq(z, tt.max) {
+ t.Errorf("max(%v, %v) = %v, want %v", tt.min, tt.max, z, tt.max)
+ }
+ if z := max(tt.max, tt.min); !eq(z, tt.max) {
+ t.Errorf("max(%v, %v) = %v, want %v", tt.max, tt.min, z, tt.max)
+ }
+ }
+ for _, x := range all {
+ if z := max(nan, x); !math.IsNaN(z) {
+ t.Errorf("min(%v, %v) = %v, want %v", nan, x, z, nan)
+ }
+ if z := max(x, nan); !math.IsNaN(z) {
+ t.Errorf("min(%v, %v) = %v, want %v", nan, x, z, nan)
+ }
+ }
+}
+
+// testMinMax tests that min/max behave correctly on every pair of
+// values in vals.
+//
+// vals should be a sequence of values in strictly ascending order.
+func testMinMax[T int | uint8 | string](t *testing.T, vals ...T) {
+ for i, x := range vals {
+ for _, y := range vals[i+1:] {
+ if !(x < y) {
+ t.Fatalf("values out of order: !(%v < %v)", x, y)
+ }
+
+ if z := min(x, y); z != x {
+ t.Errorf("min(%v, %v) = %v, want %v", x, y, z, x)
+ }
+ if z := min(y, x); z != x {
+ t.Errorf("min(%v, %v) = %v, want %v", y, x, z, x)
+ }
+
+ if z := max(x, y); z != y {
+ t.Errorf("max(%v, %v) = %v, want %v", x, y, z, y)
+ }
+ if z := max(y, x); z != y {
+ t.Errorf("max(%v, %v) = %v, want %v", y, x, z, y)
+ }
+ }
+ }
+}
+
+func TestMinMaxInt(t *testing.T) { testMinMax[int](t, -7, 0, 9) }
+func TestMinMaxUint8(t *testing.T) { testMinMax[uint8](t, 0, 1, 2, 4, 7) }
+func TestMinMaxString(t *testing.T) { testMinMax[string](t, "a", "b", "c") }
+
+// TestMinMaxStringTies ensures that min(a, b) returns a when a == b.
+func TestMinMaxStringTies(t *testing.T) {
+ s := "xxx"
+ x := strings.Split(s, "")
+
+ test := func(i, j, k int) {
+ if z := min(x[i], x[j], x[k]); unsafe.StringData(z) != unsafe.StringData(x[i]) {
+ t.Errorf("min(x[%v], x[%v], x[%v]) = %p, want %p", i, j, k, unsafe.StringData(z), unsafe.StringData(x[i]))
+ }
+ if z := max(x[i], x[j], x[k]); unsafe.StringData(z) != unsafe.StringData(x[i]) {
+ t.Errorf("max(x[%v], x[%v], x[%v]) = %p, want %p", i, j, k, unsafe.StringData(z), unsafe.StringData(x[i]))
+ }
+ }
+
+ test(0, 1, 2)
+ test(0, 2, 1)
+ test(1, 0, 2)
+ test(1, 2, 0)
+ test(2, 0, 1)
+ test(2, 1, 0)
+}