From: Matthew Dempsky Date: Fri, 19 May 2023 00:16:03 +0000 (-0700) Subject: cmd/compile: implement min/max builtins X-Git-Tag: go1.21rc1~340 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=b0f15b4ac07e5d552475fefcf61bdeda90e66642;p=gostls13.git 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 Auto-Submit: Matthew Dempsky Reviewed-by: Keith Randall TryBot-Result: Gopher Robot Run-TryBot: Matthew Dempsky Reviewed-by: Keith Randall --- diff --git a/src/cmd/compile/internal/escape/call.go b/src/cmd/compile/internal/escape/call.go index 154daa2d65..e88cae0969 100644 --- a/src/cmd/compile/internal/escape/call.go +++ b/src/cmd/compile/internal/escape/call.go @@ -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 { diff --git a/src/cmd/compile/internal/escape/expr.go b/src/cmd/compile/internal/escape/expr.go index fc56530969..e5f590ddcb 100644 --- a/src/cmd/compile/internal/escape/expr.go +++ b/src/cmd/compile/internal/escape/expr.go @@ -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) diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 3cf52debf9..5355edc176 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -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 } diff --git a/src/cmd/compile/internal/ir/fmt.go b/src/cmd/compile/internal/ir/fmt.go index a9cf716dff..0c553a9963 100644 --- a/src/cmd/compile/internal/ir/fmt.go +++ b/src/cmd/compile/internal/ir/fmt.go @@ -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: diff --git a/src/cmd/compile/internal/ir/node.go b/src/cmd/compile/internal/ir/node.go index bdc40a8e7c..7f8ca023f2 100644 --- a/src/cmd/compile/internal/ir/node.go +++ b/src/cmd/compile/internal/ir/node.go @@ -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) diff --git a/src/cmd/compile/internal/ir/op_string.go b/src/cmd/compile/internal/ir/op_string.go index e0861457cb..571ac6cb45 100644 --- a/src/cmd/compile/internal/ir/op_string.go +++ b/src/cmd/compile/internal/ir/op_string.go @@ -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) { diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index fc57592084..88fee51d33 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -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 diff --git a/src/cmd/compile/internal/typecheck/const.go b/src/cmd/compile/internal/typecheck/const.go index 86920a4d0e..f4fb614e63 100644 --- a/src/cmd/compile/internal/typecheck/const.go +++ b/src/cmd/compile/internal/typecheck/const.go @@ -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, diff --git a/src/cmd/compile/internal/typecheck/func.go b/src/cmd/compile/internal/typecheck/func.go index de8b8b325c..1d1de5bf94 100644 --- a/src/cmd/compile/internal/typecheck/func.go +++ b/src/cmd/compile/internal/typecheck/func.go @@ -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) diff --git a/src/cmd/compile/internal/typecheck/stmt.go b/src/cmd/compile/internal/typecheck/stmt.go index 72e91c4fde..c434ff9118 100644 --- a/src/cmd/compile/internal/typecheck/stmt.go +++ b/src/cmd/compile/internal/typecheck/stmt.go @@ -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, diff --git a/src/cmd/compile/internal/typecheck/typecheck.go b/src/cmd/compile/internal/typecheck/typecheck.go index dcc5c75165..6e4feeccd9 100644 --- a/src/cmd/compile/internal/typecheck/typecheck.go +++ b/src/cmd/compile/internal/typecheck/typecheck.go @@ -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) diff --git a/src/cmd/compile/internal/typecheck/universe.go b/src/cmd/compile/internal/typecheck/universe.go index 38e452ff2a..e43bede4ce 100644 --- a/src/cmd/compile/internal/typecheck/universe.go +++ b/src/cmd/compile/internal/typecheck/universe.go @@ -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}, diff --git a/src/cmd/compile/internal/walk/builtin.go b/src/cmd/compile/internal/walk/builtin.go index 98825bd95b..528296e99d 100644 --- a/src/cmd/compile/internal/walk/builtin.go +++ b/src/cmd/compile/internal/walk/builtin.go @@ -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. diff --git a/src/cmd/compile/internal/walk/expr.go b/src/cmd/compile/internal/walk/expr.go index 7914cdaf6c..909e7d624e 100644 --- a/src/cmd/compile/internal/walk/expr.go +++ b/src/cmd/compile/internal/walk/expr.go @@ -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) diff --git a/src/cmd/compile/internal/walk/order.go b/src/cmd/compile/internal/walk/order.go index 243b682ebb..1e76761de3 100644 --- a/src/cmd/compile/internal/walk/order.go +++ b/src/cmd/compile/internal/walk/order.go @@ -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) diff --git a/src/runtime/minmax.go b/src/runtime/minmax.go new file mode 100644 index 0000000000..e5efc65c1d --- /dev/null +++ b/src/runtime/minmax.go @@ -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 +} diff --git a/src/runtime/minmax_test.go b/src/runtime/minmax_test.go new file mode 100644 index 0000000000..e0bc28fbf6 --- /dev/null +++ b/src/runtime/minmax_test.go @@ -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) +}