Skip to content

Phase C & D: unit-stride-innermost traversal and offset/length SIMD kernels for Double - #115

Open
Quafadas with Copilot wants to merge 8 commits into
mainfrom
copilot/layout-follow-ups-again
Open

Phase C & D: unit-stride-innermost traversal and offset/length SIMD kernels for Double#115
Quafadas with Copilot wants to merge 8 commits into
mainfrom
copilot/layout-follow-ups-again

Conversation

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Follow-up to the Layout extraction (#108/#109): 58 elementwise/strided loops across the matrix implementation walked rows outer / cols inner regardless of which axis was actually contiguous in memory, making every submatrix view cache-hostile and unvectorisable. Separately, all SIMD kernels in doublearrays.scala were whole-array only, so strided views could never reach them at all.

Unit-stride-innermost traversal (Phase C)

  • Added Layout.unitStrideAxis (0 = rows, 1 = cols, -1 = neither) and a shared inline Layout.foreach2D(inline body: (Int, Int) => Unit) helper that nests the loop so the contiguous axis is always innermost, for both source reads and column-major destination writes.
  • Rewrote all matching loops to route through foreach2D, across:
    • vecxt/src/doublematrix.scala, vecxt/src/MatrixInstance.scala (shared)
    • vecxt/src-jvm/floatmatrix.scala, vecxt/src-jvm/doublematrix.scala, vecxt/src-jvm/intmatrix.scala
    • vecxt/src-js/doublematrix.scala
    • vecxt/src-native/doublematrix_native.scala
  • Order-dependent loops (e.g. printMat) and already-fast-pathed/??? branches were left untouched.
// before: rows outer, cols inner — strides by colStride on every innermost step
var i = 0
while i < m.rows do
  var j = 0
  while j < m.cols do
    val idx = m.layout.linearIndex(i, j)
    ...
    j += 1
  i += 1

// after: contiguous axis innermost, regardless of which axis that is
m.layout.foreach2D { (i, j) =>
  val idx = m.layout.linearIndex(i, j)
  ...
}

Offset/length SIMD kernels (Phase D, Double/JVM only)

  • Added Layout.segmentCount / segmentLength / segmentStart — decomposes a layout with a unit-stride axis into its contiguous runs.
  • Added (from, len) and (d, from, len, dest, destFrom) overloads to vecxt/src-jvm/doublearrays.scala for sumSIMD, norm, multInPlace/*=, /=, +, -, /, so a single contiguous column/row segment of a view can go through BLAS/Vector-API kernels without first materialising the whole array. Whole-array methods are unchanged.
  • Added vecxt/test/src-jvm/doublearraysSegment.test.scala, validating each new overload against the equivalent whole-array kernel run on a sliced copy.

Deferred, not in scope for this PR: wiring DoubleMatrix's strided scalar-op branches (*=, *, /, +, -) to dispatch through these segment kernels, and the JMH crossover-size benchmarking called for in the issue. The call sites live in cross-platform shared code while the new kernels are intentionally JVM-only, so this needs a JVM-specific dispatch seam that doesn't yet exist — left as a follow-up rather than bolted on here.

Copilot AI linked an issue Aug 3, 2026 that may be closed by this pull request
Copilot AI and others added 2 commits August 3, 2026 11:28
Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>
Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement layout follow-up tasks from issue #108 Phase C & D: unit-stride-innermost traversal and offset/length SIMD kernels for Double Aug 3, 2026
Copilot AI requested a review from Quafadas August 3, 2026 11:43
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Bytecode audit (Tier 1) — ⚠️ no failures, 1 WARN

JDK 25.0.1 (major 25), OpenJDK 64-Bit Server VM

Vector lanes (DoubleVector.SPECIES_PREFERRED.length()): 8

threshold value provenance
MaxTrivialSize 6 discovered
MaxInlineSize 35 discovered
FreqInlineSize 325 discovered
MaxInlineLevel 15 discovered
InlineSmallCode 2500 discovered
NodeCountInliningCutoff 18000 assumed
HugeMethodLimit 8000 assumed

-XX:HugeMethodLimit= was rejected on the command line: a develop flag compiled out of this product build, so 8000 is taken from the HotSpot source and cannot be confirmed against the running JVM.

metric now baseline delta
cheatsheet methods 97
total bytes 42067 51103 -17.7%
distinct library ops 198 178 +11.2%
bytes per op 212.5 287.1 -26.0%
severity check at method detail
WARN C1 cheatsheet.scala:118 CheatsheetTest$.matrixRangeSlicing 5643 bytes, past 68% of HugeMethodLimit=8000 (assumed)
Annotated methods (168)
method annotations bytes budget used loop at
vecxt.intarrays$.$plus @Thin 24 69% of 35 no intarrays.scala:546
vecxt.intarrays$.$minus @Thin 24 69% of 35 no intarrays.scala:399
vecxt.NDArrayFloatOps$.compareGeneral @HotPath 195 60% of 325 yes ndarrayFloatOps.scala:67
vecxt.NDArrayFloatOps$.binaryOpGeneral @HotPath 195 60% of 325 yes ndarrayFloatOps.scala:20
vecxt.NDArrayIntOps$.compareGeneral @HotPath 186 57% of 325 yes ndarrayIntOps.scala:67
vecxt.NDArrayIntOps$.binaryOpGeneral @HotPath 186 57% of 325 yes ndarrayIntOps.scala:19
vecxt.NDArrayDoubleOps$.compareGeneral @HotPath 186 57% of 325 yes ndarrayDoubleOps.scala:74
vecxt.NDArrayDoubleOps$.binaryOpGeneral @HotPath 186 57% of 325 yes ndarrayDoubleOps.scala:24
vecxt.doublearrays$.clamp$bang @AllocFree @HotPath 180 55% of 325 yes doublearrays.scala:898
vecxt.matrix$Layout.linearIndex @Thin 19 54% of 35 no matrix.scala:48
vecxt.floatarrays$.clamp$bang @AllocFree @HotPath 172 53% of 325 yes floatarrays.scala:417
vecxt.NDArrayFloatOps$.compareScalarGeneral @HotPath 165 51% of 325 yes ndarrayFloatOps.scala:94
vecxt.NDArrayFloatOps$.binaryOpInPlaceGeneral @HotPath 162 50% of 325 yes ndarrayFloatOps.scala:119
vecxt.NDArrayDoubleOps$.compareScalarGeneral @HotPath 157 48% of 325 yes ndarrayDoubleOps.scala:102
vecxt.NDArrayIntOps$.compareScalarGeneral @HotPath 156 48% of 325 yes ndarrayIntOps.scala:94
vecxt.NDArrayIntOps$.binaryOpInPlaceGeneral @HotPath 153 47% of 325 yes ndarrayIntOps.scala:119
vecxt.NDArrayDoubleOps$.binaryOpInPlaceGeneral @HotPath 153 47% of 325 yes ndarrayDoubleOps.scala:131
vecxt.NDArrayIntOps$.unaryOpGeneral @HotPath 152 47% of 325 yes ndarrayIntOps.scala:42
vecxt.NDArrayDoubleOps$.unaryOpGeneral @HotPath 152 47% of 325 yes ndarrayDoubleOps.scala:48
vecxt.intarrays$.$minus @Thin 16 46% of 35 no intarrays.scala:519
vecxt.doublearrays$.fillLinspace @AllocFree @HotPath 133 41% of 325 yes doublearrays.scala:34
vecxt.intarrays$.increments @HotPath 121 37% of 325 yes intarrays.scala:219
vecxt.ndarray$.mkNDArray @Thin 13 37% of 35 no ndarray.scala:188
vecxt.doublearrays$.$div @AllocFree @HotPath 118 36% of 325 yes doublearrays.scala:1281
vecxt.doublearrays$.$plus @AllocFree @HotPath 116 36% of 325 yes doublearrays.scala:1035
vecxt.doublearrays$.$minus @AllocFree @HotPath 116 36% of 325 yes doublearrays.scala:1098
vecxt.doublearrays$.increments @HotPath 110 34% of 325 yes doublearrays.scala:397
vecxt.intarrays$.dot @AllocFree @HotPath 108 33% of 325 yes intarrays.scala:372
vecxt.doublearrays$.unary_$minus @HotPath 108 33% of 325 yes doublearrays.scala:172
vecxt.doublearrays$.tanh @HotPath 108 33% of 325 yes doublearrays.scala:365
vecxt.doublearrays$.tan @HotPath 108 33% of 325 yes doublearrays.scala:354
vecxt.doublearrays$.sqrt @HotPath 108 33% of 325 yes doublearrays.scala:317
vecxt.doublearrays$.sinh @HotPath 108 33% of 325 yes doublearrays.scala:339
vecxt.doublearrays$.sin @HotPath 108 33% of 325 yes doublearrays.scala:328
vecxt.doublearrays$.log1p @HotPath 108 33% of 325 yes doublearrays.scala:306
vecxt.doublearrays$.log10 @HotPath 108 33% of 325 yes doublearrays.scala:295
vecxt.doublearrays$.log @HotPath 108 33% of 325 yes doublearrays.scala:284
vecxt.doublearrays$.expm1 @HotPath 108 33% of 325 yes doublearrays.scala:273
vecxt.doublearrays$.exp @HotPath 108 33% of 325 yes doublearrays.scala:262
vecxt.doublearrays$.cosh @HotPath 108 33% of 325 yes doublearrays.scala:251
vecxt.doublearrays$.cos @HotPath 108 33% of 325 yes doublearrays.scala:240
vecxt.doublearrays$.cbrt @HotPath 108 33% of 325 yes doublearrays.scala:229
vecxt.doublearrays$.atan @HotPath 108 33% of 325 yes doublearrays.scala:218
vecxt.doublearrays$.asin @HotPath 108 33% of 325 yes doublearrays.scala:207
vecxt.doublearrays$.acos @HotPath 108 33% of 325 yes doublearrays.scala:196
vecxt.doublearrays$.abs @HotPath 108 33% of 325 yes doublearrays.scala:184
vecxt.intarrays$.$less @HotPath 106 33% of 325 yes intarrays.scala:48
vecxt.intarrays$.$less$eq @HotPath 106 33% of 325 yes intarrays.scala:52
vecxt.intarrays$.$greater @HotPath 106 33% of 325 yes intarrays.scala:56
vecxt.intarrays$.$greater$eq @HotPath 106 33% of 325 yes intarrays.scala:60
vecxt.intarrays$.$eq$colon$eq @HotPath 106 33% of 325 yes intarrays.scala:40
vecxt.intarrays$.$bang$colon$eq @HotPath 106 33% of 325 yes intarrays.scala:44
vecxt.floatarrays$.unary_$minus @HotPath 104 32% of 325 yes floatarrays.scala:114
vecxt.floatarrays$.tanh @HotPath 104 32% of 325 yes floatarrays.scala:303
vecxt.floatarrays$.tan @HotPath 104 32% of 325 yes floatarrays.scala:292
vecxt.floatarrays$.sqrt @HotPath 104 32% of 325 yes floatarrays.scala:259
vecxt.floatarrays$.sinh @HotPath 104 32% of 325 yes floatarrays.scala:281
vecxt.floatarrays$.sin @HotPath 104 32% of 325 yes floatarrays.scala:270
vecxt.floatarrays$.log1p @HotPath 104 32% of 325 yes floatarrays.scala:248
vecxt.floatarrays$.log10 @HotPath 104 32% of 325 yes floatarrays.scala:237
vecxt.floatarrays$.log @HotPath 104 32% of 325 yes floatarrays.scala:226
vecxt.floatarrays$.expm1 @HotPath 104 32% of 325 yes floatarrays.scala:215
vecxt.floatarrays$.exp @HotPath 104 32% of 325 yes floatarrays.scala:204
vecxt.floatarrays$.cosh @HotPath 104 32% of 325 yes floatarrays.scala:193
vecxt.floatarrays$.cos @HotPath 104 32% of 325 yes floatarrays.scala:182
vecxt.floatarrays$.cbrt @HotPath 104 32% of 325 yes floatarrays.scala:171
vecxt.floatarrays$.atan @HotPath 104 32% of 325 yes floatarrays.scala:160
vecxt.floatarrays$.asin @HotPath 104 32% of 325 yes floatarrays.scala:149
vecxt.floatarrays$.acos @HotPath 104 32% of 325 yes floatarrays.scala:138
vecxt.floatarrays$.abs @HotPath 104 32% of 325 yes floatarrays.scala:126
vecxt.intarrays$.mean @Thin 11 31% of 35 no intarrays.scala:287
vecxt.doublearrays$.sumSIMD @AllocFree @HotPath 102 31% of 325 yes doublearrays.scala:703
vecxt.doublearrays$.tanh$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.tan$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.sqrt$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.sinh$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.sin$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.log1p$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.log10$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.log$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.expm1$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.exp$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.cosh$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.cos$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.cbrt$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.atan$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.asin$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.acos$bang @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.abs$bang @AllocFree @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.doublearrays$.$minus$bang @AllocFree @HotPath 98 30% of 325 yes doublearrays.scala:151
vecxt.floatarrays$.increments @HotPath 97 30% of 325 yes floatarrays.scala:652
vecxt.doublearrays$.$times$times$bang @HotPath 95 29% of 325 yes doublearrays.scala:372
vecxt.intarrays$.$less @HotPath 94 29% of 325 yes intarrays.scala:139
vecxt.intarrays$.$less$eq @HotPath 94 29% of 325 yes intarrays.scala:143
vecxt.intarrays$.$greater @HotPath 94 29% of 325 yes intarrays.scala:147
vecxt.intarrays$.$greater$eq @HotPath 94 29% of 325 yes intarrays.scala:151
vecxt.intarrays$.$eq$colon$eq @HotPath 94 29% of 325 yes intarrays.scala:131
vecxt.intarrays$.$bang$colon$eq @HotPath 94 29% of 325 yes intarrays.scala:135
vecxt.floatarrays$.tanh$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.tan$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.sqrt$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.sinh$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.sin$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.log1p$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.log10$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.log$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.expm1$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.exp$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.cosh$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.cos$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.cbrt$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.atan$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.asin$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.acos$bang @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.abs$bang @AllocFree @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.floatarrays$.$minus$bang @AllocFree @HotPath 93 29% of 325 yes floatarrays.scala:94
vecxt.intarrays$.variance @Thin 10 29% of 35 no intarrays.scala:304
vecxt.intarrays$.std @Thin 10 29% of 35 no intarrays.scala:361
vecxt.doublearrays$.variance @AllocFree @Thin 10 29% of 35 no doublearrays.scala:541
vecxt.doublearrays$.$minus$eq @AllocFree @HotPath 92 28% of 325 yes doublearrays.scala:1145
vecxt.doublearrays$.$plus$eq @AllocFree @HotPath 90 28% of 325 yes doublearrays.scala:1057
vecxt.doublearrays$.$times$eq @AllocFree @HotPath 88 27% of 325 yes doublearrays.scala:1220
vecxt.doublearrays$.productSIMD @AllocFree @HotPath 86 26% of 325 yes doublearrays.scala:726
vecxt.floatarrays$.$times$times$bang @HotPath 85 26% of 325 yes floatarrays.scala:314
vecxt.doublearrays$.sumSIMD @AllocFree @HotPath 85 26% of 325 yes doublearrays.scala:677
vecxt.doublearrays$.fma$bang @AllocFree @HotPath 85 26% of 325 yes doublearrays.scala:1120
vecxt.intarrays$.$plus$eq @AllocFree @HotPath 84 26% of 325 yes intarrays.scala:555
vecxt.intarrays$.$minus$eq @AllocFree @HotPath 84 26% of 325 yes intarrays.scala:527
vecxt.floatarrays$.$times$eq @AllocFree @HotPath 84 26% of 325 yes floatarrays.scala:874
vecxt.floatarrays$.$plus$eq @AllocFree @HotPath 84 26% of 325 yes floatarrays.scala:772
vecxt.floatarrays$.$minus$eq @AllocFree @HotPath 84 26% of 325 yes floatarrays.scala:812
vecxt.ndarrayOps.expandDims @Thin 9 26% of 35 no ndarrayOps.scala
vecxt.intarrays.stdDev @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.meanAndVariance @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.lte @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.lte @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.lt @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.lt @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.gte @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.gte @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.gt @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays.gt @Thin 9 26% of 35 no intarrays.scala
vecxt.intarrays$.variance @Thin 9 26% of 35 no intarrays.scala:291
vecxt.intarrays$.stdDev @Thin 9 26% of 35 no intarrays.scala:364
vecxt.intarrays$.std @Thin 9 26% of 35 no intarrays.scala:357
vecxt.intarrays$.meanAndVariance @Thin 9 26% of 35 no intarrays.scala:308
vecxt.doublearrays.meanAndVariance @Thin 9 26% of 35 no doublearrays.scala
vecxt.doublearrays$.meanAndVariance @Thin 9 26% of 35 no doublearrays.scala:555
vecxt.intarrays$.minSIMD @AllocFree @HotPath 82 25% of 325 yes intarrays.scala:575
vecxt.intarrays$.maxSIMD @AllocFree @HotPath 82 25% of 325 yes intarrays.scala:595
vecxt.floatarrays$.productSIMD @AllocFree @HotPath 82 25% of 325 yes floatarrays.scala:537
vecxt.intarrays$.sumSIMD @AllocFree @HotPath 81 25% of 325 yes intarrays.scala:265
vecxt.floatarrays$.sumSIMD @AllocFree @HotPath 81 25% of 325 yes floatarrays.scala:516
vecxt.floatarrays$.fma$bang @AllocFree @HotPath 80 25% of 325 yes floatarrays.scala:340
vecxt.floatarrays$.$times$eq @AllocFree @HotPath 78 24% of 325 yes floatarrays.scala:926
vecxt.ndarray.shapeArray @Thin 8 23% of 35 no ndarray.scala
vecxt.matrix$Matrix.rows @Thin 8 23% of 35 no matrix.scala:192
vecxt.matrix$Matrix.rowStride @Thin 8 23% of 35 no matrix.scala:198
vecxt.matrix$Matrix.offset @Thin 8 23% of 35 no matrix.scala:204
vecxt.matrix$Matrix.numel @Thin 8 23% of 35 no matrix.scala:207
vecxt.matrix$Matrix.isDenseRowMajor @Thin 8 23% of 35 no matrix.scala:213
vecxt.matrix$Matrix.isDenseColMajor @Thin 8 23% of 35 no matrix.scala:210
vecxt.matrix$Matrix.hasSimpleContiguousMemoryLayout @Thin 8 23% of 35 no matrix.scala:225
vecxt.matrix$Matrix.cols @Thin 8 23% of 35 no matrix.scala:195
vecxt.matrix$Matrix.colStride @Thin 8 23% of 35 no matrix.scala:201
vecxt.intarrays$.countsToIdx @HotPath 70 22% of 325 yes intarrays.scala:244
vecxt.intarrays$.$minus$eq @AllocFree @HotPath 67 21% of 325 yes intarrays.scala:408
vecxt.floatarrays$.$plus$eq @AllocFree @HotPath 24 7% of 325 no floatarrays.scala:731
Method sizes
band methods
<= 6 (trivial, always inlined) 1411
7-35 (inlinable cold) 3589
36-325 (inlinable when hot) 691
326-8000 (not inlined) 105
> 8000 (NEVER JIT COMPILED) 0
bytes method module at
5643 CheatsheetTest$.matrixRangeSlicing experiments cheatsheet.scala:118
3815 CheatsheetTest$.matrixReverseSlicing experiments cheatsheet.scala:125
3801 CheatsheetTest$.ndArrayInt experiments cheatsheet.scala:416
3779 CheatsheetTest$.ndArrayBoolean experiments cheatsheet.scala:430
3061 CheatsheetTest$.ndArrayFloat experiments cheatsheet.scala:395
3015 CheatsheetTest$.ndArrayFloatReductions experiments cheatsheet.scala:405
2811 CheatsheetTest$.matrixCreation experiments cheatsheet.scala:72
2597 CheatsheetTest$.matrixOps experiments cheatsheet.scala:167
2500 vecxt_re.Tower.show vecxt_re Tower.scala:63
1896 vecxt.JvmFloatMatrix$.floatmatrixSubVector vecxt floatmatrix.scala:300
1594 vecxt.ndarrayOps$.apply vecxt ndarrayOps.scala:452
1581 vecxt.JvmDoubleMatrix$.$plus$eq vecxt doublematrix.scala:197
1557 vecxt.JvmFloatMatrix$.floatmatrixAddVectorInPlace vecxt floatmatrix.scala:218
1557 vecxt.JvmFloatMatrix$.floatmatrixSubVectorInPlace vecxt floatmatrix.scala:318
1460 CheatsheetTest$.indexingAndSlicing experiments cheatsheet.scala:98
1435 vecxt.JvmDoubleMatrix$.$plus$eq vecxt doublematrix.scala:279
1420 vecxt.JvmFloatMatrix$.floatmatrixAddScalarInPlace vecxt floatmatrix.scala:394
1420 vecxt.JvmFloatMatrix$.floatmatrixSubScalarInPlace vecxt floatmatrix.scala:466
1308 CheatsheetTest$.arrayManipulation experiments cheatsheet.scala:305
1122 vecxt.Svd$.pinv vecxt svd.scala:42
1028 CheatsheetTest$.matrixFloat experiments cheatsheet.scala:445
969 vecxt_re.Scenarr$.combine vecxt_re scenarr.scala:160
923 CheatsheetTest$.matrixInt experiments cheatsheet.scala:461
908 vecxt.Svd$.svd vecxt svd.scala:135
896 vecxt_re.NegativeBinomial$.volweightedMle vecxt_re NegativeBinomial.scala:281
Proposed baseline
{
  "jdkMajor": 25,
  "c9": { "totalBytes": 42067, "distinctOps": 198 },
  "annotated": {
    "vecxt.NDArrayDoubleOps$.binaryOpGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)Lvecxt/ndarray$NDArray;": 186,
    "vecxt.NDArrayDoubleOps$.binaryOpInPlaceGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)V": 153,
    "vecxt.NDArrayDoubleOps$.compareGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)Lvecxt/ndarray$NDArray;": 186,
    "vecxt.NDArrayDoubleOps$.compareScalarGeneral(Lvecxt/ndarray$NDArray;DLscala/Function2;)Lvecxt/ndarray$NDArray;": 157,
    "vecxt.NDArrayDoubleOps$.unaryOpGeneral(Lvecxt/ndarray$NDArray;Lscala/Function1;)Lvecxt/ndarray$NDArray;": 152,
    "vecxt.NDArrayFloatOps$.binaryOpGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)Lvecxt/ndarray$NDArray;": 195,
    "vecxt.NDArrayFloatOps$.binaryOpInPlaceGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)V": 162,
    "vecxt.NDArrayFloatOps$.compareGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)Lvecxt/ndarray$NDArray;": 195,
    "vecxt.NDArrayFloatOps$.compareScalarGeneral(Lvecxt/ndarray$NDArray;FLscala/Function2;)Lvecxt/ndarray$NDArray;": 165,
    "vecxt.NDArrayIntOps$.binaryOpGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)Lvecxt/ndarray$NDArray;": 186,
    "vecxt.NDArrayIntOps$.binaryOpInPlaceGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)V": 153,
    "vecxt.NDArrayIntOps$.compareGeneral(Lvecxt/ndarray$NDArray;Lvecxt/ndarray$NDArray;Lscala/Function2;)Lvecxt/ndarray$NDArray;": 186,
    "vecxt.NDArrayIntOps$.compareScalarGeneral(Lvecxt/ndarray$NDArray;ILscala/Function2;)Lvecxt/ndarray$NDArray;": 156,
    "vecxt.NDArrayIntOps$.unaryOpGeneral(Lvecxt/ndarray$NDArray;Lscala/Function1;)Lvecxt/ndarray$NDArray;": 152,
    "vecxt.doublearrays$.$div([DDII[DI)V": 118,
    "vecxt.doublearrays$.$minus$bang([D)V": 98,
    "vecxt.doublearrays$.$minus$eq([DD)V": 92,
    "vecxt.doublearrays$.$minus([DDII[DI)V": 116,
    "vecxt.doublearrays$.$plus$eq([DD)V": 90,
    "vecxt.doublearrays$.$plus([DDII[DI)V": 116,
    "vecxt.doublearrays$.$times$eq([D[D)V": 88,
    "vecxt.doublearrays$.$times$times$bang([DD)V": 95,
    "vecxt.doublearrays$.abs$bang([D)V": 98,
    "vecxt.doublearrays$.abs([D)[D": 108,
    "vecxt.doublearrays$.acos$bang([D)V": 98,
    "vecxt.doublearrays$.acos([D)[D": 108,
    "vecxt.doublearrays$.asin$bang([D)V": 98,
    "vecxt.doublearrays$.asin([D)[D": 108,
    "vecxt.doublearrays$.atan$bang([D)V": 98,
    "vecxt.doublearrays$.atan([D)[D": 108,
    "vecxt.doublearrays$.cbrt$bang([D)V": 98,
    "vecxt.doublearrays$.cbrt([D)[D": 108,
    "vecxt.doublearrays$.clamp$bang([DDD)V": 180,
    "vecxt.doublearrays$.cos$bang([D)V": 98,
    "vecxt.doublearrays$.cos([D)[D": 108,
    "vecxt.doublearrays$.cosh$bang([D)V": 98,
    "vecxt.doublearrays$.cosh([D)[D": 108,
    "vecxt.doublearrays$.exp$bang([D)V": 98,
    "vecxt.doublearrays$.exp([D)[D": 108,
    "vecxt.doublearrays$.expm1$bang([D)V": 98,
    "vecxt.doublearrays$.expm1([D)[D": 108,
    "vecxt.doublearrays$.fillLinspace([DDD)V": 133,
    "vecxt.doublearrays$.fma$bang([DDD)V": 85,
    "vecxt.doublearrays$.increments([D)[D": 110,
    "vecxt.doublearrays$.log$bang([D)V": 98,
    "vecxt.doublearrays$.log([D)[D": 108,
    "vecxt.doublearrays$.log10$bang([D)V": 98,
    "vecxt.doublearrays$.log10([D)[D": 108,
    "vecxt.doublearrays$.log1p$bang([D)V": 98,
    "vecxt.doublearrays$.log1p([D)[D": 108,
    "vecxt.doublearrays$.meanAndVariance([D)Lvecxt/MeanAndVariance;": 9,
    "vecxt.doublearrays$.productSIMD([D)D": 86,
    "vecxt.doublearrays$.sin$bang([D)V": 98,
    "vecxt.doublearrays$.sin([D)[D": 108,
    "vecxt.doublearrays$.sinh$bang([D)V": 98,
    "vecxt.doublearrays$.sinh([D)[D": 108,
    "vecxt.doublearrays$.sqrt$bang([D)V": 98,
    "vecxt.doublearrays$.sqrt([D)[D": 108,
    "vecxt.doublearrays$.sumSIMD([D)D": 85,
    "vecxt.doublearrays$.sumSIMD([DII)D": 102,
    "vecxt.doublearrays$.tan$bang([D)V": 98,
    "vecxt.doublearrays$.tan([D)[D": 108,
    "vecxt.doublearrays$.tanh$bang([D)V": 98,
    "vecxt.doublearrays$.tanh([D)[D": 108,
    "vecxt.doublearrays$.unary_$minus([D)[D": 108,
    "vecxt.doublearrays$.variance([DLvecxt/VarianceMode;)D": 10,
    "vecxt.doublearrays.meanAndVariance([DLvecxt/VarianceMode;)Lvecxt/MeanAndVariance;": 9,
    "vecxt.floatarrays$.$minus$bang([F)V": 93,
    "vecxt.floatarrays$.$minus$eq([FF)V": 84,
    "vecxt.floatarrays$.$plus$eq([FF)V": 84,
    "vecxt.floatarrays$.$plus$eq([F[F)V": 24,
    "vecxt.floatarrays$.$times$eq([FF)V": 78,
    "vecxt.floatarrays$.$times$eq([F[F)V": 84,
    "vecxt.floatarrays$.$times$times$bang([FF)V": 85,
    "vecxt.floatarrays$.abs$bang([F)V": 93,
    "vecxt.floatarrays$.abs([F)[F": 104,
    "vecxt.floatarrays$.acos$bang([F)V": 93,
    "vecxt.floatarrays$.acos([F)[F": 104,
    "vecxt.floatarrays$.asin$bang([F)V": 93,
    "vecxt.floatarrays$.asin([F)[F": 104,
    "vecxt.floatarrays$.atan$bang([F)V": 93,
    "vecxt.floatarrays$.atan([F)[F": 104,
    "vecxt.floatarrays$.cbrt$bang([F)V": 93,
    "vecxt.floatarrays$.cbrt([F)[F": 104,
    "vecxt.floatarrays$.clamp$bang([FFF)V": 172,
    "vecxt.floatarrays$.cos$bang([F)V": 93,
    "vecxt.floatarrays$.cos([F)[F": 104,
    "vecxt.floatarrays$.cosh$bang([F)V": 93,
    "vecxt.floatarrays$.cosh([F)[F": 104,
    "vecxt.floatarrays$.exp$bang([F)V": 93,
    "vecxt.floatarrays$.exp([F)[F": 104,
    "vecxt.floatarrays$.expm1$bang([F)V": 93,
    "vecxt.floatarrays$.expm1([F)[F": 104,
    "vecxt.floatarrays$.fma$bang([FFF)V": 80,
    "vecxt.floatarrays$.increments([F)[F": 97,
    "vecxt.floatarrays$.log$bang([F)V": 93,
    "vecxt.floatarrays$.log([F)[F": 104,
    "vecxt.floatarrays$.log10$bang([F)V": 93,
    "vecxt.floatarrays$.log10([F)[F": 104,
    "vecxt.floatarrays$.log1p$bang([F)V": 93,
    "vecxt.floatarrays$.log1p([F)[F": 104,
    "vecxt.floatarrays$.productSIMD([F)F": 82,
    "vecxt.floatarrays$.sin$bang([F)V": 93,
    "vecxt.floatarrays$.sin([F)[F": 104,
    "vecxt.floatarrays$.sinh$bang([F)V": 93,
    "vecxt.floatarrays$.sinh([F)[F": 104,
    "vecxt.floatarrays$.sqrt$bang([F)V": 93,
    "vecxt.floatarrays$.sqrt([F)[F": 104,
    "vecxt.floatarrays$.sumSIMD([F)F": 81,
    "vecxt.floatarrays$.tan$bang([F)V": 93,
    "vecxt.floatarrays$.tan([F)[F": 104,
    "vecxt.floatarrays$.tanh$bang([F)V": 93,
    "vecxt.floatarrays$.tanh([F)[F": 104,
    "vecxt.floatarrays$.unary_$minus([F)[F": 104,
    "vecxt.intarrays$.$bang$colon$eq([II)[Z": 94,
    "vecxt.intarrays$.$bang$colon$eq([I[I)[Z": 106,
    "vecxt.intarrays$.$eq$colon$eq([II)[Z": 94,
    "vecxt.intarrays$.$eq$colon$eq([I[I)[Z": 106,
    "vecxt.intarrays$.$greater$eq([II)[Z": 94,
    "vecxt.intarrays$.$greater$eq([I[I)[Z": 106,
    "vecxt.intarrays$.$greater([II)[Z": 94,
    "vecxt.intarrays$.$greater([I[I)[Z": 106,
    "vecxt.intarrays$.$less$eq([II)[Z": 94,
    "vecxt.intarrays$.$less$eq([I[I)[Z": 106,
    "vecxt.intarrays$.$less([II)[Z": 94,
    "vecxt.intarrays$.$less([I[I)[Z": 106,
    "vecxt.intarrays$.$minus$eq([II)V": 67,
    "vecxt.intarrays$.$minus$eq([I[I)V": 84,
    "vecxt.intarrays$.$minus([II)[I": 16,
    "vecxt.intarrays$.$minus([I[I)[I": 24,
    "vecxt.intarrays$.$plus$eq([I[I)V": 84,
    "vecxt.intarrays$.$plus([I[I)[I": 24,
    "vecxt.intarrays$.countsToIdx([I)[I": 70,
    "vecxt.intarrays$.dot([I[I)I": 108,
    "vecxt.intarrays$.increments([I)[I": 121,
    "vecxt.intarrays$.maxSIMD([I)I": 82,
    "vecxt.intarrays$.mean([I)D": 11,
    "vecxt.intarrays$.meanAndVariance([I)Lvecxt/MeanAndVariance;": 9,
    "vecxt.intarrays$.minSIMD([I)I": 82,
    "vecxt.intarrays$.std([I)D": 9,
    "vecxt.intarrays$.std([ILvecxt/VarianceMode;)D": 10,
    "vecxt.intarrays$.stdDev([I)D": 9,
    "vecxt.intarrays$.sumSIMD([I)I": 81,
    "vecxt.intarrays$.variance([I)D": 9,
    "vecxt.intarrays$.variance([ILvecxt/VarianceMode;)D": 10,
    "vecxt.intarrays.gt([II)[Z": 9,
    "vecxt.intarrays.gt([I[I)[Z": 9,
    "vecxt.intarrays.gte([II)[Z": 9,
    "vecxt.intarrays.gte([I[I)[Z": 9,
    "vecxt.intarrays.lt([II)[Z": 9,
    "vecxt.intarrays.lt([I[I)[Z": 9,
    "vecxt.intarrays.lte([II)[Z": 9,
    "vecxt.intarrays.lte([I[I)[Z": 9,
    "vecxt.intarrays.meanAndVariance([ILvecxt/VarianceMode;)Lvecxt/MeanAndVariance;": 9,
    "vecxt.intarrays.stdDev([ILvecxt/VarianceMode;)D": 9,
    "vecxt.matrix$Layout.linearIndex(II)I": 19,
    "vecxt.matrix$Matrix.colStride()I": 8,
    "vecxt.matrix$Matrix.cols()I": 8,
    "vecxt.matrix$Matrix.hasSimpleContiguousMemoryLayout()Z": 8,
    "vecxt.matrix$Matrix.isDenseColMajor()Z": 8,
    "vecxt.matrix$Matrix.isDenseRowMajor()Z": 8,
    "vecxt.matrix$Matrix.numel()I": 8,
    "vecxt.matrix$Matrix.offset()I": 8,
    "vecxt.matrix$Matrix.rowStride()I": 8,
    "vecxt.matrix$Matrix.rows()I": 8,
    "vecxt.ndarray$.mkNDArray(Ljava/lang/Object;[I[II)Lvecxt/ndarray$NDArray;": 13,
    "vecxt.ndarray.shapeArray(Lvecxt/ndarray$NDArray;)[I": 8,
    "vecxt.ndarrayOps.expandDims(Lvecxt/ndarray$NDArray;I)Lvecxt/ndarray$NDArray;": 9
  }
}

@Quafadas

Quafadas commented Aug 4, 2026

Copy link
Copy Markdown
Owner

@copilot The extra kernels you've added should also be annotated @hotpath or @AllocFree ? Please annotate as appropriate

…it tests

Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>

Copilot AI commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot The extra kernels you've added should also be annotated @hotpath or @AllocFree ? Please annotate as appropriate

Added @HotPath/@AllocFree to the self-contained segment kernels (sumSIMD(from, len), +/-// with (d, from, len, dest, destFrom)). The inline def segment overloads (norm, multInPlace, *=, /=) are left un-annotated — the bytecode audit's A1 check fails if these annotations are placed on an inline def, since inline bodies are erased before bytecode emission and there's nothing for the audit to measure. Also added corresponding D1Suite alloc-free tests in jitAudit for the newly annotated kernels.

@Quafadas

Quafadas commented Aug 4, 2026

Copy link
Copy Markdown
Owner

@copilot

Blocker 1 — the segment / kernel isn't self-consistent

val recip = DoubleVector.broadcast(spd, 1.0 / d)
while i < bound do  ... .mul(recip) ...        // reciprocal multiply
while i < end do    dest(i + shift) = vec(i) / d   // true division

Vector body multiplies by 1/d, scalar tail divides. So within one call, whether an element gets x * (1/d) or x / d depends on its index modulo the lane count. For d = 3.0 and the test's own backing values, indices whose value is 5, 7, 10, 14, 17, 20, 23, 25, 28… differ by 1 ulp between the two forms. I checked this rather than asserting it:

mismatch values for d=3: [5, 7, 10, 14, 17, 20, 23, 25, 28, 31, ...]

The new test doesn't catch it because it picks d = 2.0, where the reciprocal is exact. Change the divisor to 3.0, assert exact equality, and it fails — the tail values in that test are 26, 27, 28, and 28 is in the mismatch set.

Fix is one line (tail multiplies by the same scalar reciprocal), and it aligns with whole-array /=, which is already dscal(1.0/d) throughout. Worth noting the pre-existing consequence too: JVM /= is reciprocal-based while JS/Native do true division, so goal #3 is already slightly violated there. That's a separate issue, but this PR shouldn't add an intra-array version of the same problem.

Blocker 2 — publishing JVM-only public API with no callers

The PR explicitly defers wiring the segment kernels ("needs a JVM-specific dispatch seam that doesn't yet exist"). So we'd be adding ~8 public extension overloads to doublearrays that:

exist on JVM only, so any user who writes against them breaks their cross-platform build,
have no in-tree consumer, so nothing exercises them beyond the unit tests.

Add a scala.compiletime.error in their cross platform equivalents.

Blocker 3

Layout property test (op(view) == op(copy)), which will go red on maximum — fix that too.

The layout space is small and structured, so enumerate it

The interesting dimension is layout kind, not layout values, and the kinds are countable:

// (rows, cols, rowStride, colStride, offset, padding)
val corpus = for
  (r, c)   <- List((1,1), (1,4), (4,1), (2,3), (3,2), (3,3))
  kind     <- List(RowMajor, ColMajor, RowMajorPadded, ColMajorPadded, DoublyStrided)
  offset   <- List(0, 1, 7)
yield mkLayout(r, c, kind, offset)

That's ~90 cases, runs in milliseconds on all three platforms, and — the bit that matters for goal #3 — JVM, JS and Native all run the identical corpus. A random generator gives each platform a different sample unless you pin the seed, which quietly weakens "the same tests pass everywhere" into "some tests passed everywhere." For a cross-platform consistency gate that's the wrong trade.

Deterministic also means a failure is a failure, not a Tuesday. An intermittent red on main from an unlucky seed is corrosive in a way that's hard to walk back.

The oracle matters far more than the randomness

This is the part I'd focus your energy on. The property is only worth anything if the reference side is written independently of the code under test. If materialise routes through foreach2D or the same linearIndex loops, you're comparing the implementation against itself and the test is vacuous — it would have passed happily on main with sqrt! applying twice.

So the oracle should be a deliberately stupid model, in the test source, that shares nothing with vecxt:

// model: logical (i,j) -> value, nothing but arithmetic
def model[A](m: Matrix[A]): (Int, Int) => A =
  (i, j) => m.raw(m.layout.offset + i * m.layout.rowStride + j * m.layout.colStride)

// property: for every op, every layout
forEachLayout { lay =>
  val view = Matrix(backing, lay)
  val got  = op(view)
  val want = /* op applied elementwise via model, into a fresh dense matrix */
  assertLogicallyEqual(got, want)   // compares (i,j) -> value, NOT raw arrays
}```

Two details that do the real work:

Compare logically, not rawly. assertLogicallyEqual walks (i, j) through each side's own layout. Compare raw arrays and you'll false-positive on the maximum row/col-major bug in the wrong direction — or miss it.
Non-square, distinct values. Use Array.tabulate(i => (i + 1).toDouble). Distinctness is what makes a transposition visible; random doubles are also distinct but the failure message is unreadable, and Array.fill(1.0) would hide maximum entirely. Non-square is what makes a transposed shape an error rather than a silent value swap.

@Quafadas
Quafadas marked this pull request as ready for review August 4, 2026 14:35
Copilot AI and others added 2 commits August 4, 2026 14:37
…-ups-again

# Conflicts:
#	vecxt/src-jvm/doublearrays.scala
#	vecxt/src/doublematrix.scala

Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>
…pus test

Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>

Copilot AI commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot

Blocker 1 — the segment / kernel isn't self-consistent

...

All three addressed:

  1. Segment / scalar tail now multiplies by the same recipScalar as the vector body — fixed the intra-call inconsistency. Test divisor changed to 3.0 as you suggested.

  2. Added scala.compiletime.error stubs for all 8 JVM-only segment overloads on JS/Native, following the existing pattern used for the other JVM-only linear algebra ops.

  3. Added LayoutCorpusSuite per your spec — enumerated (dims × kinds × offsets) corpus, independent oracle (no foreach2D/linearIndex), logical (i,j) comparison, non-square distinct values. It caught two real bugs while I was writing it:

    • DoubleMatrix.maximum's dense fast path wrote into the output array assuming column-major order even when the input's own dense layout was row-major — fixed to wrap with m.layout, same pattern as +:+.
    • DoubleMatrix./ (scalar)'s foreach2D branch did true division while its fast path does reciprocal-multiply via dscal — same class of bug as blocker 1, just in a different method. Aligned both to reciprocal-multiply.

    A pre-existing test in matrix.test.scala had expected values baked to the old buggy maximum output — updated it to build the expectation via fromRows so it's unambiguous regardless of the result's backing order.

Also rebased onto main (picking up #123/#124) and extended the same foreach2D traversal to sqrt!/sin!/cos!, which main had reverted to plain while-loops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Layout Follow Ups

2 participants