Repository: gonum/gonum
Pull request #1589 (2021-03-17 18:14:54)

 internal/asm/f32: add assembly Sum implementation
Tests pass, implementation based on https://github.com/gonum/gonum/blob/master/internal/asm/f64/sum_amd64.s (including taking over the respective tests).

I know `f32` isn't really exposed, but I hope that in the future maybe it will be. At the moment I simply copy it over ("vendor") and noticed this functionality was missing.

codecov-io: # [Codecov](https://codecov.io/gh/gonum/gonum/pull/1589?src=pr&el=h1) Report
> Merging [#1589](https://codecov.io/gh/gonum/gonum/pull/1589?src=pr&el=desc) (df087c7) into [master](https://codecov.io/gh/gonum/gonum/commit/a6ddba7f8db212e57ac2164ec4592a502d00b5cf?el=desc) (a6ddba7) will **not change** coverage.
> The diff coverage is `n/a`.

> :exclamation: Current head df087c7 differs from pull request most recent head 946a743. Consider uploading reports for the commit 946a743 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/gonum/gonum/pull/1589/graphs/tree.svg?width=650&height=150&src=pr&token=B7qHnqSPNL)](https://codecov.io/gh/gonum/gonum/pull/1589?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #1589   +/-   ##
=======================================
  Coverage   72.70%   72.70%           
=======================================
  Files         481      481           
  Lines       57968    57968           
=======================================
  Hits        42145    42145           
  Misses      13426    13426           
  Partials     2397     2397           
```


| [Impacted Files](https://codecov.io/gh/gonum/gonum/pull/1589?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [stat/mds/mds.go](https://codecov.io/gh/gonum/gonum/pull/1589/diff?src=pr&el=tree#diff-c3RhdC9tZHMvbWRzLmdv) | `88.88% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gonum/gonum/pull/1589?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gonum/gonum/pull/1589?src=pr&el=footer). Last update [a6ddba7...946a743](https://codecov.io/gh/gonum/gonum/pull/1589?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

y0ast: Thanks for the feedback, I've addressed it.

PTAL :)
y0ast: Aside from the right shift comment, this addresses the rest. Also rebased.

Patch:
diff --git a/internal/asm/f32/stubs_amd64.go b/internal/asm/f32/stubs_amd64.go
index 276377d5..1b3b70c0 100644
--- a/internal/asm/f32/stubs_amd64.go
+++ b/internal/asm/f32/stubs_amd64.go
@@ -66,3 +66,11 @@ func DotUnitary(x, y []float32) (sum float32)
 //  }
 //  return sum
 func DotInc(x, y []float32, n, incX, incY, ix, iy uintptr) (sum float32)
+
+// Sum is
+//  var sum float32
+//  for _, v := range x {
+// 		sum += v
+//  }
+//  return sum
+func Sum(x []float32) float32
diff --git a/internal/asm/f32/stubs_noasm.go b/internal/asm/f32/stubs_noasm.go
index e8571953..17ab71f6 100644
--- a/internal/asm/f32/stubs_noasm.go
+++ b/internal/asm/f32/stubs_noasm.go
@@ -111,3 +111,17 @@ func DdotInc(x, y []float32, n, incX, incY, ix, iy uintptr) (sum float64) {
 	}
 	return
 }
+
+// Sum is
+//  var sum float32
+//  for _, v := range x {
+//  	sum += v
+//  }
+//  return sum
+func Sum(x []float32) float32 {
+	var sum float32
+	for _, v := range x {
+		sum += v
+	}
+	return sum
+}
diff --git a/internal/asm/f32/sum_amd64.s b/internal/asm/f32/sum_amd64.s
new file mode 100644
index 00000000..42e96361
--- /dev/null
+++ b/internal/asm/f32/sum_amd64.s
@@ -0,0 +1,100 @@
+// Copyright ©2021 The Gonum Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !noasm,!gccgo,!safe
+
+#include "textflag.h"
+
+#define X_PTR SI
+#define IDX AX
+#define LEN CX
+#define TAIL BX
+#define SUM X0
+#define SUM_1 X1
+#define SUM_2 X2
+#define SUM_3 X3
+
+// func Sum(x []float32) float32
+TEXT ·Sum(SB), NOSPLIT, $0
+	MOVQ x_base+0(FP), X_PTR // X_PTR = &x
+	MOVQ x_len+8(FP), LEN    // LEN = len(x)
+	XORQ IDX, IDX            // i = 0
+	PXOR SUM, SUM            // p_sum_i = 0
+	CMPQ LEN, $0             // if LEN == 0 { return 0 }
+	JE   sum_end
+
+	PXOR SUM_1, SUM_1
+	PXOR SUM_2, SUM_2
+	PXOR SUM_3, SUM_3
+
+	MOVQ X_PTR, TAIL // Check memory alignment
+	ANDQ $15, TAIL   // TAIL = &x % 16
+	JZ   no_trim     // if TAIL == 0 { goto no_trim }
+	SUBQ $16, TAIL   // TAIL -= 16
+
+sum_align: // Align on 16-byte boundary do {
+	ADDSS (X_PTR)(IDX*4), SUM // SUM += x[0]
+	INCQ  IDX                 // i++
+	DECQ  LEN                 // LEN--
+	JZ    sum_end             // if LEN == 0 { return }
+	ADDQ  $4, TAIL            // TAIL += 4
+	JNZ   sum_align           // } while TAIL < 0
+
+no_trim:
+	MOVQ LEN, TAIL
+	SHRQ $4, LEN   // LEN = floor( n / 16 )
+	JZ   sum_tail8 // if LEN == 0 { goto sum_tail8 }
+
+
+sum_loop: // sum 16x wide do {
+	ADDPS (X_PTR)(IDX*4), SUM     // sum_i += x[i:i+4]
+	ADDPS 16(X_PTR)(IDX*4), SUM_1
+	ADDPS 32(X_PTR)(IDX*4), SUM_2
+	ADDPS 48(X_PTR)(IDX*4), SUM_3
+
+	ADDQ  $16, IDX                // i += 16
+	DECQ  LEN
+	JNZ   sum_loop                // } while --LEN > 0
+
+sum_tail8:
+	ADDPS SUM_3, SUM
+	ADDPS SUM_2, SUM_1
+
+	TESTQ $8, TAIL
+	JZ    sum_tail4
+
+	ADDPS (X_PTR)(IDX*4), SUM // sum_i += x[i:i+4]
+	ADDPS 16(X_PTR)(IDX*4), SUM_1
+	ADDQ  $8, IDX
+
+sum_tail4:
+	ADDPS SUM_1, SUM
+
+	TESTQ $4, TAIL
+	JZ    sum_tail2
+
+	ADDPS (X_PTR)(IDX*4), SUM // sum_i += x[i:i+4]
+	ADDQ  $4, IDX
+
+sum_tail2:
+	HADDPS SUM, SUM            // sum_i[:2] += sum_i[2:4]
+
+	TESTQ $2, TAIL
+	JZ    sum_tail1
+
+	MOVSD (X_PTR)(IDX*4), SUM_1 // reuse SUM_1
+	ADDPS SUM_1, SUM            // sum_i += x[i:i+2]
+	ADDQ  $2, IDX
+
+sum_tail1:
+	HADDPS SUM, SUM // sum_i[0] += sum_i[1]
+
+	TESTQ $1, TAIL
+	JZ    sum_end
+
+	ADDSS (X_PTR)(IDX*4), SUM
+
+sum_end: // return sum
+	MOVSS SUM, ret+24(FP)
+	RET


Test patch:
diff --git a/internal/asm/f32/stubs_test.go b/internal/asm/f32/stubs_test.go
index 18990fc5..6aae9835 100644
--- a/internal/asm/f32/stubs_test.go
+++ b/internal/asm/f32/stubs_test.go
@@ -150,3 +150,65 @@ func TestAxpyIncTo(t *testing.T) {
 		checkValidIncGuard(t, v.dst, 0, int(v.incDst), gdLn)
 	}
 }
+
+func TestSum(t *testing.T) {
+	var srcGd float32 = -1
+	for j, v := range []struct {
+		src    []float32
+		expect float32
+	}{
+		{
+			src:    []float32{},
+			expect: 0,
+		},
+		{
+			src:    []float32{1},
+			expect: 1,
+		},
+		{
+			src:    []float32{nan},
+			expect: nan,
+		},
+		{
+			src:    []float32{1, 2, 3},
+			expect: 6,
+		},
+		{
+			src:    []float32{1, -4, 3},
+			expect: 0,
+		},
+		{
+			src:    []float32{1, 2, 3, 4},
+			expect: 10,
+		},
+		{
+			src:    []float32{1, 1, nan, 1, 1},
+			expect: nan,
+		},
+		{
+			src:    []float32{inf, 4, nan, -inf, 9},
+			expect: nan,
+		},
+		{
+			src:    []float32{1, 1, 1, 1, 9, 1, 1, 1, 2, 1, 1, 1, 1, 1, 5, 1},
+			expect: 29,
+		},
+		{
+			src:    []float32{1, 1, 1, 1, 9, 1, 1, 1, 2, 1, 1, 1, 1, 1, 5, 11, 1, 1, 1, 9, 1, 1, 1, 2, 1, 1, 1, 1, 1, 5, 1},
+			expect: 67,
+		},
+	} {
+		for _, i := range [4]int{0, 1, 2, 3} {
+			gdLn := 4 + j%4 + i
+			gsrc := guardVector(v.src, srcGd, gdLn)
+			src := gsrc[gdLn : len(gsrc)-gdLn]
+			ret := Sum(src)
+			if !same(ret, v.expect) {
+				t.Errorf("Test %d Sum error Got: %v Expected: %v", j, ret, v.expect)
+			}
+			if !isValidGuard(gsrc, srcGd, gdLn) {
+				t.Errorf("Test %d Guard violated in src vector %v %v", j, gsrc[:gdLn], gsrc[len(gsrc)-gdLn:])
+			}
+		}
+	}
+}