Skip to content

Extract Matrix[A] layout fields into Layout value class (Phase 1 + 2) - #109

Merged
Quafadas merged 5 commits into
mainfrom
copilot/extract-matrix-layout-fields
Aug 3, 2026
Merged

Extract Matrix[A] layout fields into Layout value class (Phase 1 + 2)#109
Quafadas merged 5 commits into
mainfrom
copilot/extract-matrix-layout-fields

Conversation

Copilot AI commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Matrix[A] carried five layout primitives and four derived vals as flat class members, with ~50 hand-copied index-arithmetic expressions and ~13 verbatim 5-positional-Int constructions scattered across the codebase — a silent transpose bug waiting to happen.

Phase 1 — Layout extraction (vecxt/src/matrix.scala)

  • New final class Layout with 7 fields (rows, cols, rowStride, colStride, offset, dataLength, kind) and derived vals (numel, isDenseColMajor, isDenseRowMajor, hasSimpleContiguousMemoryLayout). final class (not case class, not sealed trait) keeps getters at 5 bytes — under MaxTrivialSize(6).
  • @Thin inline def linearIndex(row, col) — replaces all hand-written offset + i*rowStride + j*colStride. Measured < 35 bytes (MaxInlineSize).
  • Layout.transpose, Layout.withDataLength, Layout.sameElementOrderAs derived operations.
  • hasSimpleContiguousMemoryLayout now baked into Layout via dataLength == numel — removes the last generic Array[A].size read from Matrix's construction path (C6a fix).
  • Matrix[A] rewritten to hold a single val layout: Layout with inline forwarders for all existing fields — zero source-compatibility breaks.
  • Matrix.apply(raw, layout) checked factory (throws IllegalArgumentException on raw.size != layout.dataLength); Matrix.mkMatrix unchecked private[vecxt] path for internal callers.
  • object Layout with Strided: Byte = 0; kind field reserved for Phase 3.
  • Matrix#layout: String renamed to layoutString (8 sites updated).

Phase 2 — adopt Layout at call sites

  • ~9 same-layout constructions (Matrix(newArr, m.rows, m.cols, m.rowStride, m.colStride, m.offset)Matrix(newArr, m.layout)) across doublematrix.scala and floatmatrix.scala. Sites allocating Array.ofDim[Double](m.numel) left unchanged — those arrays may differ in size from m.raw.
  • ~50 index-arithmetic sites (m.offset + i*m.rowStride + j*m.colStridem.layout.linearIndex(i, j)) across doublematrix.scala, floatmatrix.scala, intmatrix.scala, doublematrix_native.scala, and MatrixInstance.scala. Loop-hoisted expressions preserved.
  • matrixutil.transposeMatrix.mkMatrix(m.raw, m.layout.transpose).
  • sameDenseElementWiseMemoryLayoutCheck delegates to a.layout.sameElementOrderAs(b.layout) (exact operator precedence preserved).

New test coverage

vecxt/test/src/layout.test.scala — derived flag correctness, linearIndex (strided/offset/transposed/broadcast), transpose round-trip property, sameElementOrderAs, equals/hashCode consistency, Matrix.apply(raw, layout) invariant enforcement.

// Before
Matrix[Double](newArr, m.rows, m.cols, m.rowStride, m.colStride, m.offset)
val idx = m.offset + i * m.rowStride + j * m.colStride

// After
Matrix(newArr, m.layout)
val idx = m.layout.linearIndex(i, j)

Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor matrix layout fields into a Layout value Extract Matrix[A] layout fields into Layout value class (Phase 1 + 2) Aug 2, 2026
Copilot AI requested a review from Quafadas August 2, 2026 15:10
@github-actions

github-actions Bot commented Aug 2, 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()): 4

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 51103 46714 +9.4%
distinct library ops 178 177 +0.6%
bytes per op 287.1 263.9 +8.8%
severity check at method detail
WARN C1 cheatsheet.scala:118 CheatsheetTest$.matrixRangeSlicing 6996 bytes, past 68% of HugeMethodLimit=8000 (assumed)
Annotated methods (47)
method annotations bytes budget used loop at
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:818
vecxt.floatarrays$.clamp$bang @AllocFree @HotPath 172 53% of 325 yes floatarrays.scala:380
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.doublearrays$.fillLinspace @AllocFree @HotPath 133 41% of 325 yes doublearrays.scala:34
vecxt.intarrays$.dot @AllocFree @HotPath 129 40% of 325 yes intarrays.scala:319
vecxt.intarrays$.increments @HotPath 121 37% of 325 yes intarrays.scala:188
vecxt.ndarray$.mkNDArray @Thin 13 37% of 35 no ndarray.scala:178
vecxt.doublearrays$.increments @HotPath 110 34% of 325 yes doublearrays.scala:360
vecxt.floatarrays$.increments @HotPath 97 30% of 325 yes floatarrays.scala:615
vecxt.doublearrays$.$times$times$bang @AllocFree @HotPath 95 29% of 325 yes doublearrays.scala:335
vecxt.doublearrays$.$minus$eq @AllocFree @HotPath 92 28% of 325 yes doublearrays.scala:1009
vecxt.doublearrays$.$plus$eq @AllocFree @HotPath 90 28% of 325 yes doublearrays.scala:944
vecxt.doublearrays$.$times$eq @AllocFree @HotPath 88 27% of 325 yes doublearrays.scala:1081
vecxt.doublearrays$.productSIMD @AllocFree @HotPath 86 26% of 325 yes doublearrays.scala:646
vecxt.floatarrays$.$times$times$bang @AllocFree @HotPath 85 26% of 325 yes floatarrays.scala:277
vecxt.doublearrays$.sumSIMD @AllocFree @HotPath 85 26% of 325 yes doublearrays.scala:623
vecxt.doublearrays$.fma$bang @AllocFree @HotPath 85 26% of 325 yes doublearrays.scala:984
vecxt.intarrays$.$plus$eq @AllocFree @HotPath 84 26% of 325 yes intarrays.scala:496
vecxt.intarrays$.$minus$eq @AllocFree @HotPath 84 26% of 325 yes intarrays.scala:469
vecxt.floatarrays$.$times$eq @AllocFree @HotPath 84 26% of 325 yes floatarrays.scala:837
vecxt.floatarrays$.$plus$eq @AllocFree @HotPath 84 26% of 325 yes floatarrays.scala:735
vecxt.floatarrays$.$minus$eq @AllocFree @HotPath 84 26% of 325 yes floatarrays.scala:775
vecxt.doublearrays.meanAndVariance @Thin 9 26% of 35 no doublearrays.scala
vecxt.doublearrays$.meanAndVariance @Thin 9 26% of 35 no doublearrays.scala:501
vecxt.intarrays$.minSIMD @AllocFree @HotPath 82 25% of 325 yes intarrays.scala:516
vecxt.intarrays$.maxSIMD @AllocFree @HotPath 82 25% of 325 yes intarrays.scala:536
vecxt.floatarrays$.productSIMD @AllocFree @HotPath 82 25% of 325 yes floatarrays.scala:500
vecxt.intarrays$.sumSIMD @AllocFree @HotPath 81 25% of 325 yes intarrays.scala:233
vecxt.floatarrays$.sumSIMD @AllocFree @HotPath 81 25% of 325 yes floatarrays.scala:479
vecxt.floatarrays$.fma$bang @AllocFree @HotPath 79 24% of 325 yes floatarrays.scala:303
vecxt.floatarrays$.$times$eq @AllocFree @HotPath 78 24% of 325 yes floatarrays.scala:889
vecxt.ndarray.shapeArray @Thin 8 23% of 35 no ndarray.scala
vecxt.intarrays$.$minus$eq @AllocFree @HotPath 67 21% of 325 yes intarrays.scala:351
vecxt.floatarrays$.$plus$eq @AllocFree @HotPath 24 7% of 325 no floatarrays.scala:694
Method sizes
band methods
<= 6 (trivial, always inlined) 1409
7-35 (inlinable cold) 2944
36-325 (inlinable when hot) 595
326-8000 (not inlined) 112
> 8000 (NEVER JIT COMPILED) 0
bytes method module at
6996 CheatsheetTest$.matrixRangeSlicing experiments cheatsheet.scala:118
4480 CheatsheetTest$.matrixReverseSlicing experiments cheatsheet.scala:125
4329 CheatsheetTest$.ndArrayBoolean experiments cheatsheet.scala:430
4126 CheatsheetTest$.ndArrayInt experiments cheatsheet.scala:416
3549 CheatsheetTest$.ndArrayFloat experiments cheatsheet.scala:395
3283 CheatsheetTest$.matrixOps experiments cheatsheet.scala:167
3256 CheatsheetTest$.matrixCreation experiments cheatsheet.scala:72
3167 CheatsheetTest$.ndArrayFloatReductions experiments cheatsheet.scala:405
2500 vecxt_re.Tower.show vecxt_re Tower.scala:63
2042 vecxt.JvmDoubleMatrix$.$plus$eq vecxt doublematrix.scala:315
2028 vecxt.JvmFloatMatrix$.floatmatrixAddScalarInPlace vecxt floatmatrix.scala:442
2028 vecxt.JvmFloatMatrix$.floatmatrixSubScalarInPlace vecxt floatmatrix.scala:514
2023 CheatsheetTest$.arrayMath experiments cheatsheet.scala:253
1921 CheatsheetTest$.indexingAndSlicing experiments cheatsheet.scala:98
1594 vecxt.ndarrayOps$.apply vecxt ndarrayOps.scala:434
1556 vecxt.DoubleMatrix$.$minus$colon$minus vecxt doublematrix.scala:232
1554 vecxt.DoubleMatrix$.$plus$colon$plus vecxt doublematrix.scala:143
1504 vecxt.JvmFloatMatrix$.floatmatrixSubVector vecxt floatmatrix.scala:336
1478 CheatsheetTest$.arrayManipulation experiments cheatsheet.scala:305
1403 vecxt.Svd$.pinv vecxt svd.scala:42
1346 vecxt.JvmDoubleMatrix$.$plus$eq vecxt doublematrix.scala:227
1322 vecxt.JvmFloatMatrix$.floatmatrixAddVectorInPlace vecxt floatmatrix.scala:248
1322 vecxt.JvmFloatMatrix$.floatmatrixSubVectorInPlace vecxt floatmatrix.scala:360
1198 CheatsheetTest$.matrixFloat experiments cheatsheet.scala:445
1102 vecxt.DoubleMatrix$.hadamard vecxt doublematrix.scala:171
Proposed baseline
{
  "jdkMajor": 25,
  "c9": { "totalBytes": 51103, "distinctOps": 178 },
  "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$.$minus$eq([DD)V": 92,
    "vecxt.doublearrays$.$plus$eq([DD)V": 90,
    "vecxt.doublearrays$.$times$eq([D[D)V": 88,
    "vecxt.doublearrays$.$times$times$bang([DD)V": 95,
    "vecxt.doublearrays$.clamp$bang([DDD)V": 180,
    "vecxt.doublearrays$.fillLinspace([DDD)V": 133,
    "vecxt.doublearrays$.fma$bang([DDD)V": 85,
    "vecxt.doublearrays$.increments([D)[D": 110,
    "vecxt.doublearrays$.meanAndVariance([D)Lscala/Tuple2;": 9,
    "vecxt.doublearrays$.productSIMD([D)D": 86,
    "vecxt.doublearrays$.sumSIMD([D)D": 85,
    "vecxt.doublearrays.meanAndVariance([DLvecxt/VarianceMode;)Lscala/Tuple2;": 9,
    "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$.clamp$bang([FFF)V": 172,
    "vecxt.floatarrays$.fma$bang([FFF)V": 79,
    "vecxt.floatarrays$.increments([F)[F": 97,
    "vecxt.floatarrays$.productSIMD([F)F": 82,
    "vecxt.floatarrays$.sumSIMD([F)F": 81,
    "vecxt.intarrays$.$minus$eq([II)V": 67,
    "vecxt.intarrays$.$minus$eq([I[I)V": 84,
    "vecxt.intarrays$.$plus$eq([I[I)V": 84,
    "vecxt.intarrays$.dot([I[I)I": 129,
    "vecxt.intarrays$.increments([I)[I": 121,
    "vecxt.intarrays$.maxSIMD([I)I": 82,
    "vecxt.intarrays$.minSIMD([I)I": 82,
    "vecxt.intarrays$.sumSIMD([I)I": 81,
    "vecxt.ndarray$.mkNDArray(Ljava/lang/Object;[I[II)Lvecxt/ndarray$NDArray;": 13,
    "vecxt.ndarray.shapeArray(Lvecxt/ndarray$NDArray;)[I": 8
  }
}

@Quafadas

Quafadas commented Aug 2, 2026

Copy link
Copy Markdown
Owner

@copilot please look at the bytecode test failure?

…ods)

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

Copilot AI commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please look at the bytecode test failure?

The @Thin annotation on inline def linearIndex was the culprit. Since inline methods are expanded into their callers and never emitted as their own bytecode, the audit's A1 check correctly flagged it as invalid — there's no method in the classfile to measure.

Fixed in the latest commit by removing @Thin and updating the comment to reflect that the method inlines into ~4 JVM bytecodes at each call site. All 437 bytecode audit tests now pass.

@Quafadas

Quafadas commented Aug 3, 2026

Copy link
Copy Markdown
Owner

@copilot This constructor is unvalidated matrix.scala:212 - can you fix it pelase?

…ed Thin import

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

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot This constructor is unvalidated matrix.scala:212 - can you fix it pelase?

Fixed in the latest commit. Matrix.apply[A](raw, layout) now calls strideMatInstantiateCheck(raw, layout.rows, layout.cols, layout.rowStride, layout.colStride, layout.offset) after the dataLength check, matching the validation done by all other apply overloads. A new test covers the case where strides make an element unreachable within the array bounds.

@Quafadas
Quafadas marked this pull request as ready for review August 3, 2026 08:27
@Quafadas
Quafadas merged commit f6b625a into main Aug 3, 2026
7 checks passed
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.

# Plan: extract Matrix[A]'s layout fields into a Layout value

2 participants