Skip to content

Commit a12e1a6

Browse files
committed
go/ssa/interp: implement min/max builtins
Updates golang/go#59488. Change-Id: I68c90ddf0f9dea2c6506b9ab43beb522cbdf5fdd Reviewed-on: https://go-review.googlesource.com/c/tools/+/497516 Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Tim King <taking@google.com>
1 parent 9c97539 commit a12e1a6

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

go/ssa/interp/external.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func init() {
7070
"bytes.IndexByte": ext۰bytes۰IndexByte,
7171
"fmt.Sprint": ext۰fmt۰Sprint,
7272
"math.Abs": ext۰math۰Abs,
73+
"math.Copysign": ext۰math۰Copysign,
7374
"math.Exp": ext۰math۰Exp,
7475
"math.Float32bits": ext۰math۰Float32bits,
7576
"math.Float32frombits": ext۰math۰Float32frombits,
@@ -158,6 +159,10 @@ func ext۰math۰Abs(fr *frame, args []value) value {
158159
return math.Abs(args[0].(float64))
159160
}
160161

162+
func ext۰math۰Copysign(fr *frame, args []value) value {
163+
return math.Copysign(args[0].(float64), args[1].(float64))
164+
}
165+
161166
func ext۰math۰Exp(fr *frame, args []value) value {
162167
return math.Exp(args[0].(float64))
163168
}

go/ssa/interp/interp_go121_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.21
6+
// +build go1.21
7+
8+
package interp_test
9+
10+
func init() {
11+
testdataTests = append(testdataTests, "minmax.go")
12+
}

go/ssa/interp/ops.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,11 @@ func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value
10601060
panic(fmt.Sprintf("cap: illegal operand: %T", x))
10611061
}
10621062

1063+
case "min":
1064+
return foldLeft(min, args)
1065+
case "max":
1066+
return foldLeft(max, args)
1067+
10631068
case "real":
10641069
switch c := args[0].(type) {
10651070
case complex64:
@@ -1426,3 +1431,89 @@ func checkInterface(i *interpreter, itype *types.Interface, x iface) string {
14261431
}
14271432
return "" // ok
14281433
}
1434+
1435+
func foldLeft(op func(value, value) value, args []value) value {
1436+
x := args[0]
1437+
for _, arg := range args[1:] {
1438+
x = op(x, arg)
1439+
}
1440+
return x
1441+
}
1442+
1443+
func min(x, y value) value {
1444+
switch x := x.(type) {
1445+
case float32:
1446+
return fmin(x, y.(float32))
1447+
case float64:
1448+
return fmin(x, y.(float64))
1449+
}
1450+
1451+
// return (y < x) ? y : x
1452+
if binop(token.LSS, nil, y, x).(bool) {
1453+
return y
1454+
}
1455+
return x
1456+
}
1457+
1458+
func max(x, y value) value {
1459+
switch x := x.(type) {
1460+
case float32:
1461+
return fmax(x, y.(float32))
1462+
case float64:
1463+
return fmax(x, y.(float64))
1464+
}
1465+
1466+
// return (y > x) ? y : x
1467+
if binop(token.GTR, nil, y, x).(bool) {
1468+
return y
1469+
}
1470+
return x
1471+
}
1472+
1473+
// copied from $GOROOT/src/runtime/minmax.go
1474+
1475+
type floaty interface{ ~float32 | ~float64 }
1476+
1477+
func fmin[F floaty](x, y F) F {
1478+
if y != y || y < x {
1479+
return y
1480+
}
1481+
if x != x || x < y || x != 0 {
1482+
return x
1483+
}
1484+
// x and y are both ±0
1485+
// if either is -0, return -0; else return +0
1486+
return forbits(x, y)
1487+
}
1488+
1489+
func fmax[F floaty](x, y F) F {
1490+
if y != y || y > x {
1491+
return y
1492+
}
1493+
if x != x || x > y || x != 0 {
1494+
return x
1495+
}
1496+
// x and y are both ±0
1497+
// if both are -0, return -0; else return +0
1498+
return fandbits(x, y)
1499+
}
1500+
1501+
func forbits[F floaty](x, y F) F {
1502+
switch unsafe.Sizeof(x) {
1503+
case 4:
1504+
*(*uint32)(unsafe.Pointer(&x)) |= *(*uint32)(unsafe.Pointer(&y))
1505+
case 8:
1506+
*(*uint64)(unsafe.Pointer(&x)) |= *(*uint64)(unsafe.Pointer(&y))
1507+
}
1508+
return x
1509+
}
1510+
1511+
func fandbits[F floaty](x, y F) F {
1512+
switch unsafe.Sizeof(x) {
1513+
case 4:
1514+
*(*uint32)(unsafe.Pointer(&x)) &= *(*uint32)(unsafe.Pointer(&y))
1515+
case 8:
1516+
*(*uint64)(unsafe.Pointer(&x)) &= *(*uint64)(unsafe.Pointer(&y))
1517+
}
1518+
return x
1519+
}

go/ssa/interp/testdata/minmax.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"fmt"
9+
"math"
10+
)
11+
12+
func main() {
13+
TestMinFloat()
14+
TestMaxFloat()
15+
TestMinMaxInt()
16+
TestMinMaxUint8()
17+
TestMinMaxString()
18+
}
19+
20+
func errorf(format string, args ...any) { panic(fmt.Sprintf(format, args...)) }
21+
func fatalf(format string, args ...any) { panic(fmt.Sprintf(format, args...)) }
22+
23+
// derived from $GOROOT/src/runtime/minmax_test.go
24+
25+
var (
26+
zero = math.Copysign(0, +1)
27+
negZero = math.Copysign(0, -1)
28+
inf = math.Inf(+1)
29+
negInf = math.Inf(-1)
30+
nan = math.NaN()
31+
)
32+
33+
var tests = []struct{ min, max float64 }{
34+
{1, 2},
35+
{-2, 1},
36+
{negZero, zero},
37+
{zero, inf},
38+
{negInf, zero},
39+
{negInf, inf},
40+
{1, inf},
41+
{negInf, 1},
42+
}
43+
44+
var all = []float64{1, 2, -1, -2, zero, negZero, inf, negInf, nan}
45+
46+
func eq(x, y float64) bool {
47+
return x == y && math.Signbit(x) == math.Signbit(y)
48+
}
49+
50+
func TestMinFloat() {
51+
for _, tt := range tests {
52+
if z := min(tt.min, tt.max); !eq(z, tt.min) {
53+
errorf("min(%v, %v) = %v, want %v", tt.min, tt.max, z, tt.min)
54+
}
55+
if z := min(tt.max, tt.min); !eq(z, tt.min) {
56+
errorf("min(%v, %v) = %v, want %v", tt.max, tt.min, z, tt.min)
57+
}
58+
}
59+
for _, x := range all {
60+
if z := min(nan, x); !math.IsNaN(z) {
61+
errorf("min(%v, %v) = %v, want %v", nan, x, z, nan)
62+
}
63+
if z := min(x, nan); !math.IsNaN(z) {
64+
errorf("min(%v, %v) = %v, want %v", nan, x, z, nan)
65+
}
66+
}
67+
}
68+
69+
func TestMaxFloat() {
70+
for _, tt := range tests {
71+
if z := max(tt.min, tt.max); !eq(z, tt.max) {
72+
errorf("max(%v, %v) = %v, want %v", tt.min, tt.max, z, tt.max)
73+
}
74+
if z := max(tt.max, tt.min); !eq(z, tt.max) {
75+
errorf("max(%v, %v) = %v, want %v", tt.max, tt.min, z, tt.max)
76+
}
77+
}
78+
for _, x := range all {
79+
if z := max(nan, x); !math.IsNaN(z) {
80+
errorf("min(%v, %v) = %v, want %v", nan, x, z, nan)
81+
}
82+
if z := max(x, nan); !math.IsNaN(z) {
83+
errorf("min(%v, %v) = %v, want %v", nan, x, z, nan)
84+
}
85+
}
86+
}
87+
88+
// testMinMax tests that min/max behave correctly on every pair of
89+
// values in vals.
90+
//
91+
// vals should be a sequence of values in strictly ascending order.
92+
func testMinMax[T int | uint8 | string](vals ...T) {
93+
for i, x := range vals {
94+
for _, y := range vals[i+1:] {
95+
if !(x < y) {
96+
fatalf("values out of order: !(%v < %v)", x, y)
97+
}
98+
99+
if z := min(x, y); z != x {
100+
errorf("min(%v, %v) = %v, want %v", x, y, z, x)
101+
}
102+
if z := min(y, x); z != x {
103+
errorf("min(%v, %v) = %v, want %v", y, x, z, x)
104+
}
105+
106+
if z := max(x, y); z != y {
107+
errorf("max(%v, %v) = %v, want %v", x, y, z, y)
108+
}
109+
if z := max(y, x); z != y {
110+
errorf("max(%v, %v) = %v, want %v", y, x, z, y)
111+
}
112+
}
113+
}
114+
}
115+
116+
func TestMinMaxInt() { testMinMax[int](-7, 0, 9) }
117+
func TestMinMaxUint8() { testMinMax[uint8](0, 1, 2, 4, 7) }
118+
func TestMinMaxString() { testMinMax[string]("a", "b", "c") }

go/ssa/interp/testdata/src/math/math.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package math
22

3+
func Copysign(float64, float64) float64
4+
35
func NaN() float64
46

57
func Inf(int) float64

0 commit comments

Comments
 (0)