1
0
mirror of https://github.com/golang/go synced 2024-11-23 16:00:06 -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.X)
argument(e.discardHole(), &call.Y) 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) call := call.(*ir.CallExpr)
fixRecoverCall(call) fixRecoverCall(call)
for i := range call.Args { for i := range call.Args {

View File

@ -139,7 +139,7 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
e.discard(n.X) e.discard(n.X)
case ir.OCALLMETH, ir.OCALLFUNC, ir.OCALLINTER, ir.OINLCALL, 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: ir.OUNSAFEADD, ir.OUNSAFESLICE, ir.OUNSAFESTRING, ir.OUNSAFESTRINGDATA, ir.OUNSAFESLICEDATA:
e.call([]hole{k}, n) e.call([]hole{k}, n)

View File

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

View File

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

View File

@ -242,6 +242,8 @@ const (
ORECV // <-X ORECV // <-X
ORUNESTR // Type(X) (Type is string, X is rune) 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) 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) OREAL // real(X)
OIMAG // imag(X) OIMAG // imag(X)
OCOMPLEX // complex(X, Y) OCOMPLEX // complex(X, Y)

View File

@ -115,60 +115,62 @@ func _() {
_ = x[ORECV-104] _ = x[ORECV-104]
_ = x[ORUNESTR-105] _ = x[ORUNESTR-105]
_ = x[OSELRECV2-106] _ = x[OSELRECV2-106]
_ = x[OREAL-107] _ = x[OMIN-107]
_ = x[OIMAG-108] _ = x[OMAX-108]
_ = x[OCOMPLEX-109] _ = x[OREAL-109]
_ = x[OALIGNOF-110] _ = x[OIMAG-110]
_ = x[OOFFSETOF-111] _ = x[OCOMPLEX-111]
_ = x[OSIZEOF-112] _ = x[OALIGNOF-112]
_ = x[OUNSAFEADD-113] _ = x[OOFFSETOF-113]
_ = x[OUNSAFESLICE-114] _ = x[OSIZEOF-114]
_ = x[OUNSAFESLICEDATA-115] _ = x[OUNSAFEADD-115]
_ = x[OUNSAFESTRING-116] _ = x[OUNSAFESLICE-116]
_ = x[OUNSAFESTRINGDATA-117] _ = x[OUNSAFESLICEDATA-117]
_ = x[OMETHEXPR-118] _ = x[OUNSAFESTRING-118]
_ = x[OMETHVALUE-119] _ = x[OUNSAFESTRINGDATA-119]
_ = x[OBLOCK-120] _ = x[OMETHEXPR-120]
_ = x[OBREAK-121] _ = x[OMETHVALUE-121]
_ = x[OCASE-122] _ = x[OBLOCK-122]
_ = x[OCONTINUE-123] _ = x[OBREAK-123]
_ = x[ODEFER-124] _ = x[OCASE-124]
_ = x[OFALL-125] _ = x[OCONTINUE-125]
_ = x[OFOR-126] _ = x[ODEFER-126]
_ = x[OGOTO-127] _ = x[OFALL-127]
_ = x[OIF-128] _ = x[OFOR-128]
_ = x[OLABEL-129] _ = x[OGOTO-129]
_ = x[OGO-130] _ = x[OIF-130]
_ = x[ORANGE-131] _ = x[OLABEL-131]
_ = x[ORETURN-132] _ = x[OGO-132]
_ = x[OSELECT-133] _ = x[ORANGE-133]
_ = x[OSWITCH-134] _ = x[ORETURN-134]
_ = x[OTYPESW-135] _ = x[OSELECT-135]
_ = x[OFUNCINST-136] _ = x[OSWITCH-136]
_ = x[OINLCALL-137] _ = x[OTYPESW-137]
_ = x[OEFACE-138] _ = x[OFUNCINST-138]
_ = x[OITAB-139] _ = x[OINLCALL-139]
_ = x[OIDATA-140] _ = x[OEFACE-140]
_ = x[OSPTR-141] _ = x[OITAB-141]
_ = x[OCFUNC-142] _ = x[OIDATA-142]
_ = x[OCHECKNIL-143] _ = x[OSPTR-143]
_ = x[ORESULT-144] _ = x[OCFUNC-144]
_ = x[OINLMARK-145] _ = x[OCHECKNIL-145]
_ = x[OLINKSYMOFFSET-146] _ = x[ORESULT-146]
_ = x[OJUMPTABLE-147] _ = x[OINLMARK-147]
_ = x[ODYNAMICDOTTYPE-148] _ = x[OLINKSYMOFFSET-148]
_ = x[ODYNAMICDOTTYPE2-149] _ = x[OJUMPTABLE-149]
_ = x[ODYNAMICTYPE-150] _ = x[ODYNAMICDOTTYPE-150]
_ = x[OTAILCALL-151] _ = x[ODYNAMICDOTTYPE2-151]
_ = x[OGETG-152] _ = x[ODYNAMICTYPE-152]
_ = x[OGETCALLERPC-153] _ = x[OTAILCALL-153]
_ = x[OGETCALLERSP-154] _ = x[OGETG-154]
_ = x[OEND-155] _ = 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 { func (i Op) String() string {
if i >= Op(len(_Op_index)-1) { 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: case ir.OAPPEND:
return s.append(n.(*ir.CallExpr), false) return s.append(n.(*ir.CallExpr), false)
case ir.OMIN, ir.OMAX:
return s.minMax(n.(*ir.CallExpr))
case ir.OSTRUCTLIT, ir.OARRAYLIT: case ir.OSTRUCTLIT, ir.OARRAYLIT:
// All literals with nonzero fields have already been // All literals with nonzero fields have already been
// rewritten during walk. Any that remain are just T{} // 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) 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 // condBranch evaluates the boolean expression cond and branches to yes
// if cond is true and no if cond is false. // if cond is true and no if cond is false.
// This function is intended to handle && and || better than just calling // 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.OIMAG,
ir.OLEN, ir.OLEN,
ir.OMAKE, ir.OMAKE,
ir.OMAX,
ir.OMIN,
ir.ONEW, ir.ONEW,
ir.OPANIC, ir.OPANIC,
ir.OPRINT, ir.OPRINT,

View File

@ -254,7 +254,7 @@ func tcCall(n *ir.CallExpr, top int) ir.Node {
default: default:
base.Fatalf("unknown builtin %v", l) 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.SetOp(l.BuiltinOp)
n.X = nil n.X = nil
n.SetTypecheck(0) // re-typechecking new op is OK, not a loop 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 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. // tcRealImag typechecks an OREAL or OIMAG node.
func tcRealImag(n *ir.UnaryExpr) ir.Node { func tcRealImag(n *ir.UnaryExpr) ir.Node {
n.X = Expr(n.X) n.X = Expr(n.X)

View File

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

View File

@ -332,8 +332,8 @@ func typecheck(n ir.Node, top int) (res ir.Node) {
isExpr = false isExpr = false
} }
} }
case ir.OAPPEND: case ir.OAPPEND, ir.OMIN, ir.OMAX:
// Must be used (and not BinaryExpr/UnaryExpr). // Must be used.
isStmt = false isStmt = false
case ir.OCLEAR, ir.OCLOSE, ir.ODELETE, ir.OPANIC, ir.OPRINT, ir.OPRINTN: case ir.OCLEAR, ir.OCLOSE, ir.ODELETE, ir.OPANIC, ir.OPRINT, ir.OPRINTN:
// Must not be used. // Must not be used.
@ -605,6 +605,10 @@ func typecheck1(n ir.Node, top int) ir.Node {
n := n.(*ir.UnaryExpr) n := n.(*ir.UnaryExpr)
return tcLenCap(n) return tcLenCap(n)
case ir.OMIN, ir.OMAX:
n := n.(*ir.CallExpr)
return tcMinMax(n)
case ir.OREAL, ir.OIMAG: case ir.OREAL, ir.OIMAG:
n := n.(*ir.UnaryExpr) n := n.(*ir.UnaryExpr)
return tcRealImag(n) return tcRealImag(n)

View File

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

View File

@ -525,6 +525,12 @@ func walkNew(n *ir.UnaryExpr, init *ir.Nodes) ir.Node {
return n 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. // generate code for print.
func walkPrint(nn *ir.CallExpr, init *ir.Nodes) ir.Node { func walkPrint(nn *ir.CallExpr, init *ir.Nodes) ir.Node {
// Hoist all the argument evaluation up before the lock. // 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) n := n.(*ir.SelectorExpr)
return n.FuncName() 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: case ir.ONOT, ir.ONEG, ir.OPLUS, ir.OBITNOT, ir.OREAL, ir.OIMAG, ir.OSPTR, ir.OITAB, ir.OIDATA:
n := n.(*ir.UnaryExpr) n := n.(*ir.UnaryExpr)
n.X = walkExpr(n.X, init) 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.out = append(o.out, n)
o.popTemp(t) 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) n := n.(*ir.CallExpr)
t := o.markTemp() t := o.markTemp()
o.call(n) 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)
}