From f791c821ff5d393c875fe41889b94c19cf5f508d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 6 Jul 2023 22:14:55 +0200 Subject: [PATCH] compiler: add min and max builtin support --- compiler/compiler.go | 18 +++++ compiler/compiler_test.go | 3 + compiler/testdata/go1.21.go | 53 +++++++++++++ compiler/testdata/go1.21.ll | 152 ++++++++++++++++++++++++++++++++++++ main_test.go | 10 +++ testdata/go1.21.go | 12 +++ testdata/go1.21.txt | 2 + 7 files changed, 250 insertions(+) create mode 100644 compiler/testdata/go1.21.go create mode 100644 compiler/testdata/go1.21.ll create mode 100644 testdata/go1.21.go create mode 100644 testdata/go1.21.txt diff --git a/compiler/compiler.go b/compiler/compiler.go index 09f11e61..46c3a067 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 9675c402..92ce31b0 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 00000000..5541b489 --- /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 00000000..5d4a7019 --- /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 59254656..32d13f91 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 00000000..885e588d --- /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 00000000..ad81dcfe --- /dev/null +++ b/testdata/go1.21.txt @@ -0,0 +1,2 @@ +min/max: -3 5 +min/max: -3.000000e+000 +5.000000e+000