Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
512f0f4
Do not use number formatter for wasm
pedrovgs Jan 19, 2026
7af4032
Make mfarray compatible with wasm by disabling coreml features
pedrovgs Jan 19, 2026
696ece5
Make mfadata compatible with wasm
pedrovgs Jan 19, 2026
0685139
Get mftype compatible with wasm
pedrovgs Jan 19, 2026
bc7ab32
Provide now a mftype fallback types we when accelerate is not available
pedrovgs Jan 19, 2026
a755923
Get order now compatible with XP
pedrovgs Jan 19, 2026
654b9ff
Get a fallback implementaiton if accelerate is not availbe in types.s…
pedrovgs Jan 19, 2026
3ff8285
Get some extra wrappers for libraries we don't support
pedrovgs Jan 19, 2026
86b934d
Get now method folder compatible with wasm
pedrovgs Jan 19, 2026
f9112a2
Get fallback implementation for static folder
pedrovgs Jan 19, 2026
b6a021d
Get library and wrapper compatible with WASM
pedrovgs Jan 19, 2026
fd1101b
Add support for complex testing scenarios
pedrovgs Jan 19, 2026
d2d85dd
Disable complex tests
pedrovgs Jan 19, 2026
27f3c4b
Improve import usage
pedrovgs Jan 19, 2026
bda0fe7
Use the right param names
pedrovgs Jan 19, 2026
c1787d7
Add fallback implementations with fatal errors until we are able to i…
pedrovgs Jan 19, 2026
2c510f8
Disable performance tests temporally
pedrovgs Jan 19, 2026
3817487
Update random tests to reduce memory pressure
pedrovgs Jan 19, 2026
7327253
Fix import
pedrovgs Jan 19, 2026
48bda80
Update wrong import
pedrovgs Jan 19, 2026
acd15f4
Disable some other performance tests
pedrovgs Jan 19, 2026
8e6d4f1
Enable back wasm fallback tests and revert a change made by mistake
pedrovgs Jan 19, 2026
31c679f
Update clapack pacakge and add some guards to our code to prevent ten…
pedrovgs Jan 19, 2026
b0fbfa9
Use https for dependency instead of ssh
pedrovgs Jan 19, 2026
89cb093
Disable some perf tests temporally for WASM
pedrovgs Jan 20, 2026
9fec130
Fix CI by forcing the toolchain version we have to use
pedrovgs Jan 20, 2026
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
42 changes: 33 additions & 9 deletions .github/workflows/wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,45 @@ name: wasm

on:
push:
pull_request:

env:
# Required Swift toolchain version for WASM builds
# Must match REQUIRED_TOOLCHAIN_VERSION in scripts/build-and-test-wasm.sh
SWIFT_TOOLCHAIN_VERSION: "DEVELOPMENT-SNAPSHOT-2025-11-03-a"
# Checksum for the WASM SDK (from SwiftWasm release page)
SWIFT_WASM_SDK_CHECKSUM: "879c08f24c36e20e0b3d1fadc37f4c34c089c72caa018aec726d9e0bf84ea6ff"

jobs:
build:
runs-on: macos-latest

runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

# Use Swift 6.0.3 to match the pinned SDK version in build-and-test-wasm.sh
# The script will automatically install the 6.0.3-RELEASE WASM SDK
- name: Install Swift
uses: swift-actions/setup-swift@v2
with:
swift-version: "6.1"
# Install the specific Swift development snapshot required for WASM builds
# Toolchain comes from swift.org, SDK comes from SwiftWasm
- name: Install Swift Toolchain
run: |
SWIFT_URL="https://download.swift.org/development/ubuntu2404/swift-${SWIFT_TOOLCHAIN_VERSION}/swift-${SWIFT_TOOLCHAIN_VERSION}-ubuntu24.04.tar.gz"
echo "Downloading Swift toolchain from: $SWIFT_URL"
mkdir -p /opt/swift
curl -sL "$SWIFT_URL" | tar xz --strip-components=1 -C /opt/swift
echo "/opt/swift/usr/bin" >> $GITHUB_PATH

- name: Verify Swift Installation
run: swift --version

# Install the matching WASM SDK from SwiftWasm
- name: Install WASM SDK
run: |
SDK_URL="https://github.com/swiftwasm/swift/releases/download/swift-wasm-${SWIFT_TOOLCHAIN_VERSION}/swift-wasm-${SWIFT_TOOLCHAIN_VERSION}-wasm32-unknown-wasip1-threads.artifactbundle.zip"
echo "Installing WASM SDK from: $SDK_URL"
swift sdk install "$SDK_URL" --checksum "$SWIFT_WASM_SDK_CHECKSUM"
echo "Installed SDKs:"
swift sdk list

# Set environment variable to signal the correct toolchain is installed
- name: Set Toolchain Environment
run: echo "SWIFT_WASM_TOOLCHAIN_VERIFIED=1" >> $GITHUB_ENV

# Wasmtime is required because `swift test` doesn't work for WebAssembly targets.
# For WASM, we must build tests separately and run them with a WASM runtime.
Expand Down
2 changes: 1 addition & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-collections", from: "1.0.0"),
.package(url: "https://github.com/GoodNotes/CLAPACK", branch: "eigen-support"),
.package(url: "https://github.com/goodnotes/CLAPACK", branch: "eigen-support"),
],
targets: [
.target(
Expand Down
17 changes: 15 additions & 2 deletions Sources/Matft/core/general/print.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,22 @@ extension MfArray: CustomStringConvertible{
var shape = self.shape
var strides = self.strides

#if os(WASI)
let formatter: NumberFormatter? = nil
#else
let formatter = NumberFormatter()
formatter.positivePrefix = formatter.plusSign
formatter.maximumFractionDigits = self.storedType == .Float ? 7 : 14
#endif

func imagString(_ value: Any) -> String{
#if os(WASI)
// Avoid NumberFormatter on WASI (Foundation formatter crashes in wasm)
return "\(value)"
#else
return formatter.string(for: value) ?? "\(value)"
#endif
}

if self.size > 1000{//if size > 1000, some elements left out will be viewed
let flattenLOIndSeq = FlattenLOIndSequence(storedSize: self.storedSize, shape: &shape, strides: &strides)
Expand All @@ -39,7 +52,7 @@ extension MfArray: CustomStringConvertible{
desc += "\t\(flattenData[flattenIndex + self.offsetIndex]),\t"
}
else{
desc += "\t\(flattenData[flattenIndex + self.offsetIndex]) \(formatter.string(for: flattenImagData![flattenIndex + self.offsetIndex]) ?? "")j,\t"
desc += "\t\(flattenData[flattenIndex + self.offsetIndex]) \(imagString(flattenImagData![flattenIndex + self.offsetIndex]))j,\t"
}

if indices.last! == shape.last! - 1{
Expand Down Expand Up @@ -81,7 +94,7 @@ extension MfArray: CustomStringConvertible{
desc += "\t\(flattenData[ret.flattenIndex + self.offsetIndex]),\t"
}
else{
desc += "\t\(flattenData[ret.flattenIndex + self.offsetIndex]) \(formatter.string(for: flattenImagData![ret.flattenIndex + self.offsetIndex]) ?? "")j,\t"
desc += "\t\(flattenData[ret.flattenIndex + self.offsetIndex]) \(imagString(flattenImagData![ret.flattenIndex + self.offsetIndex]))j,\t"
}

if ret.indices.last! == shape.last! - 1{
Expand Down
5 changes: 4 additions & 1 deletion Sources/Matft/core/object/mfarray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
//

import Foundation
import Accelerate
#if canImport(CoreML)
import CoreML
#endif

open class MfArray: MfArrayProtocol{
public typealias MFDATA = MfData
Expand Down Expand Up @@ -111,6 +112,7 @@ open class MfArray: MfArrayProtocol{
self.mfstructure = mfstructure//mfstructure will be copied because mfstructure is struct
}

#if canImport(CoreML)
/// Create a VIEW or Copy mfarray from MLShapedArray
/// - Parameters:
/// - base: A base MLShapedArray
Expand All @@ -126,6 +128,7 @@ open class MfArray: MfArrayProtocol{
self.mfdata = mfdata
self.mfstructure = MfStructure(shape: base.shape.map{ Int(truncating: $0) }, strides: base.strides.map{ Int(truncating: $0) })
}
#endif

deinit {
self.base = nil
Expand Down
9 changes: 4 additions & 5 deletions Sources/Matft/core/object/mfdata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

import Foundation
import Accelerate

internal enum MfDataSource{
case mfdata
Expand Down Expand Up @@ -72,7 +71,7 @@ public class MfData: MfDataProtocol{
case .Double:
// dynamic allocation
self.data_real = allocate_doubledata_from_flattenArray(&flatten_realArray, toBool: mftype == .Bool)
self.data_imag = allocate_floatdata_from_flattenArray(&flatten_imagArray, toBool: mftype == .Bool)
self.data_imag = allocate_doubledata_from_flattenArray(&flatten_imagArray, toBool: mftype == .Bool)
}
self.storedSize = flatten_realArray.count
self.mftype = mftype
Expand Down Expand Up @@ -106,18 +105,18 @@ public class MfData: MfDataProtocol{

if let data_imag_ptr = data_imag_ptr{
self.data_imag = allocate_unsafeMRPtr(type: Float.self, count: storedSize)
memcpy(self.data_imag, data_imag_ptr, self.storedByteSize)
memcpy(self.data_imag!, data_imag_ptr, self.storedByteSize)
}
else{
self.data_imag = nil
}
case .Double:
self.data_real = allocate_unsafeMRPtr(type: Double.self, count: storedSize)
memcpy(self.data_real, data_real_ptr, self.storedByteSize)

if let data_imag_ptr = data_imag_ptr{
self.data_imag = allocate_unsafeMRPtr(type: Double.self, count: storedSize)
memcpy(self.data_imag, data_imag_ptr, self.storedByteSize)
memcpy(self.data_imag!, data_imag_ptr, self.storedByteSize)
}
else{
self.data_imag = nil
Expand Down
6 changes: 5 additions & 1 deletion Sources/Matft/core/object/mfstructure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@ internal func shape2size(_ shape: inout [Int]) -> Int{
/// - mforder: Order
/// - Returns: A strides array
internal func shape2strides(_ shape: inout [Int], mforder: MfOrder) -> [Int]{
guard !shape.isEmpty else {
return []
}

var ret = Array<Int>(repeating: 0, count: shape.count)

switch mforder {
case .Row://, .None:
var prevAxisNum = shape2size(&shape)
Expand Down
5 changes: 4 additions & 1 deletion Sources/Matft/core/object/mftype.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
//

import Foundation
import Accelerate
#if canImport(CoreML)
import CoreML
#endif

public enum MfType: Int{
case None
Expand Down Expand Up @@ -65,6 +66,7 @@ public enum MfType: Int{
return MfType.mftype(value: value as Any)
}

#if canImport(CoreML)
@available(macOS 10.13, *)
@available(iOS 14.0, *)
static internal func mftype(value: MLMultiArrayDataType) -> MfType{
Expand All @@ -77,6 +79,7 @@ public enum MfType: Int{
return .Object // Not supported
}
}
#endif

static public func priority(_ a: MfType, _ b: MfType) -> MfType{
if a.rawValue < b.rawValue{
Expand Down
4 changes: 4 additions & 0 deletions Sources/Matft/core/protocol/mfdataProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
//

import Foundation
#if canImport(CoreML)
import CoreML
#endif

public protocol MfDataBasable {}

extension MfData: MfDataBasable{}

#if canImport(CoreML)
@available(macOS 10.13, *)
@available(iOS 14.0, *)
extension MLMultiArray: MfDataBasable{}
#endif
46 changes: 46 additions & 0 deletions Sources/Matft/core/protocol/mftypeProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,53 @@
//

import Foundation
#if canImport(Accelerate)
import Accelerate
#else
/// Fallback complex type for non-Apple platforms (split complex format for Float)
public struct DSPSplitComplex {
public var realp: UnsafeMutablePointer<Float>
public var imagp: UnsafeMutablePointer<Float>

public init(realp: UnsafeMutablePointer<Float>, imagp: UnsafeMutablePointer<Float>) {
self.realp = realp
self.imagp = imagp
}
}

/// Fallback complex type for non-Apple platforms (split complex format for Double)
public struct DSPDoubleSplitComplex {
public var realp: UnsafeMutablePointer<Double>
public var imagp: UnsafeMutablePointer<Double>

public init(realp: UnsafeMutablePointer<Double>, imagp: UnsafeMutablePointer<Double>) {
self.realp = realp
self.imagp = imagp
}
}

/// Fallback complex type for non-Apple platforms (interleaved complex format for Float)
public struct DSPComplex {
public var real: Float
public var imag: Float

public init(real: Float, imag: Float) {
self.real = real
self.imag = imag
}
}

/// Fallback complex type for non-Apple platforms (interleaved complex format for Double)
public struct DSPDoubleComplex {
public var real: Double
public var imag: Double

public init(real: Double, imag: Double) {
self.real = real
self.imag = imag
}
}
#endif
/*
public protocol MfTypable: Numeric{}

Expand Down
6 changes: 2 additions & 4 deletions Sources/Matft/core/util/common/order.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
//

import Foundation
import Accelerate


/// Copy mfarray including structure
/// - Parameter src_mfarray: The source mfarray
Expand Down Expand Up @@ -36,7 +34,7 @@ internal func copy_all_mfarray(_ src_mfarray: MfArray) -> MfArray{
srcptr in
dst_mfarray.withUnsafeMutableStartImagPointer(datatype: Float.self){
dstptr in
memcpy(dstptr, srcptr, MemoryLayout<Float>.size*newsize)
memcpy(dstptr!, srcptr, MemoryLayout<Float>.size*newsize)
}
}
}
Expand All @@ -53,7 +51,7 @@ internal func copy_all_mfarray(_ src_mfarray: MfArray) -> MfArray{
srcptr in
dst_mfarray.withUnsafeMutableStartImagPointer(datatype: Double.self){
dstptr in
memcpy(dstptr, srcptr, MemoryLayout<Double>.size*newsize)
memcpy(dstptr!, srcptr, MemoryLayout<Double>.size*newsize)
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions Sources/Matft/core/util/common/type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,47 @@
//

import Foundation
#if canImport(Accelerate)
import Accelerate
#endif

#if !canImport(Accelerate)
/// Pure Swift fallback for toBool_by_vDSP
internal func toBool_by_vDSP(_ mfarray: MfArray) -> MfArray {
let mfarray = check_contiguous(mfarray)
let size = mfarray.storedSize
let newdata = MfData(size: size, mftype: .Bool)

newdata.withUnsafeMutableStartPointer(datatype: Float.self) { dstptr in
mfarray.withUnsafeMutableStartPointer(datatype: Float.self) { srcptr in
for i in 0..<size {
dstptr[i] = srcptr[i] != 0 ? Float(1) : Float(0)
}
}
}

let newstructure = MfStructure(shape: mfarray.shape, strides: mfarray.strides)
return MfArray(mfdata: newdata, mfstructure: newstructure)
}

/// Pure Swift fallback for toIBool_by_vDSP
internal func toIBool_by_vDSP(_ mfarray: MfArray) -> MfArray {
let mfarray = check_contiguous(mfarray)
let size = mfarray.storedSize
let newdata = MfData(size: size, mftype: .Bool)

newdata.withUnsafeMutableStartPointer(datatype: Float.self) { dstptr in
mfarray.withUnsafeMutableStartPointer(datatype: Float.self) { srcptr in
for i in 0..<size {
dstptr[i] = srcptr[i] == 0 ? Float(1) : Float(0)
}
}
}

let newstructure = MfStructure(shape: mfarray.shape, strides: mfarray.strides)
return MfArray(mfdata: newdata, mfstructure: newstructure)
}
#endif

internal func to_Bool(_ mfarray: MfArray, thresholdF: Float = 1e-5, thresholdD: Double = 1e-10) -> MfArray{
//convert float and contiguous
Expand Down
2 changes: 2 additions & 0 deletions Sources/Matft/core/util/pointer/ptr2array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
//

import Foundation
#if canImport(Accelerate)
import Accelerate
#endif

//convert rawpointer to flattenarray via float or Double array
//All kinds of int and uint has been handled as float
Expand Down
2 changes: 2 additions & 0 deletions Sources/Matft/core/util/pointer/withptr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
//

import Foundation
#if canImport(Accelerate)
import Accelerate
#endif

extension MfArray{

Expand Down
3 changes: 3 additions & 0 deletions Sources/Matft/core/util/typeconversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
//

import Foundation
#if canImport(Accelerate)
import Accelerate
#endif
// Note: WASI fallback implementations for vDSP functions are defined in vDSP.swift

/// Get mftype from a flatten array
/// - Parameter flattenArray: Flatten array.
Expand Down
Loading