Skip to content

Commit 93b6d43

Browse files
authored
Provide partial WebAssembly support (#55)
* Enable coverage reports and add ci step for the wasm build. The build should fail because the code is still not modified * Update workflow toolchain version * Add test step * Fix iOS CI, update WASM CI for testing we will need later and provide some documentation + scripts * Update CI and swift version * Update CI * Enable shared memory * Do not use number formatter for wasm * Make mfarray compatible with wasm by disabling coreml features * Make mfadata compatible with wasm * Get mftype compatible with wasm * Provide now a mftype fallback types we when accelerate is not available * Get order now compatible with XP * Get a fallback implementaiton if accelerate is not availbe in types.swift * Get some extra wrappers for libraries we don't support * Get now method folder compatible with wasm * Get fallback implementation for static folder * Get library and wrapper compatible with WASM * Add support for complex testing scenarios * Disable complex tests * Improve import usage * Use the right param names * Add fallback implementations with fatal errors until we are able to implement this part * Disable performance tests temporally * Update random tests to reduce memory pressure * Fix import * Update wrong import * Disable some other performance tests * Enable back wasm fallback tests and revert a change made by mistake * Update clapack pacakge and add some guards to our code to prevent tentative errors * Use https for dependency instead of ssh * Disable some perf tests temporally for WASM * Fix CI by forcing the toolchain version we have to use
1 parent d70047a commit 93b6d43

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4687
-111
lines changed

.github/workflows/swift.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@ jobs:
1212

1313
steps:
1414
- uses: actions/checkout@v4
15-
- uses: swift-actions/setup-swift@v1
15+
- uses: swift-actions/setup-swift@v2
1616
with:
17-
swift-version: "5.9.2"
18-
- name: Build
19-
run: swift build -v
20-
- name: Run tests
21-
run: swift test -v
17+
swift-version: "6.1"
18+
- name: Build and Test
19+
run: ./scripts/build-and-test-ios.sh

.github/workflows/wasm.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: wasm
2+
3+
on:
4+
push:
5+
6+
env:
7+
# Required Swift toolchain version for WASM builds
8+
# Must match REQUIRED_TOOLCHAIN_VERSION in scripts/build-and-test-wasm.sh
9+
SWIFT_TOOLCHAIN_VERSION: "DEVELOPMENT-SNAPSHOT-2025-11-03-a"
10+
# Checksum for the WASM SDK (from SwiftWasm release page)
11+
SWIFT_WASM_SDK_CHECKSUM: "879c08f24c36e20e0b3d1fadc37f4c34c089c72caa018aec726d9e0bf84ea6ff"
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-24.04
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
# Install the specific Swift development snapshot required for WASM builds
20+
# Toolchain comes from swift.org, SDK comes from SwiftWasm
21+
- name: Install Swift Toolchain
22+
run: |
23+
SWIFT_URL="https://download.swift.org/development/ubuntu2404/swift-${SWIFT_TOOLCHAIN_VERSION}/swift-${SWIFT_TOOLCHAIN_VERSION}-ubuntu24.04.tar.gz"
24+
echo "Downloading Swift toolchain from: $SWIFT_URL"
25+
mkdir -p /opt/swift
26+
curl -sL "$SWIFT_URL" | tar xz --strip-components=1 -C /opt/swift
27+
echo "/opt/swift/usr/bin" >> $GITHUB_PATH
28+
29+
- name: Verify Swift Installation
30+
run: swift --version
31+
32+
# Install the matching WASM SDK from SwiftWasm
33+
- name: Install WASM SDK
34+
run: |
35+
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"
36+
echo "Installing WASM SDK from: $SDK_URL"
37+
swift sdk install "$SDK_URL" --checksum "$SWIFT_WASM_SDK_CHECKSUM"
38+
echo "Installed SDKs:"
39+
swift sdk list
40+
41+
# Set environment variable to signal the correct toolchain is installed
42+
- name: Set Toolchain Environment
43+
run: echo "SWIFT_WASM_TOOLCHAIN_VERIFIED=1" >> $GITHUB_ENV
44+
45+
# Wasmtime is required because `swift test` doesn't work for WebAssembly targets.
46+
# For WASM, we must build tests separately and run them with a WASM runtime.
47+
# See: https://book.swiftwasm.org/getting-started/testing.html
48+
- name: Install Wasmtime
49+
uses: bytecodealliance/actions/wasmtime/setup@v1
50+
with:
51+
version: "40.0.2"
52+
github_token: ${{ github.token }}
53+
54+
- name: Build and Test
55+
run: ./scripts/build-and-test-wasm.sh

.swiftpm/xcode/xcshareddata/xcschemes/Matft.xcscheme

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
buildConfiguration = "Debug"
5555
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
5656
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
57-
shouldUseLaunchSchemeArgsEnv = "YES">
57+
shouldUseLaunchSchemeArgsEnv = "YES"
58+
codeCoverageEnabled = "YES">
5859
<Testables>
5960
<TestableReference
6061
skipped = "NO">

Package.resolved

Lines changed: 20 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,35 @@
1-
// swift-tools-version:5.1
1+
// swift-tools-version:5.9
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
55

66
let package = Package(
77
name: "Matft",
88
products: [
9-
// Products define the executables and libraries produced by a package, and make them visible to other packages.
109
.library(
1110
name: "Matft",
1211
targets: ["Matft"]),
13-
//.library(name: "ReleaseTest", targets: ["Matft "])
1412
],
1513
dependencies: [
16-
// Dependencies declare other packages that this package depends on.
17-
// .package(url: /* package url */, from: "1.0.0"),
1814
.package(url: "https://github.com/apple/swift-collections", from: "1.0.0"),
19-
15+
.package(url: "https://github.com/goodnotes/CLAPACK", branch: "eigen-support"),
2016
],
2117
targets: [
22-
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
23-
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
2418
.target(
2519
name: "pocketFFT"
2620
),
2721
.target(
2822
name: "Matft",
29-
dependencies: ["Collections", "pocketFFT"]),
23+
dependencies: [
24+
.product(name: "Collections", package: "swift-collections"),
25+
"pocketFFT",
26+
.product(name: "CLAPACK", package: "CLAPACK", condition: .when(platforms: [.wasi])),
27+
]),
3028
.testTarget(
3129
name: "MatftTests",
3230
dependencies: ["Matft"]),
3331
.testTarget(
3432
name: "PerformanceTests",
3533
dependencies: ["Matft"]),
3634
]
37-
//cxxLanguageStandard: .gnucxx1z
3835
)

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ INFO: Support Complex!!
2727
+ [SwiftPM](#swiftpm)
2828
+ [Carthage](#carthage)
2929
+ [CocoaPods](#cocoapods)
30+
* [Build Scripts](#-build-scripts)
31+
+ [iOS/macOS Build & Test](#-iosmacos-build--test)
32+
+ [WebAssembly Build & Test](#-webassembly-build--test)
3033
* [Contact](#contact)
3134

3235
<strike>
@@ -858,6 +861,43 @@ So, a pull request is very welcome!!
858861
pod install
859862
```
860863

864+
## 🛠️ Build Scripts
865+
866+
Matft provides convenient bash scripts for building and testing the project locally.
867+
868+
### 🍎 iOS/macOS Build & Test
869+
870+
To build and test Matft for iOS/macOS platforms:
871+
872+
```bash
873+
./scripts/build-and-test-ios.sh
874+
```
875+
876+
This script will:
877+
- Build the project using `swift build`
878+
- Run all tests using `swift test`
879+
880+
### 🌐 WebAssembly Build & Test
881+
882+
To build and test Matft for WebAssembly:
883+
884+
```bash
885+
./scripts/build-and-test-wasm.sh
886+
```
887+
888+
This script will:
889+
- 📦 Check and install the Swift WASM SDK if needed
890+
- 🔧 Check and install wasmtime runtime if needed
891+
- 🔨 Build the project for WebAssembly
892+
- 🧪 Build and run tests using wasmtime
893+
894+
**Note:** The WASM script automatically handles SDK and runtime installation, so you can run it on a fresh machine without any prior setup!
895+
896+
### Requirements
897+
898+
- **iOS/macOS:** Swift 6.1 or later
899+
- **WebAssembly:** Swift 6.1 or later (SDK will be automatically installed)
900+
861901
## Contact
862902

863903
Feel free to ask this project or anything via <junnosuke.kado.git@gmail.com>

Sources/Matft/core/general/print.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,22 @@ extension MfArray: CustomStringConvertible{
2424
var shape = self.shape
2525
var strides = self.strides
2626

27+
#if os(WASI)
28+
let formatter: NumberFormatter? = nil
29+
#else
2730
let formatter = NumberFormatter()
2831
formatter.positivePrefix = formatter.plusSign
2932
formatter.maximumFractionDigits = self.storedType == .Float ? 7 : 14
33+
#endif
34+
35+
func imagString(_ value: Any) -> String{
36+
#if os(WASI)
37+
// Avoid NumberFormatter on WASI (Foundation formatter crashes in wasm)
38+
return "\(value)"
39+
#else
40+
return formatter.string(for: value) ?? "\(value)"
41+
#endif
42+
}
3043

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

4558
if indices.last! == shape.last! - 1{
@@ -81,7 +94,7 @@ extension MfArray: CustomStringConvertible{
8194
desc += "\t\(flattenData[ret.flattenIndex + self.offsetIndex]),\t"
8295
}
8396
else{
84-
desc += "\t\(flattenData[ret.flattenIndex + self.offsetIndex]) \(formatter.string(for: flattenImagData![ret.flattenIndex + self.offsetIndex]) ?? "")j,\t"
97+
desc += "\t\(flattenData[ret.flattenIndex + self.offsetIndex]) \(imagString(flattenImagData![ret.flattenIndex + self.offsetIndex]))j,\t"
8598
}
8699

87100
if ret.indices.last! == shape.last! - 1{

Sources/Matft/core/object/mfarray.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
//
88

99
import Foundation
10-
import Accelerate
10+
#if canImport(CoreML)
1111
import CoreML
12+
#endif
1213

1314
open class MfArray: MfArrayProtocol{
1415
public typealias MFDATA = MfData
@@ -111,6 +112,7 @@ open class MfArray: MfArrayProtocol{
111112
self.mfstructure = mfstructure//mfstructure will be copied because mfstructure is struct
112113
}
113114

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

130133
deinit {
131134
self.base = nil

Sources/Matft/core/object/mfdata.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
//
88

99
import Foundation
10-
import Accelerate
1110

1211
internal enum MfDataSource{
1312
case mfdata
@@ -72,7 +71,7 @@ public class MfData: MfDataProtocol{
7271
case .Double:
7372
// dynamic allocation
7473
self.data_real = allocate_doubledata_from_flattenArray(&flatten_realArray, toBool: mftype == .Bool)
75-
self.data_imag = allocate_floatdata_from_flattenArray(&flatten_imagArray, toBool: mftype == .Bool)
74+
self.data_imag = allocate_doubledata_from_flattenArray(&flatten_imagArray, toBool: mftype == .Bool)
7675
}
7776
self.storedSize = flatten_realArray.count
7877
self.mftype = mftype
@@ -106,18 +105,18 @@ public class MfData: MfDataProtocol{
106105

107106
if let data_imag_ptr = data_imag_ptr{
108107
self.data_imag = allocate_unsafeMRPtr(type: Float.self, count: storedSize)
109-
memcpy(self.data_imag, data_imag_ptr, self.storedByteSize)
108+
memcpy(self.data_imag!, data_imag_ptr, self.storedByteSize)
110109
}
111110
else{
112111
self.data_imag = nil
113112
}
114113
case .Double:
115114
self.data_real = allocate_unsafeMRPtr(type: Double.self, count: storedSize)
116115
memcpy(self.data_real, data_real_ptr, self.storedByteSize)
117-
116+
118117
if let data_imag_ptr = data_imag_ptr{
119118
self.data_imag = allocate_unsafeMRPtr(type: Double.self, count: storedSize)
120-
memcpy(self.data_imag, data_imag_ptr, self.storedByteSize)
119+
memcpy(self.data_imag!, data_imag_ptr, self.storedByteSize)
121120
}
122121
else{
123122
self.data_imag = nil

Sources/Matft/core/object/mfstructure.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,12 @@ internal func shape2size(_ shape: inout [Int]) -> Int{
7373
/// - mforder: Order
7474
/// - Returns: A strides array
7575
internal func shape2strides(_ shape: inout [Int], mforder: MfOrder) -> [Int]{
76+
guard !shape.isEmpty else {
77+
return []
78+
}
79+
7680
var ret = Array<Int>(repeating: 0, count: shape.count)
77-
81+
7882
switch mforder {
7983
case .Row://, .None:
8084
var prevAxisNum = shape2size(&shape)

0 commit comments

Comments
 (0)