]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: implement min/max builtins
authorMatthew Dempsky <mdempsky@google.com>
Fri, 19 May 2023 00:16:03 +0000 (17:16 -0700)
committerGopher Robot <gobot@golang.org>
Tue, 23 May 2023 18:15:22 +0000 (18:15 +0000)
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>
17 files changed:
src/cmd/compile/internal/escape/call.go
src/cmd/compile/internal/escape/expr.go
src/cmd/compile/internal/ir/expr.go
src/cmd/compile/internal/ir/fmt.go
src/cmd/compile/internal/ir/node.go
src/cmd/compile/internal/ir/op_string.go
src/cmd/compile/internal/ssagen/ssa.go
src/cmd/compile/internal/typecheck/const.go
src/cmd/compile/internal/typecheck/func.go
src/cmd/compile/internal/typecheck/stmt.go
src/cmd/compile/internal/typecheck/typecheck.go
src/cmd/compile/internal/typecheck/universe.go
src/cmd/compile/internal/walk/builtin.go
src/cmd/compile/internal/walk/expr.go
src/cmd/compile/internal/walk/order.go
src/runtime/minmax.go [new file with mode: 0644]
src/runtime/minmax_test.go [new file with mode: 0644]

index 154daa2d650d52a17b1561bc370e0bb8be69f2b2..e88cae09693e0b04edcb6dbff6827db90cf8c967 100644 (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 {
index fc56530969bf2c865e1590f3fa91f608c66b9650..e5f590ddcb8b0803a007d207be8da98f2e7b6f6b 100644 (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)
 
index 3cf52debf987039d53e9e565f77a6d9655ca3a29..5355edc17691cdc38e1753a788328239233c0a42 100644 (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
        }
index a9cf716dffa1581980ac1fc8334cb1fecca4ea8e..0c553a9963ee60410bf8db498005a1f5ad3b840f 100644 (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:
index bdc40a8e7cc936f72e988db8be658dbcc80c23d3..7f8ca023f2b4dbd088e0a92d81bd7c95530617ad 100644 (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)
index e0861457cb81b0758a0b1570592b9a35895b9b1f..571ac6cb45b136103038b7941d36f992307ab496 100644 (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) {
index fc57592084ad8893fa7d00cc6b5ca13e78d4ad67..88fee51d335860923e266a9424a6ad2de6e300b9 100644 (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
index 86920a4d0ecc52cc1c176db4958b972b42c28a1a..f4fb614e639a17bdd759d413e75f70d702f2acb0 100644 (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,
index de8b8b325c4f19e5e1edf4b69e21f6d58ac4bbf5..1d1de5bf942c68ea0ea8249259fdb8e6b813a419 100644 (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)
index 72e91c4fdea6e2c43f10bff339cc93c7e9b49a2b..c434ff9118735d8cf36eb1eee5ef19216c434f61 100644 (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,
index dcc5c75165ab3c166aea3712c5dae9a7d224942c..6e4feeccd9ecfb87564aa1797ef7c206f6131312 100644 (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)
index 38e452ff2a7360a98a65e43b9225089e3f3a1dad..e43bede4ce29ad68492fc321e0fdbdedbd7d9bcd 100644 (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},
index 98825bd95bacced171404fdcf216ab6fc9a56507..528296e99dd8ffe2d0809a9e869764d09d5cfdb9 100644 (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.
index 7914cdaf6c502676f01b2327077797dc635eaed5..909e7d624eee6f46d0d784cea2480c8deafda46c 100644 (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)
index 243b682ebbebde5593b06f4ed198f702a38d78bf..1e76761de3537918032c21b507ecf943c757f5c1 100644 (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)
diff --git a/src/runtime/minmax.go b/src/runtime/minmax.go
new file mode 100644 (file)
index 0000000..e5efc65
--- /dev/null
@@ -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 (file)
index 0000000..e0bc28f
--- /dev/null
@@ -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)
+}