aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2023-07-06 22:14:55 +0200
committerRon Evans <[email protected]>2023-08-04 11:59:11 +0200
commitf791c821ff5d393c875fe41889b94c19cf5f508d (patch)
treecdb93c55f2e6094be063cc384187d17a89e81aad
parenta93f0ed12a30d694ad8db2a2636b1247e772c3e8 (diff)
downloadtinygo-f791c821ff5d393c875fe41889b94c19cf5f508d.tar.gz
tinygo-f791c821ff5d393c875fe41889b94c19cf5f508d.zip
compiler: add min and max builtin support
-rw-r--r--compiler/compiler.go18
-rw-r--r--compiler/compiler_test.go3
-rw-r--r--compiler/testdata/go1.21.go53
-rw-r--r--compiler/testdata/go1.21.ll152
-rw-r--r--main_test.go10
-rw-r--r--testdata/go1.21.go12
-rw-r--r--testdata/go1.21.txt2
7 files changed, 250 insertions, 0 deletions
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 09f11e614..46c3a0672 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -1637,6 +1637,24 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
llvmLen = b.CreateZExt(llvmLen, b.intType, "len.int")
}
return llvmLen, nil
+ case "min", "max":
+ // min and max builtins, added in Go 1.21.
+ // We can simply reuse the existing binop comparison code, which has all
+ // the edge cases figured out already.
+ tok := token.LSS
+ if callName == "max" {
+ tok = token.GTR
+ }
+ result := argValues[0]
+ typ := argTypes[0]
+ for _, arg := range argValues[1:] {
+ cmp, err := b.createBinOp(tok, typ, typ, result, arg, pos)
+ if err != nil {
+ return result, err
+ }
+ result = b.CreateSelect(cmp, result, arg, "")
+ }
+ return result, nil
case "print", "println":
for i, value := range argValues {
if i >= 1 && callName == "println" {
diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go
index 9675c4028..92ce31b01 100644
--- a/compiler/compiler_test.go
+++ b/compiler/compiler_test.go
@@ -54,6 +54,9 @@ func TestCompiler(t *testing.T) {
if goMinor >= 20 {
tests = append(tests, testCase{"go1.20.go", "", ""})
}
+ if goMinor >= 21 {
+ tests = append(tests, testCase{"go1.21.go", "", ""})
+ }
for _, tc := range tests {
name := tc.file
diff --git a/compiler/testdata/go1.21.go b/compiler/testdata/go1.21.go
new file mode 100644
index 000000000..5541b489d
--- /dev/null
+++ b/compiler/testdata/go1.21.go
@@ -0,0 +1,53 @@
+package main
+
+func min1(a int) int {
+ return min(a)
+}
+
+func min2(a, b int) int {
+ return min(a, b)
+}
+
+func min3(a, b, c int) int {
+ return min(a, b, c)
+}
+
+func min4(a, b, c, d int) int {
+ return min(a, b, c, d)
+}
+
+func minUint8(a, b uint8) uint8 {
+ return min(a, b)
+}
+
+func minUnsigned(a, b uint) uint {
+ return min(a, b)
+}
+
+func minFloat32(a, b float32) float32 {
+ return min(a, b)
+}
+
+func minFloat64(a, b float64) float64 {
+ return min(a, b)
+}
+
+func minString(a, b string) string {
+ return min(a, b)
+}
+
+func maxInt(a, b int) int {
+ return max(a, b)
+}
+
+func maxUint(a, b uint) uint {
+ return max(a, b)
+}
+
+func maxFloat32(a, b float32) float32 {
+ return max(a, b)
+}
+
+func maxString(a, b string) string {
+ return max(a, b)
+}
diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll
new file mode 100644
index 000000000..5d4a70197
--- /dev/null
+++ b/compiler/testdata/go1.21.ll
@@ -0,0 +1,152 @@
+; ModuleID = 'go1.21.go'
+source_filename = "go1.21.go"
+target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
+target triple = "wasm32-unknown-wasi"
+
+%runtime._string = type { ptr, i32 }
+
+; Function Attrs: allockind("alloc,zeroed") allocsize(0)
+declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
+
+declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1
+
+; Function Attrs: nounwind
+define hidden void @main.init(ptr %context) unnamed_addr #2 {
+entry:
+ ret void
+}
+
+; Function Attrs: nounwind
+define hidden i32 @main.min1(i32 %a, ptr %context) unnamed_addr #2 {
+entry:
+ ret i32 %a
+}
+
+; Function Attrs: nounwind
+define hidden i32 @main.min2(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ ret i32 %0
+}
+
+; Function Attrs: nounwind
+define hidden i32 @main.min3(i32 %a, i32 %b, i32 %c, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c)
+ ret i32 %1
+}
+
+; Function Attrs: nounwind
+define hidden i32 @main.min4(i32 %a, i32 %b, i32 %c, i32 %d, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c)
+ %2 = call i32 @llvm.smin.i32(i32 %1, i32 %d)
+ ret i32 %2
+}
+
+; Function Attrs: nounwind
+define hidden i8 @main.minUint8(i8 %a, i8 %b, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = call i8 @llvm.umin.i8(i8 %a, i8 %b)
+ ret i8 %0
+}
+
+; Function Attrs: nounwind
+define hidden i32 @main.minUnsigned(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = call i32 @llvm.umin.i32(i32 %a, i32 %b)
+ ret i32 %0
+}
+
+; Function Attrs: nounwind
+define hidden float @main.minFloat32(float %a, float %b, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = fcmp olt float %a, %b
+ %1 = select i1 %0, float %a, float %b
+ ret float %1
+}
+
+; Function Attrs: nounwind
+define hidden double @main.minFloat64(double %a, double %b, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = fcmp olt double %a, %b
+ %1 = select i1 %0, double %a, double %b
+ ret double %1
+}
+
+; Function Attrs: nounwind
+define hidden %runtime._string @main.minString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0
+ %1 = insertvalue %runtime._string %0, i32 %a.len, 1
+ %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0
+ %3 = insertvalue %runtime._string %2, i32 %b.len, 1
+ %stackalloc = alloca i8, align 1
+ %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #4
+ %5 = select i1 %4, %runtime._string %1, %runtime._string %3
+ %6 = extractvalue %runtime._string %5, 0
+ call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4
+ ret %runtime._string %5
+}
+
+declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1
+
+; Function Attrs: nounwind
+define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ ret i32 %0
+}
+
+; Function Attrs: nounwind
+define hidden i32 @main.maxUint(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = call i32 @llvm.umax.i32(i32 %a, i32 %b)
+ ret i32 %0
+}
+
+; Function Attrs: nounwind
+define hidden float @main.maxFloat32(float %a, float %b, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = fcmp ogt float %a, %b
+ %1 = select i1 %0, float %a, float %b
+ ret float %1
+}
+
+; Function Attrs: nounwind
+define hidden %runtime._string @main.maxString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
+entry:
+ %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0
+ %1 = insertvalue %runtime._string %0, i32 %a.len, 1
+ %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0
+ %3 = insertvalue %runtime._string %2, i32 %b.len, 1
+ %stackalloc = alloca i8, align 1
+ %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #4
+ %5 = select i1 %4, %runtime._string %1, %runtime._string %3
+ %6 = extractvalue %runtime._string %5, 0
+ call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4
+ ret %runtime._string %5
+}
+
+; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
+declare i32 @llvm.smin.i32(i32, i32) #3
+
+; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
+declare i8 @llvm.umin.i8(i8, i8) #3
+
+; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
+declare i32 @llvm.umin.i32(i32, i32) #3
+
+; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
+declare i32 @llvm.smax.i32(i32, i32) #3
+
+; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
+declare i32 @llvm.umax.i32(i32, i32) #3
+
+attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
+attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
+attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
+attributes #3 = { nocallback nofree nosync nounwind readnone speculatable willreturn }
+attributes #4 = { nounwind }
diff --git a/main_test.go b/main_test.go
index 59254656e..32d13f910 100644
--- a/main_test.go
+++ b/main_test.go
@@ -71,6 +71,16 @@ func TestBuild(t *testing.T) {
"zeroalloc.go",
}
+ // Go 1.21 made some changes to the language, which we can only test when
+ // we're actually on Go 1.21.
+ _, minor, err := goenv.GetGorootVersion()
+ if err != nil {
+ t.Fatal("could not get version:", minor)
+ }
+ if minor >= 21 {
+ tests = append(tests, "go1.21.go")
+ }
+
if *testTarget != "" {
// This makes it possible to run one specific test (instead of all),
// which is especially useful to quickly check whether some changes
diff --git a/testdata/go1.21.go b/testdata/go1.21.go
new file mode 100644
index 000000000..885e588da
--- /dev/null
+++ b/testdata/go1.21.go
@@ -0,0 +1,12 @@
+package main
+
+func main() {
+ ia := 1
+ ib := 5
+ ic := -3
+ fa := 1.0
+ fb := 5.0
+ fc := -3.0
+ println("min/max:", min(ia, ib, ic), max(ia, ib, ic))
+ println("min/max:", min(fa, fb, fc), max(fa, fb, fc))
+}
diff --git a/testdata/go1.21.txt b/testdata/go1.21.txt
new file mode 100644
index 000000000..ad81dcfe9
--- /dev/null
+++ b/testdata/go1.21.txt
@@ -0,0 +1,2 @@
+min/max: -3 5
+min/max: -3.000000e+000 +5.000000e+000