Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
47 changes: 43 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,29 @@ jobs:

- uses: coursier/setup-action@main
with:
jvm: temurin@17
jvm: temurin@24
apps: scala-cli

- name: Setup NodeJS v18 LTS
- name: Cache Mill
uses: actions/cache@v4
with:
path: |
~/.cache/mill
out/
key: ${{ runner.os }}-mill-${{ matrix.project }}-${{ hashFiles('build.mill', '**/*.mill') }}
restore-keys: |
${{ runner.os }}-mill-${{ matrix.project }}-
${{ runner.os }}-mill-

- name: Cache Coursier
uses: actions/cache@v4
with:
path: ~/.cache/coursier
key: ${{ runner.os }}-coursier-${{ hashFiles('build.mill', '**/*.mill') }}
restore-keys: |
${{ runner.os }}-coursier-

- name: Setup NodeJS v20 LTS
if: matrix.project == 'js'
uses: actions/setup-node@v3
with:
Expand All @@ -57,8 +76,19 @@ jobs:
- if: matrix.project == 'js'
run: npm install

- if: matrix.project == 'native'
run: sudo apt-get install -y libatlas-base-dev
- name: Cache native dependencies
if: matrix.project == 'native' || matrix.project == 'jvm'
uses: actions/cache@v4
with:
path: |
/var/cache/apt
/var/lib/apt
key: ${{ runner.os }}-apt-blas-blis-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: |
${{ runner.os }}-apt-blas-blis-

- if: matrix.project == 'native' || matrix.project == 'jvm'
run: sudo apt-get update && sudo apt-get install -y libatlas-base-dev libblis-dev

- name: scalaJSLink
if: matrix.project == 'js'
Expand All @@ -70,7 +100,16 @@ jobs:

- name: Test
run: ./millw vecxt.${{ matrix.project }}.test

- name: Test
run: ./millw vecxtensions.${{ matrix.project }}.test

- name: Test
if: matrix.project == 'jvm'
run: ./millw experiments.test

- name: Doc Gen
if: matrix.project == 'jvm'
run: ./millw site.siteGen

publish:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ metals.sbt
.vscode/settings.json
sbt-launch.jar
.scala-build
.DS_Store

# npm
node_modules/
Expand Down
79 changes: 59 additions & 20 deletions experiments/package.mill
Original file line number Diff line number Diff line change
@@ -1,26 +1,65 @@
package build.experiments

import mill.*, scalalib.*, publish.*
import contrib.jmh.JmhModule

// mill benchmark.runJmh vecxt.benchmark.AndBooleanBenchmark -jvmArgs --add-modules=jdk.incubator.vector -rf json

object `package` extends ScalaModule:
// def enableBsp = false
def scalaVersion = build.vecxt.jvm.scalaVersion
override def compileResources = Task {
super.compileResources() ++ resources()
}
def scalacOptions = Seq("-Xmax-inlines:10000")
override def forkArgs = super.forkArgs() ++ build.vecIncubatorFlag
// override def mainClass = Some("mnist")



override def moduleDeps = Seq(build.vecxt.jvm)
override def mvnDeps = super.mvnDeps() ++ Seq(
mvn"com.lihaoyi::os-lib::0.10.4",
mvn"io.github.quafadas::scautable::0.0.28",
mvn"io.github.quafadas::dedav4s::0.10.0-RC2"
)
end `package`
object `package` extends ScalaModule {
// "-Djava.library.path=/opt/homebrew/Cellar/blis/2.0/lib"

/**
* Path to the BLIS library. This assumes that you used
* On mac: brew install blis
* On ubuntu: apt install libblis3 libblis-dev [Not sure how general these settibngs are]
* Windows support is not implemented, not sure if BLIS is available on Windows.
*/

def pathToBlis = Task {
import scala.util.Properties

val osName = Properties.osName.toLowerCase
if (osName.contains("linux")) {
"""/usr/lib/x86_64-linux-gnu/"""
} else if (osName.contains("mac")) {
"""/opt/homebrew/Cellar/blis/2.0/lib"""
} else if (osName.contains("windows")) {
???
} else {
throw new Exception(s"Unsupported OS: $osName")
}
}

def scalaVersion = build.vecxt.jvm.scalaVersion
override def compileResources = Task {
super.compileResources() ++ resources()
}
def scalacOptions: T[Seq[String]] = Seq("-Xmax-inlines:10000")
override def forkArgs: T[Seq[String]] = super.forkArgs() ++ build.vecIncubatorFlag ++ Seq(
s"-Djava.library.path=${pathToBlis()}",
"-Djava.library.path=/Users/simon/Code/mlx-c/build",
"--enable-native-access=ALL-UNNAMED"
)
override def mainClass = Some("vecxt.experiments.mlxDemo")
override def moduleDeps: Seq[JavaModule] = Seq(build.vecxt.jvm, build.vecxtensions.jvm, build.generated)
override def mvnDeps = super.mvnDeps() ++ Seq(
mvn"com.lihaoyi::os-lib::0.10.4",
mvn"io.github.quafadas::scautable::0.0.28",
mvn"io.github.quafadas::dedav4s::0.10.0-RC2"

)

object test extends ScalaTests with TestModule.Munit {
def scalaVersion = build.vecxt.jvm.scalaVersion
override def mvnDeps= super.mvnDeps() ++ Seq(
mvn"org.scalameta::munit::${build.V.munitVersion}"
)

override def moduleDeps: Seq[JavaModule] = Seq(build.experiments)

override def forkArgs: T[Seq[String]] = super.forkArgs() ++ build.vecIncubatorFlag ++ Seq(
s"-Djava.library.path=${pathToBlis()}"
)

}

}
36 changes: 36 additions & 0 deletions experiments/src/BlisArena.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package vecxt.experiments

import java.lang.foreign.MemorySegment
import java.lang.foreign.MemoryLayout
import java.lang.foreign.Arena
import java.lang.foreign.ValueLayout
import blis_typed.blis_h
import scala.collection.mutable

/** This _should_ be unecessary for blis objects initialised with `blis_obj_create_with_attached_buffer`. From the docs
* of BLIS blis_obj_create_with_attached_buffer:
*
* > Objects initialized via this function should generally not be passed to bli_obj_free(), unless the user wishes to
* pass p into free().
*
* @param underlying
*/
class BlisArena(private val underlying: Arena) extends Arena:
private val blisObjects = mutable.ListBuffer[MemorySegment]()

inline def allocate(byteSize: Long, byteAlignment: Long): MemorySegment = underlying.allocate(byteSize, byteAlignment)

inline def scope = underlying.scope

def registerBlisObject(obj: MemorySegment): Unit =
blisObjects += obj

override def close(): Unit =
// Free all BLIS objects first
blisObjects.foreach(blis_h.bli_obj_free)
blisObjects.clear()

// Then close the underlying arena
underlying.close()
end close
end BlisArena
79 changes: 79 additions & 0 deletions experiments/src/DoubleVector.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package vecxt.experiments

import java.lang.foreign.MemorySegment
import java.lang.foreign.Arena
import java.lang.foreign.ValueLayout
import blis_typed.blis_h

opaque type DoubleVector = MemorySegment

object DoubleVector:

extension (v: DoubleVector)

inline def raw: MemorySegment = v

inline def apply(index: Long): Double =
v.getAtIndex(ValueLayout.JAVA_DOUBLE, index)

inline def update(index: Long, value: Double): Unit =
v.setAtIndex(ValueLayout.JAVA_DOUBLE, index, value)

inline def length: Long =
v.byteSize() / ValueLayout.JAVA_DOUBLE.byteSize()

inline def copy(using arena: Arena): DoubleVector =
val newM = arena.allocate(ValueLayout.JAVA_DOUBLE, v.length)
MemorySegment.copy(v, 0L, newM, 0L, v.byteSize())
newM
end copy

inline def toSeq: Seq[Double] =
(0L until v.length).map(i => v.getAtIndex(ValueLayout.JAVA_DOUBLE, i))

/** This will allocate an object that will be freed after the arena is closed. It does _not_ cleanup this memory
* segment itself on free.
*
* https://github.com/flame/blis/blob/master/docs/BLISObjectAPI.md#object-management > Objects initialized via this
* function should generally not be passed to bli_obj_free(), unless the user wishes to pass p into free().
* @param arena
* @return
*/
inline def blis_obj_t(using arena: Arena) =
val objSegment = arena.allocate(512L)

blis_h.bli_obj_create_with_attached_buffer(
blis_h.BLIS_DOUBLE(), // dt: BLIS_DOUBLE for double precision
1L, // m: 1 row (row vector)
v.length, // n: length columns
v.raw, // p: pointer to the actual data
v.length, // rs: row stride = length (distance between rows)
1L, // cs: column stride = 1 (contiguous elements)
objSegment // obj: output object
)
objSegment
end blis_obj_t

def +=(vec2: DoubleVector)(using arena: Arena): Unit =
blis_h.bli_addv(vec2.blis_obj_t, v.blis_obj_t)

def +(vec2: DoubleVector)(using arena: Arena): DoubleVector =
val result = v.copy
result += vec2
result
end +
end extension

// Static methods for creating DoubleVector instances
inline def ofSize(size: Long)(using arena: Arena): DoubleVector =
arena.allocate(ValueLayout.JAVA_DOUBLE, size)

inline def apply(size: Long)(using arena: Arena): DoubleVector =
arena.allocate(ValueLayout.JAVA_DOUBLE, size)

inline def apply(data: Seq[Double])(using arena: Arena): DoubleVector =
arena.allocateFrom(
ValueLayout.JAVA_DOUBLE,
data*
)
end DoubleVector
81 changes: 81 additions & 0 deletions experiments/src/FloatVector.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package vecxt.experiments

import java.lang.foreign.MemorySegment
import java.lang.foreign.Arena
import java.lang.foreign.ValueLayout

import blis_typed.blis_h

opaque type FloatVector = MemorySegment

object FloatVector:

extension (v: FloatVector)

inline def raw: MemorySegment = v

inline def apply(index: Long): Float =
v.getAtIndex(ValueLayout.JAVA_FLOAT, index)

inline def update(index: Long, value: Float): Unit =
v.setAtIndex(ValueLayout.JAVA_FLOAT, index, value)

inline def length: Long =
v.byteSize() / ValueLayout.JAVA_FLOAT.byteSize()

inline def copy(using arena: Arena): FloatVector =
val newM = arena.allocate(ValueLayout.JAVA_FLOAT, v.length)
MemorySegment.copy(v, 0L, newM, 0L, v.byteSize())
newM
end copy

inline def toSeq: Seq[Float] =
(0L until v.length).map(i => v.getAtIndex(ValueLayout.JAVA_FLOAT, i))

/** This will allocate an object that will be freed after the arena is closed. It does _not_ cleanup this memory
* segment itself on free.
*
* https://github.com/flame/blis/blob/master/docs/BLISObjectAPI.md#object-management > Objects initialized via this
* function should generally not be passed to bli_obj_free(), unless the user wishes to pass p Floato free().
* @param arena
* @return
*/
inline def blis_obj_t(using arena: Arena) =
val objSegment = arena.allocate(512L)

blis_h.bli_obj_create_with_attached_buffer(
blis_h.BLIS_FLOAT(), // dt: BLIS_DOUBLE for double precision
1L, // m: 1 row (row vector)
v.length, // n: length columns
v.raw, // p: poFloater to the actual data
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the comment: 'poFloater' should be 'pointer'.

Suggested change
v.raw, // p: poFloater to the actual data
v.raw, // p: pointer to the actual data

Copilot uses AI. Check for mistakes.
v.length, // rs: row stride = length (distance between rows)
1L, // cs: column stride = 1 (contiguous elements)
objSegment // obj: output object
)
objSegment
end blis_obj_t

def +=(vec2: FloatVector)(using Arena): Unit =
blis_h.bli_addv(vec2.blis_obj_t, v.blis_obj_t)

def +(vec2: FloatVector)(using arena: Arena): FloatVector =
val result = v.copy
result += vec2
result
end +
end extension

// Static methods for FloatVector creation

inline def ofSize(size: Long)(using arena: Arena): FloatVector =
arena.allocate(ValueLayout.JAVA_FLOAT, size)

inline def apply(size: Long)(using arena: Arena): FloatVector =
arena.allocate(ValueLayout.JAVA_FLOAT, size)

inline def apply(data: Seq[Float])(using arena: Arena): FloatVector =
arena.allocateFrom(
ValueLayout.JAVA_FLOAT,
data*
)
end FloatVector
Loading
Loading