1
0
mirror of https://github.com/golang/go synced 2024-11-23 15:20:03 -07:00

cmd/compile: implement min/max builtins

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>
This commit is contained in:
Matthew Dempsky 2023-05-18 17:16:03 -07:00 committed by Gopher Robot
parent a6a25869f0
commit b0f15b4ac0
17 changed files with 391 additions and 58 deletions

View File

@ -186,7 +186,7 @@ func (e *escape) callCommon(ks []hole, call ir.Node, init *ir.Nodes, wrapper *ir
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 {

View File

@ -139,7 +139,7 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
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)

View File

@ -174,7 +174,7 @@ func (n *CallExpr) SetOp(op Op) {
OCALL, OCALLFUNC, OCALLINTER, OCALLMETH,
ODELETE,
OGETG, OGETCALLERPC, OGETCALLERSP,
OMAKE, OPRINT, OPRINTN,
OMAKE, OMAX, OMIN, OPRINT, OPRINTN,
ORECOVER, ORECOVERFP:
n.op = op
}

View File

@ -63,6 +63,8 @@ var OpNames = []string{
OLT: "<",
OMAKE: "make",
ONEG: "-",
OMAX: "max",
OMIN: "min",
OMOD: "%",
OMUL: "*",
ONEW: "new",
@ -198,6 +200,8 @@ var OpPrec = []int{
OMAKESLICECOPY: 8,
OMAKE: 8,
OMAPLIT: 8,
OMAX: 8,
OMIN: 8,
ONAME: 8,
ONEW: 8,
ONIL: 8,
@ -788,6 +792,8 @@ func exprFmt(n Node, s fmt.State, prec int) {
case OAPPEND,
ODELETE,
OMAKE,
OMAX,
OMIN,
ORECOVER,
OPRINT,
OPRINTN:

View File

@ -242,6 +242,8 @@ const (
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)

View File

@ -115,60 +115,62 @@ func _() {
_ = 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) {

View File

@ -3314,6 +3314,9 @@ func (s *state) exprCheckPtr(n ir.Node, checkPtrOK bool) *ssa.Value {
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{}
@ -3547,6 +3550,92 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value {
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

View File

@ -539,6 +539,8 @@ func callOrChan(n ir.Node) bool {
ir.OIMAG,
ir.OLEN,
ir.OMAKE,
ir.OMAX,
ir.OMIN,
ir.ONEW,
ir.OPANIC,
ir.OPRINT,

View File

@ -254,7 +254,7 @@ func tcCall(n *ir.CallExpr, top int) ir.Node {
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
@ -803,6 +803,19 @@ func tcPrint(n *ir.CallExpr) ir.Node {
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)

View File

@ -278,6 +278,8 @@ func tcGoDefer(n *ir.GoDeferStmt) {
ir.OCLOSE,
ir.OCOPY,
ir.ODELETE,
ir.OMAX,
ir.OMIN,
ir.OPANIC,
ir.OPRINT,
ir.OPRINTN,

View File

@ -332,8 +332,8 @@ func typecheck(n ir.Node, top int) (res ir.Node) {
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.
@ -605,6 +605,10 @@ func typecheck1(n ir.Node, top int) ir.Node {
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)

View File

@ -42,6 +42,8 @@ var builtinFuncs = [...]struct {
{"imag", ir.OIMAG},
{"len", ir.OLEN},
{"make", ir.OMAKE},
{"max", ir.OMAX},
{"min", ir.OMIN},
{"new", ir.ONEW},
{"panic", ir.OPANIC},
{"print", ir.OPRINT},

View File

@ -525,6 +525,12 @@ func walkNew(n *ir.UnaryExpr, init *ir.Nodes) ir.Node {
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.

View File

@ -98,6 +98,10 @@ func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node {
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)

View File

@ -755,7 +755,7 @@ func (o *orderState) stmt(n ir.Node) {
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)

72
src/runtime/minmax.go Normal file
View File

@ -0,0 +1,72 @@
// 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
}

129
src/runtime/minmax_test.go Normal file
View File

@ -0,0 +1,129 @@
// 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)
}