Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions jitAudit/eaOffTest/src/D1EAOffSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ class D1EAOffSuite extends FunSuite:
assertAllocFreeWithEAOff("doublearrays.abs!")(arr.`abs!`)
}

// The control case: no Vector API in the body, so this cannot be on the software fallback path and its zero does not
// depend on either intrinsification or EA. If this one ever fails, the harness is wrong, not the kernel.
test("D1-EAOff: doublearrays.cumsum!") {
val arr = Array.fill(N)(1.0)
assertAllocFreeWithEAOff("doublearrays.cumsum!")(arr.`cumsum!`)
}

// ── Float ───────────────────────────────────────────────────────────────────

test("D1-EAOff: floatarrays.sumSIMD") {
Expand Down Expand Up @@ -258,6 +265,11 @@ class D1EAOffSuite extends FunSuite:
assertAllocFreeWithEAOff("floatarrays.*=(Float)")(arr *= 1.0f)
}

test("D1-EAOff: floatarrays.cumsum!") {
val arr = Array.fill(N)(1.0f)
assertAllocFreeWithEAOff("floatarrays.cumsum!")(arr.`cumsum!`)
}

// ── Int ─────────────────────────────────────────────────────────────────────

test("D1-EAOff: intarrays.sumSIMD") {
Expand Down
13 changes: 13 additions & 0 deletions jitAudit/test/src/D1Suite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ class D1Suite extends FunSuite:
}
}

// A prefix sum carries a dependency, so it cannot be vectorised and there is no Vector API in the body at all.
// That makes this the only @AllocFree kernel in the library whose zero is unconditional rather than contingent on
// C2 applying intrinsics — worth having measured precisely because it is the control case for all the others.
test("D1: doublearrays.cumsum!") {
val arr = Array.fill(N)(1.0)
assertAllocFree("doublearrays.cumsum!")(arr.`cumsum!`)
}

// ── Float ───────────────────────────────────────────────────────────────────

test("D1: floatarrays.sumSIMD") {
Expand Down Expand Up @@ -209,6 +217,11 @@ class D1Suite extends FunSuite:
assertAllocFree("floatarrays.*=(Float)")(arr *= 1.0f)
}

test("D1: floatarrays.cumsum!") {
val arr = Array.fill(N)(1.0f)
assertAllocFree("floatarrays.cumsum!")(arr.`cumsum!`)
}

// ── Int ─────────────────────────────────────────────────────────────────────

test("D1: intarrays.sumSIMD") {
Expand Down
42 changes: 36 additions & 6 deletions vecxt/src-jvm/doublearrays.scala
Original file line number Diff line number Diff line change
Expand Up @@ -937,35 +937,65 @@ object doublearrays:
maxVal + Math.log(sumExp)
end logSumExp

inline def `cumsum!`: Unit =
/** The one kernel in this file whose `@AllocFree` does not depend on intrinsification.
*
* A prefix sum carries a dependency — `vec(i)` needs the value just written to `vec(i - 1)` — so it cannot be
* vectorised and there is no Vector API here at all. Nothing to intrinsify, therefore nothing whose failure to
* intrinsify could leave a `DoubleVector` on the heap. Every other `@AllocFree` in this file is contingent on C2
* applying `VectorSupport` intrinsics; this one is true because the body allocates nothing, full stop.
*/
@HotPath
@AllocFree
def `cumsum!`: Unit =
var i = 1
while i < vec.length do
vec(i) = vec(i - 1) + vec(i)
i = i + 1
end while
end `cumsum!`

inline def cumsum: Array[Double] =
@Thin
def cumsum: Array[Double] =
val out = vec.clone()
out.`cumsum!`
out
end cumsum

inline def dot(v1: Array[Double]): Double =
/** `@Thin`, not `@HotPath`, and the distinction is the annotation's own wording.
*
* `@HotPath` describes "code that runs once per element". The per-element loop here is inside netlib's `ddot`, not
* in this method — what this method does is name the operation and dispatch to it, which is exactly `@Thin`'s
* definition. C3's no-backward-branch assertion is satisfied for the same reason, and would fail if someone ever
* open-coded the loop back into it, which is the right outcome.
*
* Not `@AllocFree`. `blas` is `JavaBLAS.getInstance`, so this is a call into a third-party pure-Java
* implementation. Whether it allocates is not visible from here and has never been measured, and the annotation
* exists to record measurements rather than expectations — the two that turned out to be false (`**!`, and
* `intarrays.dot`) were both applied by inspection. Same for `norm` and the array-argument `-=` below.
*/
@Thin
def dot(v1: Array[Double]): Double =
dimCheck(vec, v1)
blas.ddot(vec.length, vec, 1, v1, 1)
end dot

inline def norm: Double = blas.dnrm2(vec.length, vec, 1)
@Thin
def norm: Double = blas.dnrm2(vec.length, vec, 1)

inline def -(vec2: Array[Double]): Array[Double] =
@Thin
def -(vec2: Array[Double]): Array[Double] =
dimCheck(vec, vec2)
val out = vec.clone
out -= vec2
out
end -

inline def -=(vec2: Array[Double]): Unit =
/** Note the asymmetry with the scalar overload below, which is `@HotPath @AllocFree`: that one is a hand-written
* Vector API loop, this one delegates the whole operation to `daxpy`. Same name, different implementations, so
* different annotations. Not an inconsistency to tidy up.
*/
@Thin
def -=(vec2: Array[Double]): Unit =
dimCheck(vec, vec2)
blas.daxpy(vec.length, -1.0, vec2, 1, vec, 1)
end -=
Expand Down
30 changes: 24 additions & 6 deletions vecxt/src-jvm/floatarrays.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.reflect.ClassTag

import vecxt.annotations.AllocFree
import vecxt.annotations.HotPath
import vecxt.annotations.Thin

import vecxt.matrix.Matrix

Expand Down Expand Up @@ -640,12 +641,18 @@ object floatarrays:

inline def stdDev(mode: VarianceMode): Float = std(mode)

inline def dot(v1: Array[Float]): Float =
/** `@Thin` rather than `@HotPath`, and not `@AllocFree`. See the doublearrays twin for the reasoning: the
* per-element loop is inside netlib's `sdot`, not here, and whether that third-party code allocates has never been
* measured.
*/
@Thin
def dot(v1: Array[Float]): Float =
dimCheck(vec, v1)
blas.sdot(vec.length, vec, 1, v1, 1)
end dot

inline def norm: Float = blas.snrm2(vec.length, vec, 1)
@Thin
def norm: Float = blas.snrm2(vec.length, vec, 1)

@HotPath
def increments: Array[Float] =
Expand All @@ -669,15 +676,21 @@ object floatarrays:
out
end increments

inline def `cumsum!`: Unit =
/** A prefix sum carries a dependency, so it cannot be vectorised and there is no Vector API here at all. That makes
* this `@AllocFree` unconditional rather than contingent on C2 applying intrinsics — see the doublearrays twin.
*/
@HotPath
@AllocFree
def `cumsum!`: Unit =
var i = 1
while i < vec.length do
vec(i) = vec(i - 1) + vec(i)
i = i + 1
end while
end `cumsum!`

inline def cumsum: Array[Float] =
@Thin
def cumsum: Array[Float] =
val out = vec.clone()
out.`cumsum!`
out
Expand Down Expand Up @@ -706,14 +719,19 @@ object floatarrays:
(maxVal + Math.log(sumExp)).toFloat
end logSumExp

inline def -(vec2: Array[Float]): Array[Float] =
@Thin
def -(vec2: Array[Float]): Array[Float] =
dimCheck(vec, vec2)
val out = vec.clone
out -= vec2
out
end -

inline def -=(vec2: Array[Float]): Unit =
/** Asymmetric with the scalar overload, which is `@HotPath @AllocFree`: that one is a hand-written Vector API loop,
* this one delegates to `saxpy`. Same name, different implementations, different annotations.
*/
@Thin
def -=(vec2: Array[Float]): Unit =
dimCheck(vec, vec2)
blas.saxpy(vec.length, -1.0f, vec2, 1, vec, 1)
end -=
Expand Down
Loading